Skip to content

Commit 6e01508

Browse files
committed
Finalize browser RUM lifecycle alpha
Expose browser instrumentation factories and lazy auto-instrumentation through the Logfire browser package so applications can opt into richer RUM setup without importing OpenTelemetry web auto-instrumentations directly. Keep Web Vitals spans provider-owned, add explicit Logfire page URL attributes, document replay correlation tradeoffs, and bump @pydantic/logfire-browser to 0.17.0-alpha.2 so the alpha release workflow publishes these SDK changes.
1 parent a67143a commit 6e01508

17 files changed

Lines changed: 362 additions & 102 deletions

File tree

.changeset/pre.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
"@pydantic/logfire-browser": "0.16.4",
1515
"@pydantic/logfire-cf-workers": "0.12.4",
1616
"@pydantic/logfire-node": "0.18.2",
17-
"@pydantic/logfire-session-replay": "0.0.0"
17+
"@pydantic/logfire-session-replay": "0.0.0",
18+
"@pydantic/otel-cf-workers": "1.0.0"
1819
},
1920
"changesets": [
21+
"browser-rum-lifecycle",
2022
"browser-rum-session",
2123
"browser-rum-web-vitals-metrics",
2224
"browser-rum-web-vitals",

docs/packages/browser.md

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@ Browser telemetry must be sent through your own backend proxy. Do not put a Logf
1212
## Install
1313

1414
```bash
15-
npm install @pydantic/logfire-browser @opentelemetry/auto-instrumentations-web
15+
npm install @pydantic/logfire-browser
1616
```
1717

1818
## Configure
1919

2020
```ts
21-
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'
2221
import * as logfire from '@pydantic/logfire-browser'
2322

2423
const url = new URL('/logfire-proxy/v1/traces', window.location.origin)
@@ -27,19 +26,21 @@ logfire.configure({
2726
traceUrl: url.toString(),
2827
serviceName: 'web-app',
2928
serviceVersion: '1.0.0',
30-
instrumentations: [getWebAutoInstrumentations()],
29+
autoInstrumentations: true,
3130
})
3231
```
3332

3433
`traceUrl` should point to a server-side endpoint that accepts OTLP trace requests from your browser instrumentation, forwards them to Logfire, and adds the `Authorization` header on the server.
3534

35+
`autoInstrumentations` is opt-in and lazily loads OpenTelemetry browser auto-instrumentations after the Logfire browser provider is ready. For advanced integrations, `instrumentations` also accepts factories, so custom instrumentation construction can be deferred until `configure()` has registered the provider.
36+
3637
Use `diagLogLevel` while troubleshooting local browser instrumentation:
3738

3839
```ts
3940
logfire.configure({
4041
traceUrl: '/logfire-proxy/v1/traces',
4142
serviceName: 'web-app',
42-
instrumentations: [getWebAutoInstrumentations()],
43+
autoInstrumentations: true,
4344
diagLogLevel: logfire.DiagLogLevel.ALL,
4445
})
4546
```
@@ -67,9 +68,13 @@ of total duration by default. Each span gets `session.id` and
6768
`browser.session.id`; `session.id` is the OpenTelemetry semantic attribute and
6869
`browser.session.id` is emitted for Logfire Platform compatibility.
6970

70-
Session-enabled spans also get `url.full` and `url.path` by default. If your
71-
URLs may contain sensitive query strings or fragments, sanitize or suppress
72-
these attributes:
71+
Session-enabled spans also get `logfire.page.url.full` and
72+
`logfire.page.url.path` by default for current page context. During the alpha,
73+
the SDK also emits compatibility `url.full` and `url.path` values with the same
74+
sanitized page URL. Prefer `logfire.page.url.*` for page grouping because
75+
OpenTelemetry fetch/resource spans may use `url.*` for the network target URL.
76+
If your URLs may contain sensitive query strings or fragments, sanitize or
77+
suppress these attributes:
7378

7479
```ts
7580
logfire.configure({
@@ -177,13 +182,14 @@ Metric data point attributes are intentionally low-cardinality:
177182
`web_vital.name` and `web_vital.rating` by default. They do not include
178183
`session.id`, `browser.session.id`, `url.full`, `url.path`, Web Vital
179184
ids/deltas, DOM selectors, attribution fields, or raw PerformanceEntry data. Use
180-
spans for raw-sample drilldown, session/replay correlation, exact URL context,
181-
and attribution selectors.
185+
spans for raw-sample drilldown, session/replay correlation, exact page context,
186+
and attribution selectors. When metrics are configured, Logfire Platform should
187+
treat these histograms as the aggregate Web Vitals surface.
182188

183189
For modern single-page apps, these are standard document-level Web Vitals, not
184-
route-level soft-navigation metrics. Span URL attributes describe the browser
185-
URL when the callback fires; route-specific Core Web Vitals need separate route
186-
or soft-navigation instrumentation. To add a route dimension to metrics, pass a
190+
route-level soft-navigation metrics. Span page URL attributes describe the
191+
browser URL when the callback fires; route-specific Core Web Vitals need
192+
separate route or soft-navigation instrumentation. To add a route dimension to metrics, pass a
187193
low-cardinality template such as `/products/:id` through
188194
`rum.webVitals.metrics.attributes`.
189195

@@ -218,10 +224,13 @@ logfire.configure({
218224
```
219225

220226
`sessionReplay` implies default RUM session behavior. Replay chunks and browser
221-
spans share `session.id` / `browser.session.id`, and spans started while replay
222-
is active include `logfire.session_replay.active` and
223-
`logfire.session_replay.mode`. The browser SDK does not populate replay
224-
`traceIds` from active-span polling.
227+
spans share `session.id` / `browser.session.id`. Spans started after replay has
228+
loaded and sampled into `full` or `buffer` mode include
229+
`logfire.session_replay.active` and `logfire.session_replay.mode`. Those active
230+
attributes are truthful best-effort annotations, not the primary correlation
231+
key; early spans should be correlated to replay by browser session id and replay
232+
time bounds. The browser SDK does not populate replay `traceIds` from
233+
active-span polling.
225234

226235
Direct token usage is available as an advanced escape hatch, but it exposes the
227236
write token to browser code and should not be the default browser deployment

examples/browser-rum-replay/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
"preview": "vp preview --host 127.0.0.1 --port 4174"
1111
},
1212
"dependencies": {
13-
"@opentelemetry/auto-instrumentations-web": "catalog:",
1413
"cors": "^2.8.5",
1514
"express": "^4.21.2"
1615
},

examples/browser-rum-replay/src/main.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web'
21
import * as logfire from '@pydantic/logfire-browser'
32

43
interface CatalogProduct {
@@ -76,20 +75,18 @@ logfire.configure({
7675
},
7776
},
7877
},
79-
instrumentations: [
80-
getWebAutoInstrumentations({
81-
'@opentelemetry/instrumentation-document-load': { enabled: true },
82-
'@opentelemetry/instrumentation-fetch': {
83-
enabled: true,
84-
clearTimingResources: true,
85-
},
86-
'@opentelemetry/instrumentation-user-interaction': {
87-
enabled: true,
88-
eventNames: ['click', 'change'],
89-
},
90-
'@opentelemetry/instrumentation-xml-http-request': { enabled: true },
91-
}),
92-
],
78+
autoInstrumentations: {
79+
'@opentelemetry/instrumentation-document-load': { enabled: true },
80+
'@opentelemetry/instrumentation-fetch': {
81+
enabled: true,
82+
clearTimingResources: true,
83+
},
84+
'@opentelemetry/instrumentation-user-interaction': {
85+
enabled: true,
86+
eventNames: ['click', 'change'],
87+
},
88+
'@opentelemetry/instrumentation-xml-http-request': { enabled: true },
89+
},
9390
diagLogLevel: import.meta.env.VITE_LOGFIRE_DIAG === 'true' ? logfire.DiagLogLevel.ALL : logfire.DiagLogLevel.ERROR,
9491
batchSpanProcessorConfig: {
9592
maxExportBatchSize: 8,

examples/nextjs-client-side-instrumentation/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# @pydantic/nextjs-client-side-instrumentation
22

3+
## 0.1.16-alpha.2
4+
5+
### Patch Changes
6+
7+
- Updated dependencies
8+
- @pydantic/logfire-browser@0.17.0-alpha.2
9+
310
## 0.1.16-alpha.1
411

512
### Patch Changes

examples/nextjs-client-side-instrumentation/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pydantic/nextjs-client-side-instrumentation",
3-
"version": "0.1.16-alpha.1",
3+
"version": "0.1.16-alpha.2",
44
"private": true,
55
"scripts": {
66
"dev": "next dev --turbopack --port 8080",

packages/logfire-browser/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @pydantic/logfire-browser
22

3+
## 0.17.0-alpha.2
4+
5+
### Minor Changes
6+
7+
- Stabilize browser RUM lifecycle setup with deferred instrumentation factories, opt-in lazy `autoInstrumentations`, provider-owned Web Vitals spans, explicit page URL attributes, and clarified session replay correlation semantics.
8+
39
## 0.17.0-alpha.1
410

511
### Patch Changes

packages/logfire-browser/README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,13 @@ or 4 hours of total duration by default. Spans get `session.id` and
7070
`browser.session.id`; `session.id` is the OpenTelemetry semantic attribute and
7171
`browser.session.id` is emitted for Logfire Platform compatibility.
7272

73-
By default, session-enabled spans also get `url.full` and `url.path` from the
74-
current page URL. If your app has sensitive query strings or fragments, provide
75-
a sanitizer or suppress URL attributes:
73+
By default, session-enabled spans also get `logfire.page.url.full` and
74+
`logfire.page.url.path` from the current page URL. During the alpha, the SDK
75+
also emits compatibility `url.full` and `url.path` values with the same
76+
sanitized page URL. Prefer `logfire.page.url.*` for page grouping because
77+
OpenTelemetry fetch/resource spans may use `url.*` for the network target URL.
78+
If your app has sensitive query strings or fragments, provide a sanitizer or
79+
suppress URL attributes:
7680

7781
```js
7882
logfire.configure({
@@ -183,13 +187,14 @@ Metric data point attributes are intentionally low-cardinality:
183187
`web_vital.name` and `web_vital.rating` by default. They do not include
184188
`session.id`, `browser.session.id`, `url.full`, `url.path`, Web Vital
185189
ids/deltas, DOM selectors, attribution fields, or raw PerformanceEntry data. Use
186-
spans for raw-sample drilldown, session/replay correlation, exact URL context,
187-
and attribution selectors.
190+
spans for raw-sample drilldown, session/replay correlation, exact page context,
191+
and attribution selectors. When metrics are configured, Logfire Platform should
192+
treat these histograms as the aggregate Web Vitals surface.
188193

189194
For modern single-page apps, these are standard document-level Web Vitals, not
190-
route-level soft-navigation metrics. Span URL attributes describe the browser
191-
URL when the callback fires; route-specific Core Web Vitals need separate route
192-
or soft-navigation instrumentation. To add a route dimension to metrics, pass a
195+
route-level soft-navigation metrics. Span page URL attributes describe the
196+
browser URL when the callback fires; route-specific Core Web Vitals need
197+
separate route or soft-navigation instrumentation. To add a route dimension to metrics, pass a
193198
low-cardinality template such as `/products/:id` through
194199
`rum.webVitals.metrics.attributes`.
195200

@@ -225,10 +230,13 @@ logfire.configure({
225230
```
226231

227232
`sessionReplay` implies default browser session attributes. Replay chunks and
228-
browser spans share `session.id` / `browser.session.id`, and spans started
229-
while replay is active get `logfire.session_replay.active` and
230-
`logfire.session_replay.mode`. The browser integration intentionally does not
231-
poll active trace context into replay chunks.
233+
browser spans share `session.id` / `browser.session.id`. Spans started after
234+
replay has loaded and sampled into `full` or `buffer` mode get
235+
`logfire.session_replay.active` and `logfire.session_replay.mode`. Those active
236+
attributes are truthful best-effort annotations, not the primary correlation
237+
key; early spans should be correlated to replay by browser session id and replay
238+
time bounds. The browser integration intentionally does not poll active trace
239+
context into replay chunks.
232240

233241
Use a backend proxy for browser replay uploads. The proxy should authenticate
234242
the browser request with your application session or CSRF mechanism and add the

packages/logfire-browser/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"stats",
2626
"monitoring"
2727
],
28-
"version": "0.17.0-alpha.1",
28+
"version": "0.17.0-alpha.2",
2929
"type": "module",
3030
"main": "./dist/index.cjs",
3131
"module": "./dist/index.js",
@@ -54,6 +54,7 @@
5454
},
5555
"dependencies": {
5656
"@opentelemetry/api": "catalog:",
57+
"@opentelemetry/auto-instrumentations-web": "catalog:",
5758
"@opentelemetry/core": "catalog:",
5859
"@opentelemetry/exporter-metrics-otlp-http": "catalog:",
5960
"@opentelemetry/exporter-trace-otlp-http": "catalog:",

packages/logfire-browser/src/BrowserSessionSpanProcessor.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ describe('BrowserSessionSpanProcessor', () => {
104104

105105
expect(span.attributes).toEqual({
106106
'browser.session.id': 'session-1',
107+
'logfire.page.url.full': 'https://example.com/dashboard?tab=activity#recent',
108+
'logfire.page.url.path': '/dashboard',
107109
'session.id': 'session-1',
108110
'url.full': 'https://example.com/dashboard?tab=activity#recent',
109111
'url.path': '/dashboard',
@@ -138,12 +140,28 @@ describe('BrowserSessionSpanProcessor', () => {
138140

139141
expect(span.attributes).toEqual({
140142
'browser.session.id': 'session-1',
143+
'logfire.page.url.full': 'https://example.com/dashboard',
144+
'logfire.page.url.path': '/sanitized',
141145
'session.id': 'session-1',
142146
'url.full': 'https://example.com/dashboard',
143147
'url.path': '/sanitized',
144148
})
145149
})
146150

151+
it('keeps explicit page URL attributes alongside compatibility URL attributes', () => {
152+
setLocation({ href: 'https://example.com/products/123?token=secret' })
153+
const span = createSpan()
154+
155+
startSpan(createProcessor(), span)
156+
157+
expect(span.attributes).toMatchObject({
158+
'logfire.page.url.full': 'https://example.com/products/123?token=secret',
159+
'logfire.page.url.path': '/products/123',
160+
'url.full': 'https://example.com/products/123?token=secret',
161+
'url.path': '/products/123',
162+
})
163+
})
164+
147165
it('does not throw when location is unavailable', () => {
148166
setLocation(undefined)
149167
const span = createSpan()

0 commit comments

Comments
 (0)