feat(attributes): Add React Native native module call attributes - #564
Conversation
Registers the attributes sentry-react-native emits for native module call instrumentation, consolidated under a single `turbo_module.*` namespace. These shipped before being registered here, which is backwards — they have been live since 8.14.0 (`turbo_module.name`/`.method`), 8.19.0 (call aggregate) and 8.21.0 (span attribution). Hence 10 of the 21 files arrive already deprecated: they are the shipped names, registered so they can be aliased and backfilled rather than silently broken. Two problems are being corrected. The SDK emitted the same four totals under both `turbo_module.*` (root spans) and `turbo_modules.*` (aggregate span), so half the deprecations exist only to collapse a singular/plural split. The rest drop `total_` prefixes and `_ms` suffixes in favour of dot-separated grouping, putting the unit in the `brief` as `app.vitals.*` does. The per-method breakdown keys are removed rather than deprecated. They place the dynamic segment mid-key (`turbo_module.<module>.<method>.call_count`), and `has_dynamic_suffix` only models a trailing segment, so they cannot be expressed in the schema at all. The SDK will carry that breakdown as one child span per (module, method) instead, which is why the identity attributes are registered alongside the counters: on a per-method span they are dimensions, not a prefix. Measurements are deliberately excluded. The SDK also emits four transaction measurements under `turbo_modules.*`, but every existing entry in model/measurements is flat and unnamespaced, and they now duplicate span attributes, so the SDK will drop them instead. `changelog.prs` is unset pending this PR's number. Refs getsentry/sentry-react-native#6168
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨Attributes
Other
Internal Changes 🔧Deps
🤖 This preview updates automatically when you update the PR. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 822ed36. Configure here.
Resolve the generated python/src/sentry_conventions/attributes.py conflict and re-run `yarn generate`. The op constant is now emitted as `TURBO_MODULE` after #565 dropped the category prefix and `SPAN_OP` suffix from op constants.
* Record `prs: [564]` on every changelog entry now that the PR number is known. * Switch from the legacy `example` field to `examples`, which CONTRIBUTING now prefers for new attributes, and normalise field order to match `create:attribute` output. `turbo_module.arch` and `turbo_module.kind` list both of their enumerated values. * Drop the "native module and method names" scrubbing reason from `turbo_module.arch` and `turbo_module.kind` — both are closed enums that carry no app-defined names — and add it to `turbo_module.top_module`, which holds the same value as `turbo_module.top.name`.
|
@Lms24 could you please review this one? Cannot be merged without your approval. |
Lms24
left a comment
There was a problem hiding this comment.
Apologies for the review delay! To be completely honest, I don’t have the bandwidth to think every attribute through here, if it makes sense, is semantically correct, etc. My high-level advice is:
- Every attribute we currently send in SDKs should be documented as-is.
- I’d encourage you to think about if
- there aren’t already attributes that map this in a more general name space, we’d want to use in the future.
- long-term, the
turbo_modulenamespace makes sense. To me, as a non-RN expert, it seems to be something very RN-specific and I wonder if another namespace makes more sense (e.g. something likereact_native), for any attributes that don’t fit into an existing namespace - obviously, all attribute changes, can happen over time, e.g. when working on the next major
- Doing nothing is also an option :)
Approving to unblock you. Also, just for completeness, there’s 4 other code owners who could have reviewed this change. In the future I won’t have the bandwidth to carefully review every PR. Happy to share advice or answer any questions though.
…espace `turbo_module` named a React Native implementation detail, and it was already inaccurate within this PR: `turbo_module.arch: legacy` describes calls reached over the Old Architecture bridge, which are not TurboModules. Move the canonical attributes to `react_native.module.*`, with the architecture as `react_native.architecture` since it describes the runtime rather than an individual call. `turbo_module.name`, `.method` and `.arch` are shipped, so they now become deprecations too, with `_status: "backfill"` and symmetric aliases. Deprecations go from 10 to 13. Doing this in the next major instead would mean deprecating all 11 canonical keys, so this is the cheapest point to rename: nothing is merged and the SDK-side rename is already gated on this PR. The `turbo_module` span operation is left as-is; span operations have no alias or deprecation mechanism. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Description
Registers the attributes
sentry-react-nativeemits for its native module (TurboModule) call instrumentation under areact_native.*namespace. 24 attribute files — 11 canonical, 13 deprecated — plusturbo_moduleas amobilespan operation.These attributes shipped before being registered here, which is backwards: they have been live since sentry-react-native 8.14.0 (
turbo_module.name/.method), 8.19.0 (call aggregate) and 8.21.0 (span attribution). This PR is retroactive registration plus a cleanup of the naming that slipped through as a result. That is why 13 of the 24 files arrive already deprecated — they are the shipped names, registered so they can be aliased and backfilled rather than silently broken.Canonical
react_native.architectureneworlegacyreact_native.module.nameNativeModuleskey on Old Arch)react_native.module.methodreact_native.module.kindsyncorasyncreact_native.module.call.countreact_native.module.call.distinct_countreact_native.module.error.countreact_native.module.duration.totalreact_native.module.duration.maxreact_native.module.top.name<module>.<method>with the highest total durationreact_native.module.top.durationWhy
react_native.*and notturbo_module.*The first version of this PR put everything under
turbo_module.*, matching the shipped names. That namespace is wrong on two counts.It names a React Native implementation detail rather than the thing being measured. And it is inaccurate even for the attributes in this PR:
react_native.architecturehas the valuelegacy, describing calls reached over the Old Architecture bridge, which are not TurboModules at all. The instrumentation covers native module calls on both architectures; only one of them involves TurboModules.react_nativeis also a namespace this repo will want regardless — Hermes, Fabric, bundle and architecture attributes all belong there, andturbo_modulewould otherwise sit at the top level as a single-purpose orphan next toreact.The architecture sits at
react_native.architecturerather than underreact_native.module.*because it describes the runtime the app is on, not a property of an individual call, and it is worth setting on spans that involve no native module call.Doing this now rather than in the next major is the cheap option. Nothing here is merged, and the SDK-side rename is already gated on this PR, so the SDK pays for one rename instead of two. Renaming later would mean deprecating all 11 canonical keys — a second backfill wave on keys added weeks earlier.
Is there a more general namespace for this?
The closest existing fit is
rpc.*: module →rpc.service, method →rpc.method, plus anrpc.system.namevalue (the OTel enum is open). It was considered and rejected.On the New Architecture a TurboModule call is a direct synchronous JSI invocation in the same process — no serialization, no network, no remote peer. OTel's
rpc.*describes remoting systems, with client/server spans andrpc.response.status_code. The Old Architecture bridge (async, serialized) is arguably RPC-like, but the attributes are shared across both, so the group as a whole does not fit. Adoptingrpc.*would also mean product surfaces aggregating byrpc.system.namestart mixing gRPC services with local native module calls.code.*does not fit either: it describes the currently executing frame, not the callee.Deprecated
All with
_status: "backfill"and symmetric aliases.turbo_module.namereact_native.module.nameturbo_module.methodreact_native.module.methodturbo_module.archreact_native.architectureturbo_module.total_call_count,turbo_modules.total_call_countreact_native.module.call.countturbo_module.total_error_count,turbo_modules.total_error_countreact_native.module.error.countturbo_module.total_duration_ms,turbo_modules.total_duration_msreact_native.module.duration.totalturbo_module.unique_methods,turbo_modules.unique_methodsreact_native.module.call.distinct_countturbo_module.top_modulereact_native.module.top.nameturbo_module.top_module_duration_msreact_native.module.top.durationThree problems fixed. The namespace, above. Then: the SDK emitted the same four totals as
turbo_module.*on root spans andturbo_modules.*on the aggregate span — same semantics, two prefixes, which is a third of the deprecations. The rest droptotal_prefixes and_mssuffixes in favour of dot-separated grouping, with the unit in thebriefasapp.vitals.*does.react_native.module.kindandreact_native.module.duration.maxare genuinely new and have no deprecated predecessor.Keys removed rather than deprecated
The SDK also emits per-method breakdowns with the dynamic segment in the middle of the key, e.g.
turbo_module.<module>.<method>.call_countandturbo_modules.<module>.<method>.<kind>.count.has_dynamic_suffixonly models a dynamic trailing segment, so these are not expressible in the schema and no file is included for them; they cannot be formally deprecated.The SDK will stop emitting them and carry the per-method breakdown as one child span per
(module, method)using the static attributes above. That is why the identity attributes (name,method,kind,architecture) are registered alongside the counters: on a per-method child span they are dimensions, not a key prefix.Not included
turbo_modules.*transaction measurements. Every existing entry inmodel/measurements/is a flat unnamespaced key, and they duplicate numbers now available as span attributes, so the SDK will drop them instead.native.turbo_module, the slow-call breadcrumb category — breadcrumb categories are not modelled in this repo.event.kind: turbo_modules.aggregatetag, an unnamespaced generic key being removed SDK-side.Span operation
The
turbo_modulespan operation keeps its name. Span operations in this repo have noaliasordeprecationfields, so renaming one is an unmitigated break for anything queryingspan.op— worth discussing separately from the attributes.The SDK-side rename is gated on this PR and tracked in getsentry/sentry-react-native#6168.
PR Checklist
yarn testand verified that the tests pass.yarn generateto generate and format code and docs.If an attribute was added:
nextjs.function_id, notfunction_id)apply_scrubbing(i.e.manualorauto. Useneveronly for values that should never be scrubbed such as IDs)If an attribute was deprecated: