oRPC OpenTelemetry Integration

Integrate oRPC with OpenTelemetry for distributed tracing.

SKILL.md

GitHub

OpenTelemetry Integration

OpenTelemetry provides observability APIs and instrumentation for applications.

Installation

npm install @orpc/otel@latest

Setup

// server
import { NodeSDK } from '@opentelemetry/sdk-node'
import { ORPCInstrumentation } from '@orpc/otel'

const sdk = new NodeSDK({
  instrumentations: [
    new ORPCInstrumentation(),
  ],
})

sdk.start()
// client
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
import { registerInstrumentations } from '@opentelemetry/instrumentation'
import { ORPCInstrumentation } from '@orpc/otel'

const provider = new WebTracerProvider()
provider.register()

registerInstrumentations({
  instrumentations: [new ORPCInstrumentation()],
})

Middleware Span

import { trace } from '@opentelemetry/api'

export const someMiddleware = os.middleware(async (ctx, next) => {
  const span = trace.getActiveSpan()

  span?.setAttribute('someAttribute', 'someValue')
  span?.addEvent('someEvent')

  return next()
})

Object.defineProperty(someMiddleware, 'name', {
  value: 'someName',
})

Define name property on middleware to improve span naming.

Handling Uncaught Exceptions

import { SpanStatusCode, trace } from '@opentelemetry/api'

const tracer = trace.getTracer('uncaught-errors')

function recordError(eventName: string, reason: unknown) {
  const span = tracer.startSpan(eventName)
  const message = String(reason)

  if (reason instanceof Error) {
    span.recordException(reason)
  } else {
    span.recordException({ message })
  }

  span.setStatus({ code: SpanStatusCode.ERROR, message })
  span.end()
}

process.on('uncaughtException', (reason) => {
  recordError('uncaughtException', reason)
})

process.on('unhandledRejection', (reason) => {
  recordError('unhandledRejection', reason)
})

Capture Abort Signals

const handler = new RPCHandler(router, {
  interceptors: [
    ({ request, next }) => {
      const span = trace.getActiveSpan()

      request.signal?.addEventListener('abort', () => {
        span?.addEvent('aborted', { reason: String(request.signal?.reason) })
      })

      return next()
    },
  ],
})

Context Propagation

When using HTTP/fetch adapters, set up HTTP instrumentation for context propagation on both client and server.