Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,15 @@ const otel = new FastifyOtelInstrumentation({

Control which Fastify lifecycle hooks receive child spans. Defaults to instrumenting all hooks listed in Usage.

* `true` (default) – instrument all lifecycle hooks (`onRequest`, `preParsing`, `preValidation`, `preHandler`, `preSerialization`, `onSend`, `onResponse`, `onError`)
* `false` – no lifecycle hook child spans (the root `request` span and route `handler` span are still created)
* `string[]` – allowlist of hook names to instrument (for example `['preHandler']`)
* `true` (default) – instrument the route `handler` and all lifecycle hooks (`onRequest`, `preParsing`, `preValidation`, `preHandler`, `preSerialization`, `onSend`, `onResponse`, `onError`)
* `false` – no handler or lifecycle hook child spans (the root `request` span is still created)
* `string[]` – allowlist of hook names to instrument (for example `['handler', 'preHandler']`)

Per-route override via `config.otel`:

* `otel: false` – disable all OpenTelemetry spans for the route (unchanged)
* `otel: { instrumentHooks: false }` – request and handler spans only
* `otel: { instrumentHooks: true }` – all lifecycle hooks on the route (overrides a global `false`)
* `otel: { instrumentHooks: false }` – request span only
* `otel: { instrumentHooks: true }` – handler and all lifecycle hooks on the route (overrides a global `false`)
* `otel: { instrumentHooks: ['preHandler'] }` – allowlist for the route (overrides global settings)

Precedence: `otel: false`, then route `otel.instrumentHooks` when set, otherwise the global `instrumentHooks` option.
Expand Down
7 changes: 4 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const FASTIFY_HOOKS = [
'onResponse',
'onError'
]
const INSTRUMENTABLE_HOOKS = [...FASTIFY_HOOKS, 'handler']
const ATTRIBUTE_NAMES = {
HOOK_NAME: 'hook.name',
FASTIFY_TYPE: 'fastify.type',
Expand Down Expand Up @@ -77,7 +78,7 @@ function normalizeInstrumentHooks (value, { strict = false, logger = null } = {}
const allowlist = new Set()

for (const hookName of value) {
if (typeof hookName !== 'string' || !FASTIFY_HOOKS.includes(hookName)) {
if (typeof hookName !== 'string' || !INSTRUMENTABLE_HOOKS.includes(hookName)) {
if (strict) {
throw new TypeError('instrumentHooks must be a boolean or an array of hook names')
}
Expand Down Expand Up @@ -105,12 +106,12 @@ function getHookPolicy (config, globalPolicy, logger = null) {
}

function lifecycleHookBaseName (hookName) {
if (FASTIFY_HOOKS.includes(hookName)) {
if (INSTRUMENTABLE_HOOKS.includes(hookName)) {
return hookName
}
if (hookName.includes(' - ')) {
const base = hookName.split(' - ').pop()
if (FASTIFY_HOOKS.includes(base)) {
if (INSTRUMENTABLE_HOOKS.includes(base)) {
return base
}
}
Expand Down
1 change: 1 addition & 0 deletions test/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('Interface', () => {
assert.throws(() => new FastifyInstrumentation({ instrumentHooks: ['notAHook'] }), /boolean or an array/)
assert.doesNotThrow(() => new FastifyInstrumentation({ instrumentHooks: false }))
assert.doesNotThrow(() => new FastifyInstrumentation({ instrumentHooks: true }))
assert.doesNotThrow(() => new FastifyInstrumentation({ instrumentHooks: ['handler'] }))
assert.doesNotThrow(() => new FastifyInstrumentation({ instrumentHooks: ['preHandler'] }))
})

Expand Down
62 changes: 54 additions & 8 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -712,9 +712,9 @@ describe('FastifyInstrumentation', () => {
.getFinishedSpans()
.filter(span => span.instrumentationScope.name === '@fastify/otel')

assert.equal(spans.length, 2)
assert.equal(spans.length, 1)
assert.equal(spans.find(s => s.name === 'request') != null, true)
assert.equal(spans.find(s => s.name === 'handler - helloworld') != null, true)
assert.equal(spans.find(s => s.name === 'handler - helloworld'), undefined)
assert.equal(spans.find(s => s.name.startsWith('onRequest -')), undefined)
assert.equal(spans.find(s => s.name.startsWith('preHandler -')), undefined)
assert.equal(response.status, 200)
Expand Down Expand Up @@ -750,7 +750,8 @@ describe('FastifyInstrumentation', () => {
.getFinishedSpans()
.filter(span => span.instrumentationScope.name === '@fastify/otel')

assert.equal(spans.length, 2)
assert.equal(spans.length, 1)
assert.equal(spans.find(s => s.name === 'handler - helloworld'), undefined)
assert.equal(spans.find(s => s.name.startsWith('onRequest -')), undefined)
assert.equal(spans.find(s => s.name.startsWith('preHandler -')), undefined)
assert.equal(response.status, 200)
Expand Down Expand Up @@ -789,9 +790,51 @@ describe('FastifyInstrumentation', () => {
.getFinishedSpans()
.filter(span => span.instrumentationScope.name === '@fastify/otel')

assert.equal(spans.length, 3)
assert.equal(spans.length, 2)
assert.equal(spans.find(s => s.name === 'preHandler - somePreHandler') != null, true)
assert.equal(spans.find(s => s.name === 'handler - helloworld'), undefined)
assert.equal(spans.find(s => s.name.startsWith('onRequest -')), undefined)
assert.equal(response.status, 200)
})

test('should create only the handler span when instrumentHooks allows handler', async t => {
const localInstrumentation = new FastifyInstrumentation({
instrumentHooks: ['handler']
})
localInstrumentation.setTracerProvider(provider)
const app = Fastify()
const plugin = localInstrumentation.plugin()

await app.register(plugin)

app.get(
'/',
{
onRequest: (request, reply, done) => { done() },
preHandler: (request, reply, done) => { done() }
},
async function helloworld () {
return 'hello world'
}
)

await app.listen()

after(() => app.close())

const response = await fetch(
`http://localhost:${app.server.address().port}/`
)

const spans = memoryExporter
.getFinishedSpans()
.filter(span => span.instrumentationScope.name === '@fastify/otel')

assert.equal(spans.length, 2)
assert.equal(spans.find(s => s.name === 'request') != null, true)
assert.equal(spans.find(s => s.name === 'handler - helloworld') != null, true)
assert.equal(spans.find(s => s.name.startsWith('onRequest -')), undefined)
assert.equal(spans.find(s => s.name.startsWith('preHandler -')), undefined)
assert.equal(response.status, 200)
})

Expand Down Expand Up @@ -822,7 +865,7 @@ describe('FastifyInstrumentation', () => {
.getFinishedSpans()
.filter(span => span.instrumentationScope.name === '@fastify/otel')

assert.equal(spans.length, 2)
assert.equal(spans.length, 1)
})

test('should ignore invalid per-route instrumentHooks allowlist entries', async t => {
Expand Down Expand Up @@ -852,7 +895,8 @@ describe('FastifyInstrumentation', () => {
.getFinishedSpans()
.filter(span => span.instrumentationScope.name === '@fastify/otel')

assert.equal(spans.length, 2)
assert.equal(spans.length, 1)
assert.equal(spans.find(s => s.name === 'handler - helloworld'), undefined)
assert.equal(spans.find(s => s.name.startsWith('onRequest -')), undefined)
})

Expand Down Expand Up @@ -889,9 +933,10 @@ describe('FastifyInstrumentation', () => {
.getFinishedSpans()
.filter(span => span.instrumentationScope.name === '@fastify/otel')

assert.equal(spans.length, 3)
assert.equal(spans.length, 2)
assert.equal(spans.find(s => s.name.startsWith('onRequest -')), undefined)
assert.equal(spans.find(s => s.name === 'preHandler - routePreHandler') != null, true)
assert.equal(spans.find(s => s.name === 'handler - helloworld'), undefined)
})

test('should override global instrumentHooks false with per-route allowlist', async t => {
Expand Down Expand Up @@ -926,8 +971,9 @@ describe('FastifyInstrumentation', () => {
.getFinishedSpans()
.filter(span => span.instrumentationScope.name === '@fastify/otel')

assert.equal(spans.length, 3)
assert.equal(spans.length, 2)
assert.equal(spans.find(s => s.name === 'onRequest - routeOnRequest') != null, true)
assert.equal(spans.find(s => s.name === 'handler - helloworld'), undefined)
assert.equal(spans.find(s => s.name.startsWith('preHandler -')), undefined)
assert.equal(response.status, 200)
})
Expand Down
1 change: 1 addition & 0 deletions types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Context, Span, TextMapGetter, TextMapSetter, Tracer } from '@opent
import type { HTTPMethods } from 'fastify'

export type FastifyOtelHookName =
| 'handler'
| 'onRequest'
| 'preParsing'
| 'preValidation'
Expand Down