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 1 commit
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, request) => {}` | Function for adding custom attributes to Fastify layers. Receives params: `Span, FastifyRequest`. |

### Using `requestHook`

Instrumentation configuration accepts a custom "hook" function which will be called for every instrumented Fastify layer involved in a request. Custom attributes can be set on the span or run any custom logic per layer.

```javascript
import { FastifyInstrumentation } from "@opentelemetry/instrumentation-fastify"

const fastifyInstrumentation = new FastifyInstrumentation({
requestHook: function (span: Span, request: FastifyRequest) {
span.setAttribute(
'http.method',
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,11 +54,13 @@
"@types/express": "4.17.13",
"@types/mocha": "7.0.2",
"@types/node": "16.11.21",
"gts": "3.1.0",
"@types/sinon": "10.0.13",
"fastify": "^4.5.3",
"gts": "3.1.0",
"mocha": "7.2.0",
"nyc": "15.1.0",
"rimraf": "3.0.2",
"sinon": "14.0.1",
"ts-mocha": "10.0.0",
"typescript": "4.3.5"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

import {
context,
diag,
SpanAttributes,
SpanStatusCode,
trace,
} from '@opentelemetry/api';
import { getRPCMetadata, RPCType } from '@opentelemetry/core';
import {
InstrumentationBase,
InstrumentationConfig,
InstrumentationNodeModuleDefinition,
safeExecuteInTheMiddle,
} from '@opentelemetry/instrumentation';
Expand All @@ -38,7 +38,11 @@ import {
FastifyNames,
FastifyTypes,
} from './enums/AttributeNames';
import type { HandlerOriginal, PluginFastifyReply } from './types';
import type {
HandlerOriginal,
PluginFastifyReply,
FastifyInstrumentationConfig,
} from './types';
import {
endSpan,
safeExecuteInTheMiddleMaybePromise,
Expand All @@ -50,14 +54,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 +283,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) {
diag.error('fastify instrumentation: request hook failed', e);
blumamir marked this conversation as resolved.
Show resolved Hide resolved
}
},
true
);
}

return context.with(trace.setSpan(context.active(), span), () => {
done();
});
Expand Down
19 changes: 19 additions & 0 deletions plugins/node/opentelemetry-instrumentation-fastify/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,28 @@
import { Span } from '@opentelemetry/api';
import type { FastifyReply } from 'fastify';
import { spanRequestSymbol } from './constants';
import { InstrumentationConfig } from '@opentelemetry/instrumentation';
import type { FastifyRequest } from 'fastify/types/request';
blumamir marked this conversation as resolved.
Show resolved Hide resolved

export type HandlerOriginal = (() => Promise<unknown>) & (() => void);

export type PluginFastifyReply = FastifyReply & {
[spanRequestSymbol]?: Span[];
};

/**
* Function that can be used to add custom attributes to the current span
* @param span - The Fastify layer span.
* @param request - The Fastify request object.
*/
export interface FastifyCustomAttributeFunction {
(span: Span, request: FastifyRequest): void;
blumamir marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Options available for the Fastify Instrumentation
*/
export interface FastifyInstrumentationConfig extends InstrumentationConfig {
/** Function for adding custom attributes to each layer 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 * as sinon from 'sinon';

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

Expand Down Expand Up @@ -426,5 +428,35 @@ 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 = sinon.spy((span: Span, request: FastifyRequest) => {
blumamir marked this conversation as resolved.
Show resolved Hide resolved
span.setAttribute('http.method', 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',
'http.method': 'GET',
});
});
});
});
});