|
| 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 |
0 commit comments