Skip to content

Commit 18ae75c

Browse files
trentmpichlermarc
andauthored
fix(instrumentation-fastify): fix span attributes and avoid FSTDEP017 FastifyDeprecation warning for 404 request (#1763)
For a 404 `request.routeOptions.url` is undefined. Since [email protected] when routeOptions was added, we shouldn't fallback to the deprecated request.routerPath. This also corrects the assumption that the handler name is "bound ..." in all cases. E.g. for a 404 it is Fastify's core "basic404" internal function. Also add a test that Fastify instrumentation works for ESM usage. Fixes: #1757 Co-authored-by: Marc Pichler <[email protected]>
1 parent b39c96c commit 18ae75c

File tree

6 files changed

+151
-12
lines changed

6 files changed

+151
-12
lines changed

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"fastify":
2-
- versions: "4.23.2"
2+
# Sanity check the first 4.x release, instead of all releases, plus recent
3+
# releases.
4+
- versions: "4.0.0 || >=4.24.3 <5"
35
commands: npm run test
6+
7+
# Fastify versions after 4.18.0 require a typescript greater than 4.4.4.
48
"typescript":
59
- versions: "4.7.4"

plugins/node/opentelemetry-instrumentation-fastify/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,19 @@
4646
"@fastify/express": "^2.0.2",
4747
"@opentelemetry/api": "^1.3.0",
4848
"@opentelemetry/context-async-hooks": "^1.8.0",
49+
"@opentelemetry/contrib-test-utils": "^0.35.0",
4950
"@opentelemetry/instrumentation-http": "^0.45.1",
5051
"@opentelemetry/sdk-trace-base": "^1.8.0",
5152
"@opentelemetry/sdk-trace-node": "^1.8.0",
5253
"@types/express": "4.17.18",
5354
"@types/mocha": "7.0.2",
5455
"@types/node": "18.15.3",
56+
"@types/semver": "7.5.5",
5557
"fastify": "4.18.0",
5658
"mocha": "7.2.0",
5759
"nyc": "15.1.0",
5860
"rimraf": "5.0.5",
61+
"semver": "^7.5.4",
5962
"test-all-versions": "5.0.1",
6063
"ts-mocha": "10.0.0",
6164
"typescript": "4.4.4"

plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ export class FastifyInstrumentation extends InstrumentationBase {
9696
const anyRequest = request as any;
9797

9898
const rpcMetadata = getRPCMetadata(context.active());
99-
const routeName =
100-
anyRequest.routeOptions?.config?.url || request.routerPath;
99+
const routeName = anyRequest.routeOptions
100+
? anyRequest.routeOptions.url // since [email protected]
101+
: request.routerPath;
101102
if (routeName && rpcMetadata?.type === RPCType.HTTP) {
102103
rpcMetadata.route = routeName;
103104
}
@@ -265,18 +266,21 @@ export class FastifyInstrumentation extends InstrumentationBase {
265266
const anyRequest = request as any;
266267

267268
const handler =
268-
anyRequest.routeOptions?.handler || anyRequest.context?.handler || {};
269+
anyRequest.routeOptions?.handler || anyRequest.context?.handler;
269270

270-
const handlerName = handler?.name.substr(6);
271+
const handlerName = handler?.name.startsWith('bound ')
272+
? handler.name.substr(6)
273+
: handler?.name;
271274
const spanName = `${FastifyNames.REQUEST_HANDLER} - ${
272275
handlerName || this.pluginName || ANONYMOUS_NAME
273276
}`;
274277

275278
const spanAttributes: SpanAttributes = {
276279
[AttributeNames.PLUGIN_NAME]: this.pluginName,
277280
[AttributeNames.FASTIFY_TYPE]: FastifyTypes.REQUEST_HANDLER,
278-
[SemanticAttributes.HTTP_ROUTE]:
279-
anyRequest.routeOptions?.config?.url || request.routerPath,
281+
[SemanticAttributes.HTTP_ROUTE]: anyRequest.routeOptions
282+
? anyRequest.routeOptions.url // since [email protected]
283+
: request.routerPath,
280284
};
281285
if (handlerName) {
282286
spanAttributes[AttributeNames.FASTIFY_NAME] = handlerName;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// Use fastify from an ES module:
18+
// node --experimental-loader=@opentelemetry/instrumentation/hook.mjs use-fastify.mjs
19+
20+
import { trace } from '@opentelemetry/api';
21+
import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils';
22+
23+
import { FastifyInstrumentation } from '../../build/src/index.js';
24+
25+
const sdk = createTestNodeSdk({
26+
serviceName: 'use-fastify',
27+
instrumentations: [
28+
new FastifyInstrumentation()
29+
]
30+
})
31+
sdk.start();
32+
33+
import Fastify from 'fastify';
34+
import http from 'http';
35+
36+
// Start a fastify server.
37+
const app = Fastify();
38+
app.get('/a-route', function aRoute(_request, reply) {
39+
reply.send({ hello: 'world' });
40+
})
41+
const addr = await app.listen({ port: 0 });
42+
43+
// Make a single request to it.
44+
await new Promise(resolve => {
45+
http.get(addr + '/a-route', (res) => {
46+
res.resume();
47+
res.on('end', () => {
48+
resolve();
49+
});
50+
})
51+
})
52+
53+
await app.close();
54+
await sdk.shutdown();

plugins/node/opentelemetry-instrumentation-fastify/test/instrumentation.test.ts

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,21 @@ import {
2525
SimpleSpanProcessor,
2626
} from '@opentelemetry/sdk-trace-base';
2727
import { Span } from '@opentelemetry/api';
28+
import {
29+
getPackageVersion,
30+
runTestFixture,
31+
TestCollector,
32+
} from '@opentelemetry/contrib-test-utils';
33+
import * as semver from 'semver';
2834
import * as http from 'http';
2935
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
3036
import { AttributeNames, FastifyInstrumentation } from '../src';
3137
import { FastifyRequestInfo } from '../src/types';
3238

3339
const URL = require('url').URL;
3440

41+
const fastifyVersion = getPackageVersion('fastify');
42+
3543
const httpRequest = {
3644
get: (options: http.ClientRequestArgs | string) => {
3745
return new Promise((resolve, reject) => {
@@ -183,6 +191,23 @@ describe('fastify', () => {
183191
assert.strictEqual(span.parentSpanId, baseSpan.spanContext().spanId);
184192
});
185193

194+
it('should generate span for 404 request', async () => {
195+
await startServer();
196+
await httpRequest.get(`http://localhost:${PORT}/no-such-route`);
197+
198+
const spans = memoryExporter.getFinishedSpans();
199+
assert.strictEqual(spans.length, 5);
200+
const span = spans[2];
201+
assert.deepStrictEqual(span.attributes, {
202+
'fastify.name': 'basic404',
203+
'fastify.type': 'request_handler',
204+
'plugin.name': 'fastify -> @fastify/express',
205+
});
206+
assert.strictEqual(span.name, 'request handler - basic404');
207+
const baseSpan = spans[1];
208+
assert.strictEqual(span.parentSpanId, baseSpan.spanContext().spanId);
209+
});
210+
186211
describe('when subsystem is registered', () => {
187212
beforeEach(async () => {
188213
httpInstrumentation.enable();
@@ -424,12 +449,17 @@ describe('fastify', () => {
424449
await startServer();
425450
});
426451

427-
it('preClose is not instrumented', async () => {
428-
app.addHook('preClose', () => {
429-
assertRootContextActive();
430-
});
452+
it('preClose is not instrumented', async function () {
453+
// 'preClose' was added in [email protected].
454+
if (semver.lt(fastifyVersion, '4.16.0')) {
455+
this.skip();
456+
} else {
457+
app.addHook('preClose', () => {
458+
assertRootContextActive();
459+
});
431460

432-
await startServer();
461+
await startServer();
462+
}
433463
});
434464

435465
it('onClose is not instrumented', async () => {
@@ -507,4 +537,30 @@ describe('fastify', () => {
507537
});
508538
});
509539
});
540+
541+
it('should work with ESM usage', async () => {
542+
await runTestFixture({
543+
cwd: __dirname,
544+
argv: ['fixtures/use-fastify.mjs'],
545+
env: {
546+
NODE_OPTIONS:
547+
'--experimental-loader=@opentelemetry/instrumentation/hook.mjs',
548+
NODE_NO_WARNINGS: '1',
549+
},
550+
checkResult: (err, stdout, stderr) => {
551+
assert.ifError(err);
552+
},
553+
checkCollector: (collector: TestCollector) => {
554+
const spans = collector.sortedSpans;
555+
assert.strictEqual(spans.length, 1);
556+
assert.strictEqual(spans[0].name, 'request handler - aRoute');
557+
assert.strictEqual(
558+
spans[0].attributes.filter(a => a.key === 'plugin.name')[0]?.value
559+
?.stringValue,
560+
'fastify',
561+
'attribute plugin.name'
562+
);
563+
},
564+
});
565+
});
510566
});

0 commit comments

Comments
 (0)