FastifyRequest#opentelemetry() reports enabled when no request span/context exists
- Dominant language
- JavaScript
- Stars
- 62
- Forks
- 29
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 4
Description
### Prerequisites
- [x] I have written a descriptive issue title
- [x] 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
```js
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:
```js
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:
```js
{
enabled: false,
span: null,
context: null
}
```
### Actual Behavior
`request.opentelemetry()` currently returns:
```js
{
enabled: true,
span: null,
context: null
}
```
This can cause user code that relies on the discriminated union to crash:
```js
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.
Contributor guide
Research direction
Start at the implementation of the request.opentelemetry() entry point and trace how instrumentation state, route configuration, span, and context are obtained. Reproduce the ignorePaths and globally disabled cases from the issue, then verify that the returned object has enabled: false with null span and context when no request span exists.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- observability-sre
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100