Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fastify): add requestHook support #1255

Merged
merged 5 commits into from
Nov 6, 2022
Merged
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
22 changes: 22 additions & 0 deletions plugins/node/opentelemetry-instrumentation-fastify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <https://opentelemetry.io/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
*/

export * from './enums/AttributeNames';
export * from './types';
export * from './instrumentation';
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
import { getRPCMetadata, RPCType } from '@opentelemetry/core';
import {
InstrumentationBase,
InstrumentationConfig,
InstrumentationNodeModuleDefinition,
safeExecuteInTheMiddle,
} from '@opentelemetry/instrumentation';
Expand All @@ -39,6 +38,7 @@ import {
FastifyTypes,
} from './enums/AttributeNames';
import type { HandlerOriginal, PluginFastifyReply } from './internal-types';
import type { FastifyInstrumentationConfig } from './types';
import {
endSpan,
safeExecuteInTheMiddleMaybePromise,
Expand All @@ -50,14 +50,22 @@ 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,
Object.assign({}, config)
);
}

override setConfig(config: FastifyInstrumentationConfig = {}) {
this._config = Object.assign({}, config);
}

override getConfig(): FastifyInstrumentationConfig {
return this._config as FastifyInstrumentationConfig;
}

init() {
return [
new InstrumentationNodeModuleDefinition<any>(
Expand Down Expand Up @@ -271,6 +279,19 @@ export class FastifyInstrumentation extends InstrumentationBase {
spanName,
spanAttributes
);

if (instrumentation.getConfig().requestHook) {
blumamir marked this conversation as resolved.
Show resolved Hide resolved
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();
});
Expand Down
39 changes: 39 additions & 0 deletions plugins/node/opentelemetry-instrumentation-fastify/src/types.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ 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';
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;

Expand Down Expand Up @@ -426,5 +428,71 @@ describe('fastify', () => {
await startServer();
});
});

describe('using requestHook in config', () => {
blumamir marked this conversation as resolved.
Show resolved Hide resolved
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',
});
});
});
});
});