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 3 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: 7c0b582 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 SummaryThis PR adds two opt-out browser locale/region resource attributes —
Confidence Score: 5/5Additive, default-on resource attributes with no mutations to existing behavior; user-provided attributes still override them and all environment-access paths are guarded. The change only spreads two new attributes into the resource map before user overrides and SDK metadata. Both reads are guarded for missing globals and empty strings. The previously noted untested navigator.language code path is now covered by a globalThis mock test. No existing contracts are altered. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Rum.init()"] --> B["getBrowserContextResourceAttributes()"]
B --> C["resolveBrowserContext()"]
C --> D{"typeof navigator\n!== 'undefined'?"}
D -- yes --> E["navigator.language"]
D -- no --> F["language = undefined"]
C --> G{"Intl available?"}
G -- yes --> H["Intl.DateTimeFormat()\n.resolvedOptions().timeZone"]
G -- no / throws --> I["timeZone = undefined"]
E --> J["BrowserContext\n{ language?, timeZone? }"]
F --> J
H --> J
I --> J
J --> K{"language\ntruthy?"}
K -- yes --> L["attrs['browser.language']"]
K -- no --> M["omit"]
J --> N{"timeZone\ntruthy?"}
N -- yes --> O["attrs['hyperdx.browser.timezone']"]
N -- no --> P["omit"]
L --> Q["resourceAttrs spread\n(before user attrs & SDK_INFO)"]
O --> Q
M --> Q
P --> Q
Reviews (3): Last reviewed commit: "refactor(otel-web): namespace the custom..." | 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.
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.