Utility · for debugging with an agent
Agent Log
Agent Log records what a web page does (console output, errors, network calls) in your own browser, then exports it as an OpenTelemetry log file that you hand to Claude or another agent to debug the page.
An agent that writes a web page cannot watch it run in your browser. One script records the page there, in this browser's IndexedDB, with no server involved; a red Logging badge shows while it records, and the badge's panel downloads or copies the log. Nothing is sent anywhere.
Turning it on
Bookmarklet, for any page. Drag this link to your bookmarks bar, open the page you want to debug, then click the bookmark. Clicking it again opens the panel.
Pages with a strict Content Security Policy (GitHub, many banks and web mail) block scripts from other sites, so the bookmarklet does nothing there and the browser's console shows a CSP error. Use the full-script paste below on those pages.
Console snippet. Paste this into the browser's developer console (F12, then Console). It loads the same script the bookmarklet does.
Code typed into the console is not subject to the page's CSP, so pasting the whole script (about 40 KB) works where loading it from this site does not.
Query parameter, for your own pages. Add the script to a page you build:
The script does nothing until the page is opened with ?logging=true. After that, logging stays on in that tab, across reloads, until you press Stop logging or open a URL with ?logging=false. A page that should always log adds a data-start attribute to the script tag. The script is also served from GitHub through jsDelivr at https://cdn.jsdelivr.net/gh/oaustegard/oaustegard.github.io@main/web-utilities/agent-log.js, which caches branch files and can lag the site. A <script> tag cannot load it from raw.githubusercontent.com, because GitHub serves raw files as text/plain with nosniff.
Trying it on this page
This page includes the script. Start logging, make a few log lines, then open the panel from the badge in the corner.
Watching elements
Agent Log can also record what happens on chosen elements: clicks, double clicks, typing (one record per pause, with the value), changes, form submits, focus, keys other than text typed into a field, and changes to the element's own content and attributes. Open the panel, press Watch an element, then click the element. While picking, ↑ and ↓ move to the parent or first child, Enter picks the highlighted element and Esc cancels. Each watch shows in the panel with the number of elements it matches; × removes it.
A watch is a CSS selector, and Agent Log keeps the watches for each site until they are removed, so they survive reloads and apps that redraw the element. An agent can name the elements itself: ?logging=true&watch=%23checkout (repeat watch= for more) on a page that includes the script, or AgentLog.watch("#checkout", { label: "checkout button" }) in the console on any page. Password fields, and fields whose name looks like a card number, token or one-time code, are recorded as [hidden].
Recorded events
Each record is an OpenTelemetry log record. Its eventName says where it came from:
| eventName | Recorded when | Severity |
|---|---|---|
console.log, .info, .debug, .warn, .error, .trace, .assert | the page calls the console method (assert only when it fails) | DEBUG to ERROR, by method |
error.uncaught | an exception reaches window | ERROR |
error.rejection | a promise rejects with no handler | ERROR |
error.resource | an image, script or stylesheet fails to load | ERROR |
csp.violation | the page's CSP blocks something | WARN |
http.fetch, http.xhr | a request finishes or fails: method, URL, status, duration | INFO; WARN at 400 and up; ERROR when it fails |
page.navigation, page.visibility | in-page navigation; the tab is hidden or shown | INFO |
dom.click, .dblclick, .input, .change, .submit, .focus, .blur, .keydown | an event on a watched element | INFO |
dom.mutation | a watched element's attributes, text or children change (at most 20 records a second per watch) | INFO |
dom.watch | a watch is added or removed, or the number of elements it matches changes | INFO; WARN at zero |
agentlog.start, agentlog.stop | logging starts or stops | INFO |
Objects passed to the console become attributes, so a structured call is the cheapest way to get state into the log:
console.log("bot tick", { tic, pos: { x, y }, sector, target: t.kind, action });
That record has the body bot tick and the attributes tic, pos, sector, target and action. Strings and numbers in the call form the body; errors become exception.type, exception.message and exception.stacktrace; arrays, maps and DOM nodes become arg0, arg1 and so on. Values are copied when they are logged, nested four levels deep at most, with long strings and long arrays cut short.
The export format
Download and Copy produce an OTLP/JSON ExportLogsServiceRequest, the format an OpenTelemetry collector accepts at /v1/logs. Every page load is one resourceLogs entry; its resource attributes hold the page URL, title, user agent, viewport and a session.id:
{"resourceLogs": [{
"resource": {"attributes": [{"key": "service.name", "value": {"stringValue": "austegard.com"}}, …]},
"scopeLogs": [{"scope": {"name": "agent-log", "version": "1.0.0"}, "logRecords": [
{"timeUnixNano": "1763164800123456000", "severityNumber": 9, "severityText": "INFO",
"eventName": "console.log", "body": {"stringValue": "bot tick"},
"attributes": [{"key": "tic", "value": {"intValue": "412"}}, …]}
]}]
}]}
Integers are strings and timestamps are nanoseconds, as the OTLP/JSON encoding requires. The skill below has a short Python reader that flattens the file into one line per record and filters it.
The skill for agents
The capturing-browser-logs skill tells an agent how to have you turn logging on, how to read and filter the export, and how to add structured console.log calls to code it is debugging. Paste it into a project's instructions, or add it to Claude as a skill.
Limits and privacy
- The log is stored per site: a log recorded on
example.comcan only be viewed and exported from a page onexample.com. Clear deletes it. - It keeps the newest 20,000 records and drops the oldest.
AgentLog.start({ max: 50000 })raises the cap. - Console output and errors are recorded from the moment it starts. Started from the bookmarklet, it misses whatever the page logged while loading; reload a page that includes the script with
?logging=trueto catch that. Requests are the exception: the browser keeps a list of the page's recent requests, so those made before the bookmarklet was clicked are recorded too, markedagentlog.before_start. - Some apps keep their own reference to
fetchorconsole.logfrom when they started (Bluesky's API client does). Started late, Agent Log still records their requests from the browser's request list, with URL, duration and usually the status but not the method (agentlog.sourceisresource-timing), and it does not see console calls made through such a reference. - Query parameters with credential-like names (
token,key,code,password,sigand similar) are replaced withREDACTEDin recorded URLs. Request and response bodies and headers are not recorded. Console arguments are recorded as given, so read the export before you share it if the page logs anything private. - It covers the page itself: not its iframes, workers or service workers.
From the console, AgentLog.download(), AgentLog.copy(), AgentLog.export() (the OTLP object), AgentLog.stop() and AgentLog.clear() do what the panel's buttons do. The source is agent-log.js, one file with no dependencies.