Skip to content

FastifyRequest#opentelemetry() reports enabled when no request span/context exists #170

Description

@0XFF-96

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

5.10.0

Plugin version

11.0.2

Node.js version

22.22.2

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

26.5.2

Description

request.opentelemetry().enabled can return true even when the current request was not instrumented and both span and context are null.

This happens when instrumentation is skipped by ignorePaths, and also when the instrumentation instance is disabled globally. In both cases the request span is not created, but the public request API currently computes enabled only from the route config, roughly equivalent to config.otel !== false.

That makes the runtime value inconsistent with the TypeScript contract for FastifyOtelRequestContext, where enabled: true implies non-null span and context, while enabled: false implies both are null.

Steps to Reproduce

const Fastify = require('fastify')
const FastifyOtelInstrumentation = require('@fastify/otel')

const app = Fastify()
const instrumentation = new FastifyOtelInstrumentation({
  ignorePaths: '/health'
})

await app.register(instrumentation.plugin())

app.get('/health', (request) => {
  const otel = request.opentelemetry()

  console.log(otel.enabled) // true
  console.log(otel.span)    // null
  console.log(otel.context) // null

  return 'ok'
})

await app.inject({ method: 'GET', url: '/health' })

The same contract issue can happen when instrumentation is disabled globally:

const app = Fastify()
const instrumentation = new FastifyOtelInstrumentation()

await app.register(instrumentation.plugin())

app.get('/disabled', (request) => {
  const otel = request.opentelemetry()

  console.log(otel.enabled) // true
  console.log(otel.span)    // null
  console.log(otel.context) // null

  return 'ok'
})

instrumentation.disable()

await app.inject({ method: 'GET', url: '/disabled' })

Expected Behavior

When instrumentation is skipped and no request span/context exists, request.opentelemetry() should return a disabled context:

{
  enabled: false,
  span: null,
  context: null
}

Actual Behavior

request.opentelemetry() currently returns:

{
  enabled: true,
  span: null,
  context: null
}

This can cause user code that relies on the discriminated union to crash:

const otel = request.opentelemetry()

if (otel.enabled) {
  otel.span.setAttribute('example', 'value')
}

Fix

Compute enabled from the actual instrumentation/request state, not only from route config. It should be true only when instrumentation is enabled, the route is not disabled, and the request actually has a non-null span and context.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions