Skip to content

Conversation

TheYoxy
Copy link

@TheYoxy TheYoxy commented Oct 21, 2025

This adds an attribute to every span created by orpc, which describes the operation when sentry is detected in the opentelemetry context.

It search in the opentelemetry active context, for the symbol sentry_scopes which is added by default when calling Sentry.init().
Reference of this symbol

The operation is using an attribute, sentry.op which is defined here. This attribute describes the operation type and will be displayed directly on the sentry dashboard as orpc.

Here is a preview of what it should look likes when sentry is detected:

image

Without sentry.op set

image

With sentry.op set. I have updated the name of the span for this one

Note

I haven't added any specific playground to play with it since it needs to setup a sentry instance with accounts that must be shared with anyone who wants to try it.

Summary by CodeRabbit

  • New Features
    • Enhanced Sentry integration with improved operation tracing and diagnostic visibility for better error tracking and monitoring across the application.

@vercel
Copy link

vercel bot commented Oct 21, 2025

@TheYoxy is attempting to deploy a commit to the unnoq-team Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Walkthrough

This PR adds Sentry context detection and OpenTelemetry span annotation throughout the oRPC stack. A new isInSentryContext() utility function checks if code is running within a Sentry context, and when true, sets the sentry.op attribute to "orpc" on OTEL spans across client adapters, server handlers, procedure execution, batch processing, and peer communication layers.

Changes

Cohort / File(s) Summary
Core Sentry Context Detection
packages/shared/src/otel.ts, packages/shared/src/otel.test.ts
Introduces isInSentryContext() function that checks for Sentry scope in active OTEL context using a symbol key. Includes unit tests for presence/absence of Sentry context scenarios.
Client Adapter
packages/client/src/adapters/standard/link.ts
Adds isInSentryContext guard to conditionally set sentry.op attribute on spans during encode, send, and decode stages of RPC link operations.
Server Handler
packages/server/src/adapters/standard/handler.ts
Wraps handle, find_procedure, and decode_input span callbacks with isInSentryContext checks to annotate root and nested spans with sentry.op="orpc".
Server Batch Processing
packages/server/src/plugins/batch.ts
Sets sentry.op on root batch request span when isInSentryContext() returns true in batch interceptor.
Server Procedure Execution
packages/server/src/procedure-client.ts
Applies isInSentryContext checks across multiple execution paths: procedure call, validation stages, middleware execution, and handler invocation to tag spans.
Standard Server Transport
packages/standard-server-fetch/src/body.ts, packages/standard-server-peer/src/client.ts, packages/standard-server-peer/src/event-iterator.ts, packages/standard-server-peer/src/server.ts
Consistently adds isInSentryContext imports and span callbacks that conditionally set sentry.op attribute across fetch body handling, peer client requests, event iterators, and peer server operations.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Span as OTEL Span
    participant Sentry as Sentry Context
    participant Op as Operation

    App->>Span: runWithSpan(callback)
    Span->>Sentry: isInSentryContext()?
    alt In Sentry Context
        Sentry-->>Span: true
        Span->>Span: span.setAttribute("sentry.op", "orpc")
    else Not in Sentry Context
        Sentry-->>Span: false
        note over Span: No sentry.op set
    end
    Span->>Op: Execute callback with span
    Op-->>Span: Complete
    Span-->>App: Return result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The changes follow a consistent repetitive pattern across multiple files (import function, add conditional check, set span attribute), reducing cognitive load. However, the distributed nature across 9+ files and multiple layers (adapters, handlers, procedures, transport) requires systematic review to ensure the guard is correctly applied in each context.

Possibly related PRs

Suggested labels

size:L, instrumentation, observability

Poem

🐰 Spans now wear Sentry's badge with pride,
"orpc" tagged where context hides,
From client to peer, a glowing trail,
Tracing every oRPC tale!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "[otel] - Adding sentry operation attribute" directly summarizes the primary objective of the changeset. The PR introduces a new isInSentryContext() function and systematically applies it across multiple files to conditionally set the sentry.op attribute on OpenTelemetry spans when operating within a Sentry context. The title is specific, clear, and concise—it uses descriptive language ("Adding sentry operation attribute") rather than vague terms, includes the "[otel]" prefix to indicate the OpenTelemetry scope, and would allow a teammate scanning the history to quickly understand that this PR enhances OpenTelemetry span instrumentation for Sentry integration.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @TheYoxy, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request aims to improve the observability of orpc applications when using Sentry. By programmatically adding a specific operation attribute to OpenTelemetry spans, it allows for more granular and meaningful tracing data to be displayed in the Sentry dashboard, making it easier to understand the context and flow of orpc related activities within a distributed system.

Highlights

  • Sentry Integration: This pull request enhances Sentry integration by adding a sentry.op attribute to OpenTelemetry spans. This attribute, set to 'orpc', will provide clearer categorization of operations within the Sentry dashboard when Sentry is detected in the active OpenTelemetry context.
  • New isInSentryContext Utility: A new utility function, isInSentryContext, has been introduced in the @orpc/shared package. This function checks for the presence of the sentry_scopes symbol in the OpenTelemetry context, allowing for conditional application of Sentry-specific attributes.
  • Widespread Span Attribution: The sentry.op: 'orpc' attribute is now conditionally applied to various spans across the codebase, including client-side request processing, server-side request handling, batch operations, procedure calls, input/output validation, middleware execution, and peer-to-peer communication, ensuring comprehensive Sentry visibility for orpc operations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Oct 21, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly adds the sentry.op attribute to OpenTelemetry spans when Sentry is detected. The implementation of isInSentryContext and its tests are well done. However, there is significant code duplication across multiple files where the attribute is set. I've suggested extracting this logic into a new helper function setSentryOpAttribute in packages/shared/src/otel.ts and using it throughout the codebase. This will improve maintainability and centralize the Sentry-specific logic.

import type { StandardLinkPlugin } from './plugin'
import type { StandardLinkClient, StandardLinkCodec } from './types'
import { asyncIteratorWithSpan, getGlobalOtelConfig, intercept, isAsyncIteratorObject, ORPC_NAME, runWithSpan, toArray } from '@orpc/shared'
import { asyncIteratorWithSpan, getGlobalOtelConfig, intercept, isAsyncIteratorObject, isInSentryContext, ORPC_NAME, runWithSpan, toArray } from '@orpc/shared'
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To avoid code duplication, I've suggested adding a setSentryOpAttribute helper. Please import it here instead of isInSentryContext.

Suggested change
import { asyncIteratorWithSpan, getGlobalOtelConfig, intercept, isAsyncIteratorObject, isInSentryContext, ORPC_NAME, runWithSpan, toArray } from '@orpc/shared'
import { asyncIteratorWithSpan, getGlobalOtelConfig, intercept, isAsyncIteratorObject, setSentryOpAttribute, ORPC_NAME, runWithSpan, toArray } from '@orpc/shared'

Comment on lines +53 to +55
if (isInSentryContext()) {
span?.setAttribute('sentry.op', 'orpc')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Please use the setSentryOpAttribute helper function to reduce code duplication.

        setSentryOpAttribute(span)

Comment on lines +79 to +81
if (isInSentryContext()) {
span?.setAttribute('sentry.op', 'orpc')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Please use the setSentryOpAttribute helper function to reduce code duplication.

              setSentryOpAttribute(span)

Comment on lines +94 to +96
if (isInSentryContext()) {
span?.setAttribute('sentry.op', 'orpc')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Please use the setSentryOpAttribute helper function to reduce code duplication.

                  setSentryOpAttribute(span)

Comment on lines +107 to +109
if (isInSentryContext()) {
span?.setAttribute('sentry.op', 'orpc')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Please use the setSentryOpAttribute helper function to reduce code duplication.

              setSentryOpAttribute(span)

Comment on lines +98 to +100
if (isInSentryContext()) {
span?.setAttribute('sentry.op', 'orpc')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Please use the setSentryOpAttribute helper function to reduce code duplication.

      setSentryOpAttribute(span)

import type { EventIteratorPayload } from './codec'
import type { EncodedMessage, EncodedMessageSendFn } from './types'
import { AbortError, AsyncIdQueue, getGlobalOtelConfig, isAsyncIteratorObject, runWithSpan } from '@orpc/shared'
import { AbortError, AsyncIdQueue, getGlobalOtelConfig, isAsyncIteratorObject, isInSentryContext, runWithSpan } from '@orpc/shared'
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To avoid code duplication, I've suggested adding a setSentryOpAttribute helper. Please import it here instead of isInSentryContext.

Suggested change
import { AbortError, AsyncIdQueue, getGlobalOtelConfig, isAsyncIteratorObject, isInSentryContext, runWithSpan } from '@orpc/shared'
import { AbortError, AsyncIdQueue, getGlobalOtelConfig, isAsyncIteratorObject, setSentryOpAttribute, runWithSpan } from '@orpc/shared'

Comment on lines +111 to +113
if (isInSentryContext()) {
span?.setAttribute('sentry.op', 'orpc')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Please use the setSentryOpAttribute helper function to reduce code duplication.

          setSentryOpAttribute(span)

Comment on lines +118 to +120
if (isInSentryContext()) {
span?.setAttribute('sentry.op', 'orpc')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Please use the setSentryOpAttribute helper function to reduce code duplication.

              setSentryOpAttribute(span)

Comment on lines +143 to +145
if (isInSentryContext()) {
span?.setAttribute('sentry.op', 'orpc')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Please use the setSentryOpAttribute helper function to reduce code duplication.

              setSentryOpAttribute(span)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/standard-server-peer/src/event-iterator.ts (1)

7-7: Minor formatting adjustment.

The space inside the empty braces is a stylistic change with no functional impact. Typically handled by code formatters.

packages/server/src/adapters/standard/handler.ts (1)

86-88: Consider extracting the Sentry span annotation pattern.

The same conditional block appears three times in this file:

if (isInSentryContext()) {
  span?.setAttribute('sentry.op', 'orpc')
}

You could extract this into a helper function to reduce duplication:

function setSentryOpIfInContext(span: Span | undefined): void {
  if (isInSentryContext()) {
    span?.setAttribute('sentry.op', 'orpc')
  }
}

Then use: setSentryOpIfInContext(span)

However, since this pattern is applied consistently across multiple modules according to the PR summary, the current approach prioritizes clarity and local reasoning over abstraction.

Also applies to: 105-110, 127-132

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcb5260 and 1a593ec.

📒 Files selected for processing (10)
  • packages/client/src/adapters/standard/link.ts (5 hunks)
  • packages/server/src/adapters/standard/handler.ts (5 hunks)
  • packages/server/src/plugins/batch.ts (2 hunks)
  • packages/server/src/procedure-client.ts (6 hunks)
  • packages/shared/src/otel.test.ts (2 hunks)
  • packages/shared/src/otel.ts (2 hunks)
  • packages/standard-server-fetch/src/body.ts (2 hunks)
  • packages/standard-server-peer/src/client.ts (2 hunks)
  • packages/standard-server-peer/src/event-iterator.ts (2 hunks)
  • packages/standard-server-peer/src/server.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
packages/server/src/plugins/batch.ts (1)
packages/shared/src/otel.ts (1)
  • isInSentryContext (206-209)
packages/server/src/procedure-client.ts (1)
packages/shared/src/otel.ts (1)
  • isInSentryContext (206-209)
packages/standard-server-peer/src/event-iterator.ts (1)
packages/shared/src/otel.ts (2)
  • SetSpanErrorOptions (56-61)
  • isInSentryContext (206-209)
packages/client/src/adapters/standard/link.ts (1)
packages/shared/src/otel.ts (2)
  • isInSentryContext (206-209)
  • runWithSpan (155-184)
packages/server/src/adapters/standard/handler.ts (2)
packages/shared/src/otel.ts (1)
  • isInSentryContext (206-209)
packages/standard-server-peer/src/client.ts (1)
  • request (73-179)
packages/standard-server-peer/src/client.ts (1)
packages/shared/src/otel.ts (1)
  • isInSentryContext (206-209)
packages/standard-server-peer/src/server.ts (1)
packages/shared/src/otel.ts (2)
  • isInSentryContext (206-209)
  • runWithSpan (155-184)
packages/standard-server-fetch/src/body.ts (2)
packages/standard-server-node/src/body.ts (2)
  • ToStandardBodyOptions (10-10)
  • toStandardBody (12-51)
packages/shared/src/otel.ts (2)
  • runWithSpan (155-184)
  • isInSentryContext (206-209)
packages/shared/src/otel.test.ts (1)
packages/shared/src/otel.ts (2)
  • setGlobalOtelConfig (34-36)
  • isInSentryContext (206-209)
🔇 Additional comments (16)
packages/shared/src/otel.ts (2)

10-15: LGTM: Symbol definition aligns with Sentry conventions.

The use of Symbol.for('sentry_scopes') correctly creates a global symbol that matches Sentry's internal implementation, enabling proper detection of Sentry context across module boundaries.


203-209: LGTM: Clean implementation with safe optional chaining.

The function correctly detects Sentry context by checking for the presence of the Sentry scope symbol in the active OpenTelemetry context. The optional chaining ensures graceful handling when OTEL is not configured.

packages/server/src/plugins/batch.ts (1)

82-85: LGTM: Sentry context detection properly integrated.

The conditional span annotation is correctly placed at the start of the batch request handling span, ensuring Sentry operation metadata is available when operating within a Sentry context.

packages/server/src/procedure-client.ts (1)

114-116: LGTM: Comprehensive span annotation across procedure lifecycle.

The Sentry context check is systematically applied to all critical spans in the procedure execution flow (call_procedure, validate_input, validate_output, middleware, and handler), ensuring consistent observability when integrated with Sentry.

packages/standard-server-peer/src/client.ts (1)

78-81: LGTM: Consistent Sentry context handling in peer client.

The span annotation follows the established pattern. The callback is correctly marked as async to accommodate the attribute setting logic.

packages/standard-server-peer/src/server.ts (1)

110-113: LGTM: Proper span annotation in peer server flow.

The Sentry context detection is correctly applied across all peer server spans (receive, handle, and send), maintaining consistency with the broader instrumentation pattern.

packages/standard-server-fetch/src/body.ts (1)

15-18: LGTM: Span parameter correctly utilized for Sentry context.

The callback signature properly updated to accept the span parameter, enabling conditional annotation based on Sentry context detection.

packages/shared/src/otel.test.ts (1)

365-394: LGTM: Comprehensive test coverage for Sentry context detection.

The test suite properly validates all critical scenarios: symbol presence (true case), missing global config (false case), and symbol absence in active context (false case). The test implementation correctly uses Symbol.for('sentry_scopes') to match the production code.

packages/client/src/adapters/standard/link.ts (2)

53-55: LGTM: Root span and nested spans properly annotated.

The Sentry context check is correctly integrated at the root RPC span level and propagated through encode, send, and decode operations. The implementation aligns with OpenTelemetry semantic conventions for RPC spans.


65-68: Good documentation of context management strategy.

The updated comment clearly explains the need for manual context management in browser environments where the OpenTelemetry context manager may not work reliably with async functions.

packages/standard-server-peer/src/event-iterator.ts (2)

4-4: LGTM - Import addition is correct.

The isInSentryContext import is properly added and used in the implementation below.


98-100: LGTM - Sentry context detection and span annotation.

The implementation correctly checks for Sentry context and sets the sentry.op attribute when appropriate. This follows the established pattern used across other modules in the PR.

packages/server/src/adapters/standard/handler.ts (4)

11-11: LGTM - Import addition is correct.

The isInSentryContext import is properly added and used throughout the file.


86-88: LGTM - Sentry span annotation in top-level handler.

The Sentry context check and span attribute setting is correctly implemented for the top-level request span.


105-110: LGTM - Correct refactoring to callback pattern.

The change from a direct call to a callback pattern is correct for runWithSpan. The Sentry context check is properly integrated.


127-132: LGTM - Correct refactoring to callback pattern.

The refactoring matches the pattern used for the find_procedure span and correctly integrates the Sentry context check.

@unnoq
Copy link
Owner

unnoq commented Oct 21, 2025

OpenTelemetry already distinguishes spans using the package name: https://github.com/unnoq/orpc/blob/main/packages/otel/src/instrumentation.ts#L20
So I don’t think this should be handled by oRPC - instead, I'd suggest asking Sentry to improve its OpenTelemetry compatibility.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 21, 2025

More templates

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1120

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1120

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1120

@orpc/experimental-durable-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-iterator@1120

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@1120

@orpc/interop

npm i https://pkg.pr.new/@orpc/interop@1120

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1120

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1120

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1120

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@1120

@orpc/otel

npm i https://pkg.pr.new/@orpc/otel@1120

@orpc/experimental-publisher

npm i https://pkg.pr.new/@orpc/experimental-publisher@1120

@orpc/react

npm i https://pkg.pr.new/@orpc/react@1120

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@1120

@orpc/experimental-react-swr

npm i https://pkg.pr.new/@orpc/experimental-react-swr@1120

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1120

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1120

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@1120

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@1120

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@1120

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@1120

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@1120

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@1120

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@1120

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1120

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1120

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1120

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@1120

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@1120

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1120

commit: 1a593ec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants