Skip to content

feat(otel-web): emit browser.language and browser.timezone resource attributes - #267

Merged
wrn14897 merged 5 commits into
mainfrom
teeohhem/rum-browser-context
Aug 11, 2026
Merged

feat(otel-web): emit browser.language and browser.timezone resource attributes#267
wrn14897 merged 5 commits into
mainfrom
teeohhem/rum-browser-context

Conversation

@teeohhem

@teeohhem teeohhem commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

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) from navigator.language — e.g. en-US
  • browser.timezone (IANA zone) from Intl.DateTimeFormat().resolvedOptions().timeZone — e.g. America/New_York

Why — 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.geolocation prompts 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

  • New 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.
  • Wired into index.ts resourceAttrs before the user-provided resourceAttributes, so callers can override either key.

Test plan

  • New test/browserContext.test.ts (added to the node mocha spec): asserts language+timezone map to the right keys, that absent values are omitted, and that resolveBrowserContext() returns a non-empty IANA zone from the real environment.
  • Verified locally: yarn workspace @hyperdx/otel-web test:unit:ci-node → all passing, including the existing Rum.init test (which now exercises the new helper at runtime). tsc --noEmit clean.

Note: @hyperdx/otel-web isn't currently wired into the aggregate nx ci:unit target (pre-existing gap), so these tests run via test:unit:ci-node rather 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 the getBrowserContextResourceAttributes() spread.

…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-bot

changeset-bot Bot commented Jun 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0b1258b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/otel-web Minor
@hyperdx/browser Minor
@hyperdx/otel-web-session-recorder Major

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-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds browser language and timezone resource attributes to browser RUM telemetry, while preserving user overrides.

  • Resolves locale data from navigator and Intl.
  • Maps available values to browser resource attributes.
  • Adds Node Mocha coverage for mapping, absent values, timezone resolution, and the previously untested navigator.language path.
  • Updates the Deno npm build to use the versioned JSR dnt package.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

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

Comment thread packages/otel-web/test/browserContext.test.ts
…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).
@teeohhem
teeohhem requested a review from wrn14897 June 5, 2026 19:10
/** navigator.language, e.g. "en-US" (OTel semconv `browser.language`). */
language?: string;
/** IANA time zone, e.g. "America/New_York". */
timeZone?: string;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this customized? I don't see that in the spec https://opentelemetry.io/docs/specs/semconv/resource/browser/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .changeset/rum-browser-context.md Outdated

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

@wrn14897 wrn14897 Jun 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timezone or timeZone?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
kodiakhq Bot and others added 2 commits August 10, 2026 15:35
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.
@wrn14897
wrn14897 merged commit 3ad7fa8 into main Aug 11, 2026
8 checks passed
@kodiakhq
kodiakhq Bot deleted the teeohhem/rum-browser-context branch August 11, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants