0053: Monitor agents and the browser script

On this page

Status: accepted

Context

The proxy sees each request from the outside: its time, status, headers and body. What happened inside (the queries, the cache, the calls out, the exception, the job queued) only the app knows, and what happened in the browser only the page knows. Short Leash serves every kind of project, so this has to work without installing a package in each one, without changing the project's files, and without slowing it down.

Decision

  • One wire format. Every agent sends batches to the collector on 127.0.0.1 (POST /v2/batch). Each batch is one execution (a request, a job, a command) and its events, each with when it started after the execution began and how long it took. Batches carry a token Short Leash makes at each start. The collector refuses anything without it, so other programs on the computer can't fill the Monitor. Requests are linked by the id the proxy stamps (0051); jobs carry the id of the request that queued them.

  • Agents, loaded the way each runtime allows:

    Runtime Loaded by Hooks
    PHP (Laravel, Statamic) The prepend file (0011) and Short Leash's provider Stages, route, queries, cache, mail, notifications, views, HTTP calls, logs, exceptions, jobs, commands, schedules, Octane
    Python Granian pointed at Short Leash's boot file by its path, which wraps the app (LEASH_TARGET, PYTHONPATH); workers started with python -m leash_monitor.run WSGI/ASGI, Django, SQLAlchemy, logging, requests, httpx, Celery, RQ
    Ruby RUBYOPT=-r and a Railtie Rails notifications: queries, cache, mail, views, Active Job run in the app's process, Net::HTTP
    Node NODE_OPTIONS=--import, only into a Node that takes it (18.19 and 20.6 on) http.Server, fetch and http.request, pg, mysql2, better-sqlite3, console errors

    Each agent uses only its language's standard library, hooks a framework only when the app has already loaded it, never fails the app, and does nothing when Short Leash's variables aren't set. The agents load while dump capture is on; a site's "Short Leash's agent" switch leaves them out of its app. Everything else is covered by OpenTelemetry (0054).

  • The browser script.

    • Where it goes: for a site with browser capture on (the default), the proxy adds one same-origin script tag after <head> in HTML documents opened on this computer. It adds the tag as the page streams, so streaming pages keep streaming.
    • When it's skipped:
      • shared traffic;
      • pages the app compresses anyway (Short Leash asks the app for pages uncompressed);
      • fragments fetched by script;
      • pages whose Content-Security-Policy doesn't let them load their own scripts;
      • Short Leash's own sends.
    • What it reports: errors, unhandled rejections, console errors, the page's own calls and Web Vitals. The page's calls carry X-Leash-Page, so the API requests a page made are linked to it. The proxy strips that header before the site sees it.
    • Its paths: the script and its reports live under /.well-known/leash/, which never reaches the site and isn't there through a share. Reports are taken only as JSON from the site's own pages (Sec-Fetch-Site: same-origin, else a matching Origin), so another website open in the same browser can't post them, and a page view only joins a request of its own site.

Alternatives

  • A package in each project works best in production tools, but it changes the project, and Short Leash would need one per framework version.
  • OpenTelemetry only needs the app to be instrumented already, and few local projects are; it stays as the path for everything without an agent.
  • A browser extension would see every site and need installing in every browser.

Consequences

  • An app sees Short Leash's agent in its process (NODE_OPTIONS, RUBYOPT, its WSGI or ASGI wrapper) and Short Leash's tag in its pages, locally. The per-site switches turn browser capture and the agents off.
  • A page with a strict CSP that doesn't allow its own scripts gets no browser capture, and its policy is left as it is.
  • Each agent is code Short Leash has to keep working with new framework versions; when one breaks, the app still runs, and the Monitor shows less.