Skip to content

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

Open
teeohhem wants to merge 2 commits into
mainfrom
teeohhem/rum-browser-context
Open

feat(otel-web): emit browser.language and browser.timezone resource attributes#267
teeohhem wants to merge 2 commits into
mainfrom
teeohhem/rum-browser-context

Conversation

@teeohhem
Copy link
Copy Markdown
Contributor

@teeohhem teeohhem commented Jun 5, 2026

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
Copy link
Copy Markdown

changeset-bot Bot commented Jun 5, 2026

🦋 Changeset detected

Latest commit: 67f7e07

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
Copy link
Copy Markdown

greptile-apps Bot commented Jun 5, 2026

Greptile Summary

Adds two zero-permission browser locale/region signals — browser.language (from navigator.language) and browser.timezone (from Intl.DateTimeFormat().resolvedOptions().timeZone) — as resource attributes on every span emitted by the browser RUM SDK.

  • packages/otel-web/src/browserContext.ts: New module with resolveBrowserContext() (reads browser globals defensively) and getBrowserContextResourceAttributes() (maps to OTel attributes, omits absent/empty values, injectable context for testing).
  • packages/otel-web/src/index.ts: Spreads the new attributes into resourceAttrs before user-provided resourceAttributes, preserving override semantics.
  • packages/otel-web/test/browserContext.test.ts: Full unit coverage including empty-string omission and a globalThis.navigator mock that exercises the previously-untested language-read path.

Confidence Score: 5/5

Safe to merge — purely additive resource attributes, guarded for non-browser environments, user-overridable, and covered by new unit tests.

The change is narrowly scoped: two new resource attributes read once at SDK init time from well-established browser globals. Both reads are defensive (undefined check for navigator, try/catch for Intl), absent values are omitted, and the placement before user-provided attributes means no existing configuration is broken. Tests cover the full matrix including the empty-string edge case and the navigator mock path.

No files require special attention.

Important Files Changed

Filename Overview
packages/otel-web/src/browserContext.ts New module implementing resolveBrowserContext() and getBrowserContextResourceAttributes() with injectable context, try/catch around Intl, and empty-string guards.
packages/otel-web/src/index.ts Spreads getBrowserContextResourceAttributes() into resourceAttrs before user-provided attributes, correctly allowing user overrides.
packages/otel-web/test/browserContext.test.ts Covers happy path, absent value omission, empty-string language, real IANA timezone, and navigator mock — addressing the previously raised gap.
packages/otel-web/.mocharc.json Adds browserContext.test.ts to the mocha spec array; no issues.
.changeset/rum-browser-context.md Minor version bump for both @hyperdx/otel-web and @hyperdx/browser with accurate description.

Sequence Diagram

sequenceDiagram
    participant App as App Code
    participant Rum as Rum.init()
    participant BC as getBrowserContextResourceAttributes()
    participant Nav as navigator.language
    participant Intl as Intl.DateTimeFormat()
    participant SDK as OTel SDK

    App->>Rum: "Rum.init({ resourceAttributes, ... })"
    Rum->>BC: getBrowserContextResourceAttributes()
    BC->>Nav: "typeof navigator !== 'undefined'?"
    Nav-->>BC: language (e.g. "en-US") or undefined
    BC->>Intl: resolvedOptions().timeZone
    Intl-->>BC: timeZone (e.g. "America/New_York") or catch → undefined
    BC-->>Rum: "{ "browser.language": "en-US", "browser.timezone": "America/New_York" }"
    Rum->>SDK: "resourceAttrs = { ...browserCtx, ...userAttrs, ...SDK_INFO }"
    Note over Rum,SDK: User-provided attributes override browser context attrs
Loading

Reviews (2): Last reviewed commit: "test(otel-web): cover navigator.language..." | 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
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.

1 participant