Skip to content

Commit 97b077a

Browse files
authored
feat(opentelemetry): Add addOpenTelemetryInstrumentation (#11667)
Adds `addOpenTelemetryInstrumentation`, a helper that can be used to dynamically register OpenTelemetry instrumentation. This helps unblock work on #11548 The helper itself is quite small: ```ts export function addOpenTelemetryInstrumentation( instrumentation: InstrumentationOption | InstrumentationOption[], ): void { registerInstrumentations({ instrumentations: Array.isArray(instrumentation) ? instrumentation : [instrumentation], }); } ``` and is designed to accept either a standalone instrumentation or an array of instrumentations. This gives users a ton of flexibility into usage!
1 parent bc43dbf commit 97b077a

File tree

25 files changed

+152
-158
lines changed

25 files changed

+152
-158
lines changed

packages/astro/src/index.server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export {
8787
hapiIntegration,
8888
setupHapiErrorHandler,
8989
spotlightIntegration,
90+
addOpenTelemetryInstrumentation,
9091
} from '@sentry/node';
9192

9293
// We can still leave this for the carrier init and type exports

packages/aws-serverless/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export {
9696
spanToJSON,
9797
spanToTraceHeader,
9898
trpcMiddleware,
99+
addOpenTelemetryInstrumentation,
99100
} from '@sentry/node';
100101

101102
export {

packages/bun/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export {
117117
spanToJSON,
118118
spanToTraceHeader,
119119
trpcMiddleware,
120+
addOpenTelemetryInstrumentation,
120121
} from '@sentry/node';
121122

122123
export {

packages/google-cloud-serverless/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export {
9696
spanToJSON,
9797
spanToTraceHeader,
9898
trpcMiddleware,
99+
addOpenTelemetryInstrumentation,
99100
} from '@sentry/node';
100101

101102
export {

packages/node/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type { NodeOptions } from './types';
3939
export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/utils';
4040

4141
export {
42+
addOpenTelemetryInstrumentation,
4243
// These are custom variants that need to be used instead of the core one
4344
// As they have slightly different implementations
4445
continueTrace,

packages/node/src/integrations/http.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { ClientRequest, IncomingMessage, ServerResponse } from 'http';
22
import type { Span } from '@opentelemetry/api';
33
import { SpanKind } from '@opentelemetry/api';
4-
import { registerInstrumentations } from '@opentelemetry/instrumentation';
54
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
5+
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
66

77
import {
88
addBreadcrumb,
@@ -52,7 +52,7 @@ const _httpIntegration = ((options: HttpOptions = {}) => {
5252
return {
5353
name: 'Http',
5454
setupOnce() {
55-
const instrumentations = [
55+
addOpenTelemetryInstrumentation(
5656
new HttpInstrumentation({
5757
ignoreOutgoingRequestHook: request => {
5858
const url = getRequestUrl(request);
@@ -141,11 +141,7 @@ const _httpIntegration = ((options: HttpOptions = {}) => {
141141
}
142142
},
143143
}),
144-
];
145-
146-
registerInstrumentations({
147-
instrumentations,
148-
});
144+
);
149145
},
150146
};
151147
}) satisfies IntegrationFn;

packages/node/src/integrations/node-fetch.ts

+20-23
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import type { Span } from '@opentelemetry/api';
22
import { SpanKind } from '@opentelemetry/api';
3-
import type { Instrumentation } from '@opentelemetry/instrumentation';
4-
import { registerInstrumentations } from '@opentelemetry/instrumentation';
53
import { addBreadcrumb, defineIntegration } from '@sentry/core';
4+
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
65
import { getRequestSpanData, getSpanKind } from '@sentry/opentelemetry';
76
import type { IntegrationFn } from '@sentry/types';
87
import { logger } from '@sentry/utils';
98
import { DEBUG_BUILD } from '../debug-build';
109
import { NODE_MAJOR } from '../nodeVersion';
1110

11+
import type { FetchInstrumentation } from 'opentelemetry-instrumentation-fetch-node';
12+
1213
import { addOriginToSpan } from '../utils/addOriginToSpan';
1314

1415
interface NodeFetchOptions {
@@ -29,7 +30,7 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
2930
const _breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
3031
const _ignoreOutgoingRequests = options.ignoreOutgoingRequests;
3132

32-
async function getInstrumentation(): Promise<[Instrumentation] | void> {
33+
async function getInstrumentation(): Promise<FetchInstrumentation | void> {
3334
// Only add NodeFetch if Node >= 18, as previous versions do not support it
3435
if (NODE_MAJOR < 18) {
3536
DEBUG_BUILD && logger.log('NodeFetch is not supported on Node < 18, skipping instrumentation...');
@@ -38,22 +39,20 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
3839

3940
try {
4041
const pkg = await import('opentelemetry-instrumentation-fetch-node');
41-
return [
42-
new pkg.FetchInstrumentation({
43-
ignoreRequestHook: (request: { origin?: string }) => {
44-
const url = request.origin;
45-
return _ignoreOutgoingRequests && url && _ignoreOutgoingRequests(url);
46-
},
47-
onRequest: ({ span }: { span: Span }) => {
48-
_updateSpan(span);
42+
return new pkg.FetchInstrumentation({
43+
ignoreRequestHook: (request: { origin?: string }) => {
44+
const url = request.origin;
45+
return _ignoreOutgoingRequests && url && _ignoreOutgoingRequests(url);
46+
},
47+
onRequest: ({ span }: { span: Span }) => {
48+
_updateSpan(span);
4949

50-
if (_breadcrumbs) {
51-
_addRequestBreadcrumb(span);
52-
}
53-
},
54-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
55-
} as any),
56-
];
50+
if (_breadcrumbs) {
51+
_addRequestBreadcrumb(span);
52+
}
53+
},
54+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
55+
} as any);
5756
} catch (error) {
5857
// Could not load instrumentation
5958
DEBUG_BUILD && logger.log('Could not load NodeFetch instrumentation.');
@@ -64,11 +63,9 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => {
6463
name: 'NodeFetch',
6564
setupOnce() {
6665
// eslint-disable-next-line @typescript-eslint/no-floating-promises
67-
getInstrumentation().then(instrumentations => {
68-
if (instrumentations) {
69-
registerInstrumentations({
70-
instrumentations,
71-
});
66+
getInstrumentation().then(instrumentation => {
67+
if (instrumentation) {
68+
addOpenTelemetryInstrumentation(instrumentation);
7269
}
7370
});
7471
},

packages/node/src/integrations/tracing/connect.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
import { registerInstrumentations } from '@opentelemetry/instrumentation';
21
import { ConnectInstrumentation } from '@opentelemetry/instrumentation-connect';
32
import { captureException, defineIntegration } from '@sentry/core';
3+
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
44
import type { IntegrationFn } from '@sentry/types';
55

66
type ConnectApp = {
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
78
use: (middleware: any) => void;
89
};
910

1011
const _connectIntegration = (() => {
1112
return {
1213
name: 'Connect',
1314
setupOnce() {
14-
registerInstrumentations({
15-
instrumentations: [new ConnectInstrumentation({})],
16-
});
15+
addOpenTelemetryInstrumentation(new ConnectInstrumentation({}));
1716
},
1817
};
1918
}) satisfies IntegrationFn;
2019

2120
export const connectIntegration = defineIntegration(_connectIntegration);
2221

22+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2323
function connectErrorMiddleware(err: any, req: any, res: any, next: any): void {
2424
captureException(err);
2525
next(err);

packages/node/src/integrations/tracing/express.ts

+21-23
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type * as http from 'http';
2-
import { registerInstrumentations } from '@opentelemetry/instrumentation';
32
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
43
import { defineIntegration, getDefaultIsolationScope } from '@sentry/core';
54
import { captureException, getClient, getIsolationScope } from '@sentry/core';
5+
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
66
import type { IntegrationFn } from '@sentry/types';
77

88
import { logger } from '@sentry/utils';
@@ -14,29 +14,27 @@ const _expressIntegration = (() => {
1414
return {
1515
name: 'Express',
1616
setupOnce() {
17-
registerInstrumentations({
18-
instrumentations: [
19-
new ExpressInstrumentation({
20-
requestHook(span) {
21-
addOriginToSpan(span, 'auto.http.otel.express');
22-
},
23-
spanNameHook(info, defaultName) {
24-
if (getIsolationScope() === getDefaultIsolationScope()) {
25-
DEBUG_BUILD &&
26-
logger.warn('Isolation scope is still default isolation scope - skipping setting transactionName');
27-
return defaultName;
28-
}
29-
if (info.layerType === 'request_handler') {
30-
// type cast b/c Otel unfortunately types info.request as any :(
31-
const req = info.request as { method?: string };
32-
const method = req.method ? req.method.toUpperCase() : 'GET';
33-
getIsolationScope().setTransactionName(`${method} ${info.route}`);
34-
}
17+
addOpenTelemetryInstrumentation(
18+
new ExpressInstrumentation({
19+
requestHook(span) {
20+
addOriginToSpan(span, 'auto.http.otel.express');
21+
},
22+
spanNameHook(info, defaultName) {
23+
if (getIsolationScope() === getDefaultIsolationScope()) {
24+
DEBUG_BUILD &&
25+
logger.warn('Isolation scope is still default isolation scope - skipping setting transactionName');
3526
return defaultName;
36-
},
37-
}),
38-
],
39-
});
27+
}
28+
if (info.layerType === 'request_handler') {
29+
// type cast b/c Otel unfortunately types info.request as any :(
30+
const req = info.request as { method?: string };
31+
const method = req.method ? req.method.toUpperCase() : 'GET';
32+
getIsolationScope().setTransactionName(`${method} ${info.route}`);
33+
}
34+
return defaultName;
35+
},
36+
}),
37+
);
4038
},
4139
};
4240
}) satisfies IntegrationFn;

packages/node/src/integrations/tracing/fastify.ts

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { registerInstrumentations } from '@opentelemetry/instrumentation';
21
import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify';
32
import { captureException, defineIntegration, getIsolationScope } from '@sentry/core';
3+
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
44
import type { IntegrationFn } from '@sentry/types';
55

66
import { addOriginToSpan } from '../../utils/addOriginToSpan';
@@ -9,15 +9,13 @@ const _fastifyIntegration = (() => {
99
return {
1010
name: 'Fastify',
1111
setupOnce() {
12-
registerInstrumentations({
13-
instrumentations: [
14-
new FastifyInstrumentation({
15-
requestHook(span) {
16-
addOriginToSpan(span, 'auto.http.otel.fastify');
17-
},
18-
}),
19-
],
20-
});
12+
addOpenTelemetryInstrumentation(
13+
new FastifyInstrumentation({
14+
requestHook(span) {
15+
addOriginToSpan(span, 'auto.http.otel.fastify');
16+
},
17+
}),
18+
);
2119
},
2220
};
2321
}) satisfies IntegrationFn;

packages/node/src/integrations/tracing/graphql.ts

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { registerInstrumentations } from '@opentelemetry/instrumentation';
21
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
32
import { defineIntegration } from '@sentry/core';
3+
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
44
import type { IntegrationFn } from '@sentry/types';
55

66
import { addOriginToSpan } from '../../utils/addOriginToSpan';
@@ -9,16 +9,14 @@ const _graphqlIntegration = (() => {
99
return {
1010
name: 'Graphql',
1111
setupOnce() {
12-
registerInstrumentations({
13-
instrumentations: [
14-
new GraphQLInstrumentation({
15-
ignoreTrivialResolveSpans: true,
16-
responseHook(span) {
17-
addOriginToSpan(span, 'auto.graphql.otel.graphql');
18-
},
19-
}),
20-
],
21-
});
12+
addOpenTelemetryInstrumentation(
13+
new GraphQLInstrumentation({
14+
ignoreTrivialResolveSpans: true,
15+
responseHook(span) {
16+
addOriginToSpan(span, 'auto.graphql.otel.graphql');
17+
},
18+
}),
19+
);
2220
},
2321
};
2422
}) satisfies IntegrationFn;

packages/node/src/integrations/tracing/hapi/index.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { registerInstrumentations } from '@opentelemetry/instrumentation';
21
import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi';
32
import {
43
SDK_VERSION,
@@ -10,6 +9,7 @@ import {
109
getIsolationScope,
1110
getRootSpan,
1211
} from '@sentry/core';
12+
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
1313
import type { IntegrationFn } from '@sentry/types';
1414
import { logger } from '@sentry/utils';
1515
import { DEBUG_BUILD } from '../../../debug-build';
@@ -19,9 +19,7 @@ const _hapiIntegration = (() => {
1919
return {
2020
name: 'Hapi',
2121
setupOnce() {
22-
registerInstrumentations({
23-
instrumentations: [new HapiInstrumentation()],
24-
});
22+
addOpenTelemetryInstrumentation(new HapiInstrumentation());
2523
},
2624
};
2725
}) satisfies IntegrationFn;

packages/node/src/integrations/tracing/koa.ts

+19-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { registerInstrumentations } from '@opentelemetry/instrumentation';
21
import { KoaInstrumentation } from '@opentelemetry/instrumentation-koa';
32
import { SEMATTRS_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
43
import {
@@ -8,6 +7,7 @@ import {
87
getIsolationScope,
98
spanToJSON,
109
} from '@sentry/core';
10+
import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
1111
import type { IntegrationFn } from '@sentry/types';
1212
import { logger } from '@sentry/utils';
1313
import { DEBUG_BUILD } from '../../debug-build';
@@ -16,31 +16,30 @@ const _koaIntegration = (() => {
1616
return {
1717
name: 'Koa',
1818
setupOnce() {
19-
registerInstrumentations({
20-
instrumentations: [
21-
new KoaInstrumentation({
22-
requestHook(span, info) {
23-
if (getIsolationScope() === getDefaultIsolationScope()) {
24-
DEBUG_BUILD &&
25-
logger.warn('Isolation scope is default isolation scope - skipping setting transactionName');
26-
return;
27-
}
28-
const attributes = spanToJSON(span).data;
29-
const route = attributes && attributes[SEMATTRS_HTTP_ROUTE];
30-
const method = info.context.request.method.toUpperCase() || 'GET';
31-
if (route) {
32-
getIsolationScope().setTransactionName(`${method} ${route}`);
33-
}
34-
},
35-
}),
36-
],
37-
});
19+
addOpenTelemetryInstrumentation(
20+
new KoaInstrumentation({
21+
requestHook(span, info) {
22+
if (getIsolationScope() === getDefaultIsolationScope()) {
23+
DEBUG_BUILD &&
24+
logger.warn('Isolation scope is default isolation scope - skipping setting transactionName');
25+
return;
26+
}
27+
const attributes = spanToJSON(span).data;
28+
const route = attributes && attributes[SEMATTRS_HTTP_ROUTE];
29+
const method = info.context.request.method.toUpperCase() || 'GET';
30+
if (route) {
31+
getIsolationScope().setTransactionName(`${method} ${route}`);
32+
}
33+
},
34+
}),
35+
);
3836
},
3937
};
4038
}) satisfies IntegrationFn;
4139

4240
export const koaIntegration = defineIntegration(_koaIntegration);
4341

42+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4443
export const setupKoaErrorHandler = (app: { use: (arg0: (ctx: any, next: any) => Promise<void>) => void }): void => {
4544
app.use(async (ctx, next) => {
4645
try {

0 commit comments

Comments
 (0)