feat(otel-web): emit browser.language and browser.timezone resource attributes#267
feat(otel-web): emit browser.language and browser.timezone resource attributes#267teeohhem wants to merge 2 commits into
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: 67f7e07 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 SummaryAdds two zero-permission browser locale/region signals —
Confidence Score: 5/5Safe 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
Sequence DiagramsequenceDiagram
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
Reviews (2): Last reviewed commit: "test(otel-web): cover navigator.language..." | 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).
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.