Skip to content

Commit 7ca465d

Browse files
ehfengclaude
andcommitted
Add browser telemetry guide
Documents the SSE-based browser telemetry stream — enabling capture, consuming events, per-category configuration, reconnection, and the full event type reference across console, network, page, interaction, and monitor categories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d8c3232 commit 7ca465d

3 files changed

Lines changed: 315 additions & 1 deletion

File tree

browsers/telemetry.mdx

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
---
2+
title: "Browser Telemetry"
3+
description: "Stream real-time console, network, page, and interaction events from your browser sessions"
4+
---
5+
6+
Browser telemetry gives you a real-time stream of everything happening inside a session — console output, network requests, page lifecycle events, and user interactions. Events are delivered over [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events), so you can process them as they happen or feed them into your own observability pipeline.
7+
8+
## Enabling telemetry
9+
10+
Pass `telemetry.enabled` when creating a browser to start capturing with VM defaults:
11+
12+
<CodeGroup>
13+
```typescript Typescript/Javascript
14+
import Kernel from '@onkernel/sdk';
15+
16+
const kernel = new Kernel();
17+
18+
const kernelBrowser = await kernel.browsers.create({
19+
telemetry: { enabled: true },
20+
});
21+
```
22+
23+
```python Python
24+
from kernel import Kernel
25+
26+
kernel = Kernel()
27+
28+
kernel_browser = kernel.browsers.create(
29+
telemetry={"enabled": True},
30+
)
31+
```
32+
</CodeGroup>
33+
34+
To capture only specific categories, pass per-category flags under `telemetry.browser`:
35+
36+
<CodeGroup>
37+
```typescript Typescript/Javascript
38+
const kernelBrowser = await kernel.browsers.create({
39+
telemetry: {
40+
enabled: true,
41+
browser: {
42+
console: { enabled: true },
43+
network: { enabled: true },
44+
page: { enabled: false },
45+
interaction: { enabled: false },
46+
},
47+
},
48+
});
49+
```
50+
51+
```python Python
52+
kernel_browser = kernel.browsers.create(
53+
telemetry={
54+
"enabled": True,
55+
"browser": {
56+
"console": {"enabled": True},
57+
"network": {"enabled": True},
58+
"page": {"enabled": False},
59+
"interaction": {"enabled": False},
60+
},
61+
},
62+
)
63+
```
64+
</CodeGroup>
65+
66+
Omitted categories default to enabled. Setting `enabled: false` at the top level disables all capture and can't be combined with category settings.
67+
68+
## Consuming the stream
69+
70+
The telemetry endpoint streams events as SSE. Each frame carries a JSON envelope with a monotonic sequence number and the event payload:
71+
72+
<CodeGroup>
73+
```typescript Typescript/Javascript
74+
const response = await fetch(
75+
`https://api.onkernel.com/v1/browsers/${kernelBrowser.session_id}/telemetry`,
76+
{
77+
headers: {
78+
'Authorization': `Bearer ${process.env.KERNEL_API_KEY}`,
79+
'Accept': 'text/event-stream',
80+
},
81+
},
82+
);
83+
84+
const reader = response.body!.getReader();
85+
const decoder = new TextDecoder();
86+
let buffer = '';
87+
88+
while (true) {
89+
const { done, value } = await reader.read();
90+
if (done) break;
91+
92+
buffer += decoder.decode(value, { stream: true });
93+
const lines = buffer.split('\n');
94+
buffer = lines.pop()!;
95+
96+
for (const line of lines) {
97+
if (!line.startsWith('data: ')) continue;
98+
const envelope = JSON.parse(line.slice(6));
99+
console.log(envelope.event.type, envelope.event);
100+
}
101+
}
102+
```
103+
104+
```python Python
105+
import httpx
106+
import json
107+
import os
108+
109+
with httpx.stream(
110+
"GET",
111+
f"https://api.onkernel.com/v1/browsers/{kernel_browser.session_id}/telemetry",
112+
headers={
113+
"Authorization": f"Bearer {os.environ['KERNEL_API_KEY']}",
114+
"Accept": "text/event-stream",
115+
},
116+
) as response:
117+
for line in response.iter_lines():
118+
if not line.startswith("data: "):
119+
continue
120+
envelope = json.loads(line[6:])
121+
print(envelope["event"]["type"], envelope["event"])
122+
```
123+
</CodeGroup>
124+
125+
The stream stays open until the browser session terminates. A keepalive comment is sent every 15 seconds when no events arrive.
126+
127+
## Reconnecting
128+
129+
Each SSE frame includes an `id:` field set to the event's sequence number. If your connection drops, pass the last received sequence number as `Last-Event-ID` to resume without gaps:
130+
131+
```
132+
GET /v1/browsers/{id}/telemetry
133+
Last-Event-ID: 42
134+
```
135+
136+
Gaps in received `seq` values indicate dropped events.
137+
138+
## Event categories
139+
140+
Telemetry events fall into four configurable categories plus a set of monitor events that are always emitted.
141+
142+
### Console
143+
144+
Console output and uncaught exceptions from the browser.
145+
146+
| Type | Description |
147+
|---|---|
148+
| `console_log` | `console.log`, `console.info`, `console.warn`, and other non-error console calls |
149+
| `console_error` | `console.error` calls and uncaught JavaScript exceptions |
150+
151+
Console events include the message text, all arguments coerced to strings, and the JavaScript call stack when available.
152+
153+
### Network
154+
155+
HTTP request and response metadata flowing through the browser.
156+
157+
| Type | Description |
158+
|---|---|
159+
| `network_request` | Outgoing HTTP request (URL, method, headers, post data) |
160+
| `network_response` | HTTP response (status code, headers, body) |
161+
| `network_loading_failed` | Failed network request (error text, canceled flag) |
162+
| `network_idle` | Network has been idle (no in-flight requests) |
163+
164+
<Info>
165+
Network response bodies are truncated at 8 KB for structured types (JSON, XML, form data) and 4 KB for other text. Binary responses (images, fonts, media) are excluded.
166+
</Info>
167+
168+
### Page
169+
170+
Page lifecycle events from navigation through layout stability.
171+
172+
| Type | Description |
173+
|---|---|
174+
| `page_navigation` | Navigation to a new URL |
175+
| `page_dom_content_loaded` | DOMContentLoaded fired |
176+
| `page_load` | Page load complete |
177+
| `page_tab_opened` | New tab or window opened |
178+
| `page_layout_shift` | Cumulative Layout Shift (CLS) detected |
179+
| `page_lcp` | Largest Contentful Paint recorded |
180+
| `page_layout_settled` | Layout has stabilized |
181+
| `page_navigation_settled` | Navigation fully settled |
182+
183+
### Interaction
184+
185+
User interaction events from clicks, typing, and scrolling.
186+
187+
| Type | Description |
188+
|---|---|
189+
| `interaction_click` | Mouse click (coordinates, target selector) |
190+
| `interaction_key` | Keyboard input |
191+
| `interaction_scroll_settled` | Scroll interaction settled |
192+
193+
### Monitor
194+
195+
Monitor events are always emitted regardless of category configuration. They report on the health of the telemetry stream itself.
196+
197+
| Type | Description |
198+
|---|---|
199+
| `monitor_screenshot` | Screenshot captured |
200+
| `monitor_disconnected` | CDP connection to Chrome lost — events may be dropped until reconnection |
201+
| `monitor_reconnected` | CDP connection re-established |
202+
| `monitor_reconnect_failed` | Reconnection failed after all retries — no further events will arrive |
203+
| `monitor_init_failed` | Telemetry initialization failed on the VM |
204+
205+
After a `monitor_disconnected` event, treat any in-progress computed state (like `network_idle` or `page_layout_settled`) as unreliable until `monitor_reconnected` arrives.
206+
207+
## Updating telemetry on a running session
208+
209+
You can enable, disable, or reconfigure telemetry categories on an active session with a PATCH:
210+
211+
<CodeGroup>
212+
```typescript Typescript/Javascript
213+
await kernel.browsers.update(kernelBrowser.session_id, {
214+
telemetry: {
215+
browser: {
216+
network: { enabled: false },
217+
},
218+
},
219+
});
220+
```
221+
222+
```python Python
223+
kernel.browsers.update(
224+
kernel_browser.session_id,
225+
telemetry={
226+
"browser": {
227+
"network": {"enabled": False},
228+
},
229+
},
230+
)
231+
```
232+
</CodeGroup>
233+
234+
Omitted categories are left unchanged. To disable all telemetry on a running session, set `enabled: false`:
235+
236+
<CodeGroup>
237+
```typescript Typescript/Javascript
238+
await kernel.browsers.update(kernelBrowser.session_id, {
239+
telemetry: { enabled: false },
240+
});
241+
```
242+
243+
```python Python
244+
kernel.browsers.update(
245+
kernel_browser.session_id,
246+
telemetry={"enabled": False},
247+
)
248+
```
249+
</CodeGroup>
250+
251+
## Event envelope
252+
253+
Every SSE frame carries a JSON object with this shape:
254+
255+
```json
256+
{
257+
"seq": 17,
258+
"event": {
259+
"ts": 1716900000000000,
260+
"type": "console_log",
261+
"source": {
262+
"kind": "cdp",
263+
"event": "Runtime.consoleAPICalled"
264+
},
265+
"data": {
266+
"level": "log",
267+
"text": "Page loaded successfully",
268+
"args": ["Page loaded successfully"],
269+
"url": "https://example.com",
270+
"target_type": "page"
271+
}
272+
}
273+
}
274+
```
275+
276+
- `seq` — monotonic sequence number, also the SSE `id:` field
277+
- `event.ts` — event timestamp in Unix microseconds
278+
- `event.type` — discriminator for the event type
279+
- `event.source.kind` — where the event originated: `cdp`, `kernel_api`, `extension`, or `local_process`
280+
- `event.data` — event-specific payload, always includes the `BrowserEventContext` fields (`session_id`, `target_id`, `frame_id`, `url`, `nav_seq`) for CDP-sourced events

docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"expanded": true,
8282
"pages": [
8383
"browsers/live-view",
84+
"browsers/telemetry",
8485
"browsers/termination",
8586
"browsers/standby",
8687
"browsers/headless",

introduction/observe.mdx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,42 @@ for event in logs:
124124

125125
Full reference: [Logs](/apps/logs).
126126

127+
## Browser telemetry
128+
129+
Stream real-time events from the browser — console output, network requests, page lifecycle, and interactions — over Server-Sent Events. Enable it at creation, tune which categories you capture, and pipe the stream into your own observability stack.
130+
131+
<CodeGroup>
132+
```typescript Typescript/Javascript
133+
const kernelBrowser = await kernel.browsers.create({
134+
telemetry: { enabled: true },
135+
});
136+
137+
const response = await fetch(
138+
`https://api.onkernel.com/v1/browsers/${kernelBrowser.session_id}/telemetry`,
139+
{
140+
headers: {
141+
'Authorization': `Bearer ${process.env.KERNEL_API_KEY}`,
142+
'Accept': 'text/event-stream',
143+
},
144+
},
145+
);
146+
```
147+
148+
```python Python
149+
kernel_browser = kernel.browsers.create(
150+
telemetry={"enabled": True},
151+
)
152+
153+
# Stream events via SSE
154+
# GET /v1/browsers/{session_id}/telemetry
155+
```
156+
</CodeGroup>
157+
158+
Full reference: [Browser Telemetry](/browsers/telemetry).
159+
127160
## Picking the right tool
128161

129162
- **Building the agent?** Keep a [live view](/browsers/live-view) tab open while you iterate.
130163
- **Debugging a failure?** Capture a [replay](/browsers/replays) for the run, then watch the video.
131-
- **Instrumenting the agent itself?** Drop [screenshots](/browsers/computer-controls#take-screenshots) and [logs](/apps/logs) into your traces at the points that matter.
164+
- **Instrumenting the agent itself?** Stream [browser telemetry](/browsers/telemetry) into your pipeline, or drop [screenshots](/browsers/computer-controls#take-screenshots) and [logs](/apps/logs) into your traces at the points that matter.
132165
- **Putting a human in the loop?** Embed the [live view](/browsers/live-view#embedding-in-an-iframe) in your own UI.

0 commit comments

Comments
 (0)