feat(otel-web): emit browser.language and browser.timezone resource attributes - #267
Conversation
…ttributes Add approximate locale/region signals to the browser RUM resource: - browser.language (OTel semconv) from navigator.language, e.g. "en-US" - browser.timezone (IANA zone) from Intl, e.g. "America/New_York" These are honest proxies for a user's region, NOT IP geolocation — the browser cannot determine country without a permission prompt; true geo (geo.country.name, …) is derived in the collector from the client IP. The resolver is split out (browserContext.ts) with the context injectable so the attribute mapping is unit-tested without real browser globals, and omits absent values so it never overwrites a user attribute with an empty string. Wired into resourceAttrs before the user-provided resourceAttributes so callers can override.
🦋 Changeset detectedLatest commit: 0b1258b The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Greptile SummaryThe PR adds browser language and timezone resource attributes to browser RUM telemetry, while preserving user overrides.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/otel-web/src/browserContext.ts | Adds guarded browser-context resolution and resource-attribute mapping. |
| packages/otel-web/src/index.ts | Adds browser-context attributes before caller-provided attributes so callers retain override precedence. |
| packages/otel-web/test/browserContext.test.ts | Adds mapping and environment-resolution coverage, including a safely restored navigator mock for the previously untested language branch. |
| packages/otel-web/.mocharc.json | Includes the browser-context test in the Node Mocha suite. |
| packages/deno/build-npm.ts | Switches the dnt build dependency to a pinned JSR package reference. |
| .changeset/rum-browser-context.md | Records the browser-context attributes as minor releases for the affected packages. |
Reviews (5): Last reviewed commit: "fix(deno): import dnt from JSR to avoid ..." | Re-trigger Greptile
…anches
Adds two unit tests flagged in review of the RUM browser-context change:
- getBrowserContextResourceAttributes({ language: '' }) -> {} confirms an
empty-string language is treated as absent.
- resolveBrowserContext() reads navigator.language: mock navigator with a
fixed 'fr-FR' locale so the truthful branch is exercised deterministically,
independent of the host's locale (Node 22 defines navigator.language).
| /** navigator.language, e.g. "en-US" (OTel semconv `browser.language`). */ | ||
| language?: string; | ||
| /** IANA time zone, e.g. "America/New_York". */ | ||
| timeZone?: string; |
There was a problem hiding this comment.
Is this customized? I don't see that in the spec https://opentelemetry.io/docs/specs/semconv/resource/browser/
There was a problem hiding this comment.
Good catch — this was custom. The OTel browser resource semconv only defines browser.brands / browser.platform / browser.mobile / browser.language / browser.user_agent; there's no timezone attribute. To avoid squatting in the OTel-reserved browser. namespace (and a possible future collision), I've renamed it to hyperdx.browser.timezone in 7c0b582. browser.language stays as-is since it's the spec attribute.
|
|
||
| Emit approximate locale/region resource attributes from the browser RUM | ||
| SDK: `browser.language` (OTel semconv, from `navigator.language`) and | ||
| `browser.timezone` (IANA zone from `Intl`). These are honest proxies for |
There was a problem hiding this comment.
The emitted attribute key is lowercase — now hyperdx.browser.timezone (7c0b582). The timeZone (camelCase) you may have seen is just the TypeScript field name, mirroring the JS Intl.DateTimeFormat().resolvedOptions().timeZone property — it's not the wire name. Updated the changeset to match.
…wser.timezone Per review: the OTel browser resource semconv has no timezone attribute, so the custom one was squatting in the reserved `browser.` namespace. Move it to the vendor namespace `hyperdx.browser.timezone` to avoid a future collision. `browser.language` is unchanged (it is the spec attribute). Updates the emit site, docstring, changeset, and the two existing test assertions.
The unpinned https://deno.land/x/dnt import pulls ts_morph from deno.land/x, which intermittently fails with 'error: brotli error' on Deno 1.x in CI. The JSR-published dnt resolves its dependencies from jsr.io instead, eliminating the failing fetch.
Summary
Adds two approximate locale/region signals to the browser RUM resource so dashboards can break traffic down by region without any collector enrichment:
browser.language(OTel semantic-convention attribute) fromnavigator.language— e.g.en-USbrowser.timezone(IANA zone) fromIntl.DateTimeFormat().resolvedOptions().timeZone— e.g.America/New_YorkWhy — and what this is not
These are honest proxies for where a user is. They are not IP geolocation: a browser cannot determine a visitor's country without a permission prompt (
navigator.geolocationprompts and returns lat/long, which is wrong for passive RUM). True geo (geo.country.name, …) is derived in the OTel collector from the client IP via the geoip processor. This change gives a zero-permission, zero-infra region signal in the meantime, emitted on the resource so it's present on every span.Implementation
src/browserContext.ts:resolveBrowserContext()reads the environment;getBrowserContextResourceAttributes(context?)maps it to attributes. The context is injectable so the mapping is unit-testable without real browser globals, and absent values are omitted so they never overwrite a user attribute with an empty string.index.tsresourceAttrsbefore the user-providedresourceAttributes, so callers can override either key.Test plan
test/browserContext.test.ts(added to the node mocha spec): asserts language+timezone map to the right keys, that absent values are omitted, and thatresolveBrowserContext()returns a non-empty IANA zone from the real environment.yarn workspace @hyperdx/otel-web test:unit:ci-node→ all passing, including the existingRum.inittest (which now exercises the new helper at runtime).tsc --noEmitclean.Note:
@hyperdx/otel-webisn't currently wired into the aggregatenx ci:unittarget (pre-existing gap), so these tests run viatest:unit:ci-noderather than the broader karma suite.Risks / rollback
Additive resource attributes, default-on but user-overridable, guarded for non-browser/
Intl-less environments. The locale/UA values are already sent as HTTP headers, so no new exposure. Rollback = remove thegetBrowserContextResourceAttributes()spread.