diff --git a/plugins/node/opentelemetry-instrumentation-fastify/README.md b/plugins/node/opentelemetry-instrumentation-fastify/README.md
index 89c4a6e1d0..3e74f96b8b 100644
--- a/plugins/node/opentelemetry-instrumentation-fastify/README.md
+++ b/plugins/node/opentelemetry-instrumentation-fastify/README.md
@@ -45,6 +45,28 @@ registerInstrumentations({
See [examples/fastify](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/examples/fastify) for a short example.
+## Fastify Instrumentation Options
+
+| Options | Type | Example | Description |
+| `requestHook` | `FastifyCustomAttributeFunction` | `(span, requestInfo) => {}` | Function for adding custom attributes to Fastify requests. Receives params: `Span, FastifyRequestInfo`. |
+
+### Using `requestHook`
+
+Instrumentation configuration accepts a custom "hook" function which will be called for every instrumented Fastify request. Custom attributes can be set on the span or run any custom logic per request.
+
+```javascript
+import { FastifyInstrumentation } from "@opentelemetry/instrumentation-fastify"
+
+const fastifyInstrumentation = new FastifyInstrumentation({
+ requestHook: function (span: Span, info: FastifyRequestInfo) {
+ span.setAttribute(
+ 'http.method',
+ info.request.method,
+ )
+ }
+});
+```
+
## Useful links
- For more information on OpenTelemetry, visit:
diff --git a/plugins/node/opentelemetry-instrumentation-fastify/package.json b/plugins/node/opentelemetry-instrumentation-fastify/package.json
index 915fcda542..71b0a1b843 100644
--- a/plugins/node/opentelemetry-instrumentation-fastify/package.json
+++ b/plugins/node/opentelemetry-instrumentation-fastify/package.json
@@ -54,8 +54,8 @@
"@types/express": "4.17.13",
"@types/mocha": "7.0.2",
"@types/node": "18.11.7",
- "gts": "3.1.0",
"fastify": "^4.5.3",
+ "gts": "3.1.0",
"mocha": "7.2.0",
"nyc": "15.1.0",
"rimraf": "3.0.2",
diff --git a/plugins/node/opentelemetry-instrumentation-fastify/src/index.ts b/plugins/node/opentelemetry-instrumentation-fastify/src/index.ts
index 34b600dd0c..958c8fef60 100644
--- a/plugins/node/opentelemetry-instrumentation-fastify/src/index.ts
+++ b/plugins/node/opentelemetry-instrumentation-fastify/src/index.ts
@@ -15,4 +15,5 @@
*/
export * from './enums/AttributeNames';
+export * from './types';
export * from './instrumentation';
diff --git a/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts b/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts
index 4f6df78268..94b4feb035 100644
--- a/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts
+++ b/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts
@@ -23,7 +23,6 @@ import {
import { getRPCMetadata, RPCType } from '@opentelemetry/core';
import {
InstrumentationBase,
- InstrumentationConfig,
InstrumentationNodeModuleDefinition,
safeExecuteInTheMiddle,
} from '@opentelemetry/instrumentation';
@@ -39,6 +38,7 @@ import {
FastifyTypes,
} from './enums/AttributeNames';
import type { HandlerOriginal, PluginFastifyReply } from './internal-types';
+import type { FastifyInstrumentationConfig } from './types';
import {
endSpan,
safeExecuteInTheMiddleMaybePromise,
@@ -50,7 +50,7 @@ export const ANONYMOUS_NAME = 'anonymous';
/** Fastify instrumentation for OpenTelemetry */
export class FastifyInstrumentation extends InstrumentationBase {
- constructor(config: InstrumentationConfig = {}) {
+ constructor(config: FastifyInstrumentationConfig = {}) {
super(
'@opentelemetry/instrumentation-fastify',
VERSION,
@@ -58,6 +58,14 @@ export class FastifyInstrumentation extends InstrumentationBase {
);
}
+ override setConfig(config: FastifyInstrumentationConfig = {}) {
+ this._config = Object.assign({}, config);
+ }
+
+ override getConfig(): FastifyInstrumentationConfig {
+ return this._config as FastifyInstrumentationConfig;
+ }
+
init() {
return [
new InstrumentationNodeModuleDefinition(
@@ -271,6 +279,19 @@ export class FastifyInstrumentation extends InstrumentationBase {
spanName,
spanAttributes
);
+
+ if (instrumentation.getConfig().requestHook) {
+ safeExecuteInTheMiddle(
+ () => instrumentation.getConfig().requestHook!(span, { request }),
+ e => {
+ if (e) {
+ instrumentation._diag.error('request hook failed', e);
+ }
+ },
+ true
+ );
+ }
+
return context.with(trace.setSpan(context.active(), span), () => {
done();
});
diff --git a/plugins/node/opentelemetry-instrumentation-fastify/src/types.ts b/plugins/node/opentelemetry-instrumentation-fastify/src/types.ts
new file mode 100644
index 0000000000..2deadc0598
--- /dev/null
+++ b/plugins/node/opentelemetry-instrumentation-fastify/src/types.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Span } from '@opentelemetry/api';
+import { InstrumentationConfig } from '@opentelemetry/instrumentation';
+
+export interface FastifyRequestInfo {
+ request: any; // FastifyRequest object from fastify package
+}
+
+/**
+ * Function that can be used to add custom attributes to the current span
+ * @param span - The Fastify handler span.
+ * @param info - The Fastify request info object.
+ */
+export interface FastifyCustomAttributeFunction {
+ (span: Span, info: FastifyRequestInfo): void;
+}
+
+/**
+ * Options available for the Fastify Instrumentation
+ */
+export interface FastifyInstrumentationConfig extends InstrumentationConfig {
+ /** Function for adding custom attributes to each handler span */
+ requestHook?: FastifyCustomAttributeFunction;
+}
diff --git a/plugins/node/opentelemetry-instrumentation-fastify/test/instrumentation.test.ts b/plugins/node/opentelemetry-instrumentation-fastify/test/instrumentation.test.ts
index aa17f3b246..d80595728a 100644
--- a/plugins/node/opentelemetry-instrumentation-fastify/test/instrumentation.test.ts
+++ b/plugins/node/opentelemetry-instrumentation-fastify/test/instrumentation.test.ts
@@ -24,6 +24,7 @@ import {
ReadableSpan,
SimpleSpanProcessor,
} from '@opentelemetry/sdk-trace-base';
+import { Span } from '@opentelemetry/api';
import { HookHandlerDoneFunction } from 'fastify/types/hooks';
import { FastifyReply } from 'fastify/types/reply';
import { FastifyRequest } from 'fastify/types/request';
@@ -31,6 +32,7 @@ import * as http from 'http';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { ANONYMOUS_NAME } from '../src/instrumentation';
import { AttributeNames, FastifyInstrumentation } from '../src';
+import { FastifyRequestInfo } from '../src/types';
const URL = require('url').URL;
@@ -426,5 +428,71 @@ describe('fastify', () => {
await startServer();
});
});
+
+ describe('using requestHook in config', () => {
+ it('calls requestHook provided function when set in config', async () => {
+ const requestHook = (span: Span, info: FastifyRequestInfo) => {
+ span.setAttribute(
+ SemanticAttributes.HTTP_METHOD,
+ info.request.method
+ );
+ };
+
+ instrumentation.setConfig({
+ ...instrumentation.getConfig(),
+ requestHook,
+ });
+
+ app.get('/test', (req, res) => {
+ res.send('OK');
+ });
+
+ await startServer();
+ await httpRequest.get(`http://localhost:${PORT}/test`);
+
+ const spans = memoryExporter.getFinishedSpans();
+ assert.strictEqual(spans.length, 5);
+ const span = spans[3];
+ assert.deepStrictEqual(span.attributes, {
+ 'fastify.type': 'request_handler',
+ 'plugin.name': 'fastify -> @fastify/express',
+ [SemanticAttributes.HTTP_ROUTE]: '/test',
+ [SemanticAttributes.HTTP_METHOD]: 'GET',
+ });
+ });
+
+ it('does not propagate an error from a requestHook that throws exception', async () => {
+ const requestHook = (span: Span, info: FastifyRequestInfo) => {
+ span.setAttribute(
+ SemanticAttributes.HTTP_METHOD,
+ info.request.method
+ );
+
+ throw Error('error thrown in requestHook');
+ };
+
+ instrumentation.setConfig({
+ ...instrumentation.getConfig(),
+ requestHook,
+ });
+
+ app.get('/test', (req, res) => {
+ res.send('OK');
+ });
+
+ await startServer();
+ await httpRequest.get(`http://localhost:${PORT}/test`);
+
+ const spans = memoryExporter.getFinishedSpans();
+ assert.strictEqual(spans.length, 5);
+ const span = spans[3];
+ assert.deepStrictEqual(span.attributes, {
+ 'fastify.type': 'request_handler',
+ 'plugin.name': 'fastify -> @fastify/express',
+ [SemanticAttributes.HTTP_ROUTE]: '/test',
+ [SemanticAttributes.HTTP_METHOD]: 'GET',
+ });
+ });
+ });
});
});