-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathfastify.ts
160 lines (141 loc) · 4.91 KB
/
fastify.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify';
import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
captureException,
defineIntegration,
getClient,
getIsolationScope,
spanToJSON,
} from '@sentry/core';
import type { IntegrationFn, Span } from '@sentry/types';
import { generateInstrumentOnce } from '../../otel/instrument';
import { ensureIsWrapped } from '../../utils/ensureIsWrapped';
// We inline the types we care about here
interface Fastify {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
register: (plugin: any) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
addHook: (hook: string, handler: (request: any, reply: any, error: Error) => void) => void;
}
/**
* Minimal request type containing properties around route information.
* Works for Fastify 3, 4 and presumably 5.
*/
interface FastifyRequestRouteInfo {
// since [email protected]
routeOptions?: {
url?: string;
method?: string;
};
routerPath?: string;
}
const INTEGRATION_NAME = 'Fastify';
export const instrumentFastify = generateInstrumentOnce(
INTEGRATION_NAME,
() =>
new FastifyInstrumentation({
requestHook(span) {
addFastifySpanAttributes(span);
},
}),
);
const _fastifyIntegration = (() => {
return {
name: INTEGRATION_NAME,
setupOnce() {
instrumentFastify();
},
};
}) satisfies IntegrationFn;
/**
* Adds Sentry tracing instrumentation for [Fastify](https://fastify.dev/).
*
* If you also want to capture errors, you need to call `setupFastifyErrorHandler(app)` after you set up your Fastify server.
*
* For more information, see the [fastify documentation](https://docs.sentry.io/platforms/javascript/guides/fastify/).
*
* @example
* ```javascript
* const Sentry = require('@sentry/node');
*
* Sentry.init({
* integrations: [Sentry.fastifyIntegration()],
* })
* ```
*/
export const fastifyIntegration = defineIntegration(_fastifyIntegration);
/**
* Add an Fastify error handler to capture errors to Sentry.
*
* @param fastify The Fastify instance to which to add the error handler
*
* @example
* ```javascript
* const Sentry = require('@sentry/node');
* const Fastify = require("fastify");
*
* const app = Fastify();
*
* Sentry.setupFastifyErrorHandler(app);
*
* // Add your routes, etc.
*
* app.listen({ port: 3000 });
* ```
*/
export function setupFastifyErrorHandler(fastify: Fastify): void {
const plugin = Object.assign(
function (fastify: Fastify, _options: unknown, done: () => void): void {
fastify.addHook('onError', async (_request, _reply, error) => {
captureException(error);
});
// registering `onRequest` hook here instead of using Otel `onRequest` callback b/c `onRequest` hook
// is ironically called in the fastify `preHandler` hook which is called later in the lifecycle:
// https://fastify.dev/docs/latest/Reference/Lifecycle/
fastify.addHook('onRequest', async (request, _reply) => {
const reqWithRouteInfo = request as FastifyRequestRouteInfo;
// Taken from Otel Fastify instrumentation:
// https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts#L94-L96
const routeName = reqWithRouteInfo.routeOptions?.url || reqWithRouteInfo.routerPath;
const method = reqWithRouteInfo.routeOptions?.method || 'GET';
getIsolationScope().setTransactionName(`${method} ${routeName}`);
});
done();
},
{
[Symbol.for('skip-override')]: true,
[Symbol.for('fastify.display-name')]: 'sentry-fastify-error-handler',
},
);
fastify.register(plugin);
// Sadly, middleware spans do not go through `requestHook`, so we handle those here
// We register this hook in this method, because if we register it in the integration `setup`,
// it would always run even for users that are not even using fastify
const client = getClient();
if (client) {
client.on('spanStart', span => {
addFastifySpanAttributes(span);
});
}
ensureIsWrapped(fastify.addHook, 'fastify');
}
function addFastifySpanAttributes(span: Span): void {
const attributes = spanToJSON(span).data || {};
// this is one of: middleware, request_handler
const type = attributes['fastify.type'];
// If this is already set, or we have no fastify span, no need to process again...
if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] || !type) {
return;
}
span.setAttributes({
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.fastify',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.fastify`,
});
// Also update the name, we don't need to "middleware - " prefix
const name = attributes['fastify.name'] || attributes['plugin.name'] || attributes['hook.name'];
if (typeof name === 'string') {
// Also remove `fastify -> ` prefix
span.updateName(name.replace(/^fastify -> /, ''));
}
}