Skip to content

Commit ee28dc2

Browse files
committed
ref
1 parent 43866e7 commit ee28dc2

3 files changed

Lines changed: 43 additions & 54 deletions

File tree

packages/server-utils/src/integrations/tracing-channel/express/index.ts

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,19 @@
1-
import * as diagnosticsChannel from 'node:diagnostics_channel';
21
import type { IntegrationFn } from '@sentry/core';
3-
import { defineIntegration, waitForTracingChannelBinding } from '@sentry/core';
2+
import { defineIntegration } from '@sentry/core';
43
import type { ExpressIntegrationOptions } from './types';
54
import { instrumentExpress } from './instrumentation';
65
import { expressModuleNames } from '../../../orchestrion/config/express';
7-
import { instrumentOrchestrion, instrumentOrchestrionLazy } from '../../../orchestrion/instrumentation';
6+
import { invokeOrchestrionInstrumentation } from '../../../orchestrion/instrumentation';
87

98
// NOTE: this uses the same name as the OTel integration by design.
109
// When enabled, the OTel 'Express' integration is omitted from the default set.
1110
const INTEGRATION_NAME = 'Express' as const;
1211

1312
const _expressChannelIntegration = ((options: ExpressIntegrationOptions = {}) => {
14-
// `tracingChannel` is unavailable before Node 18.19 so do nothing in that case.
15-
if (!diagnosticsChannel.tracingChannel) {
16-
return {
17-
name: INTEGRATION_NAME,
18-
};
19-
}
20-
21-
const instrumentFn = () => {
22-
instrumentExpress(options, diagnosticsChannel.tracingChannel);
23-
};
24-
2513
return {
2614
name: INTEGRATION_NAME,
27-
setupOnce() {
28-
waitForTracingChannelBinding(() => {
29-
instrumentOrchestrion(expressModuleNames, instrumentFn);
30-
});
31-
},
3215
setup(client) {
33-
instrumentOrchestrionLazy(client, expressModuleNames, instrumentFn);
16+
invokeOrchestrionInstrumentation(client, expressModuleNames, instrumentExpress, [options]);
3417
},
3518
};
3619
}) satisfies IntegrationFn;

packages/server-utils/src/integrations/tracing-channel/express/instrumentation.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type * as diagnosticsChannel from 'node:diagnostics_channel';
1+
import * as diagnosticsChannel from 'node:diagnostics_channel';
22
import { HTTP_ROUTE } from '@sentry/conventions/attributes';
33
import type { Span } from '@sentry/core';
44
import {
@@ -45,16 +45,8 @@ const ATTR_EXPRESS_TYPE = 'express.type';
4545

4646
const NOOP = (): void => {};
4747

48-
let _isInstrumented = false;
49-
50-
export function instrumentExpress(
51-
options: ExpressIntegrationOptions,
52-
tracingChannel: typeof diagnosticsChannel.tracingChannel,
53-
): void {
54-
if (_isInstrumented) {
55-
return;
56-
}
57-
_isInstrumented = true;
48+
export function instrumentExpress(options: ExpressIntegrationOptions): void {
49+
const tracingChannel = diagnosticsChannel.tracingChannel;
5850

5951
// Record each layer's registered path *pattern* as it is registered, so the
6052
// matched route can be reconstructed with its parameters intact at request

packages/server-utils/src/orchestrion/instrumentation.ts

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,58 @@
11
import type { Client } from '@sentry/core';
2-
import { addNonEnumerableProperty } from '@sentry/core';
2+
import { addNonEnumerableProperty, waitForTracingChannelBinding } from '@sentry/core';
33
import { getOrchestrionInjectedModules } from './detect';
4+
import * as diagnosticsChannel from 'node:diagnostics_channel';
45

56
const INSTRUMENTATION_FN_SYMBOL = Symbol.for('InstrumentationFn');
6-
type InstrumentationFn = (() => void) & { [INSTRUMENTATION_FN_SYMBOL]?: boolean };
7+
// oxlint-disable-next-line typescript/no-explicit-any
8+
type InstrumentationFn = ((...args: any[]) => void) & { [INSTRUMENTATION_FN_SYMBOL]?: boolean };
79

810
/**
9-
* Instrument the provided callback to run when one of the provided module names has been orchestrion-injected already.
10-
* Make sure to pass the same callback instance to this as is passed to `instrumentOrchestrionLazy`.
11+
* Run the provided instrumentation callback when one of the provided module names is orchestrion-injected.
12+
* If it is already injected, it will invoce the callback immediately (e.g. when build-time injection is used).
13+
* If runtime injection is used, it may invoke the callback at a later point in time, when the injection actually happens.
14+
* The callback will never be invoked more than once.
1115
*/
12-
export function instrumentOrchestrion(moduleNames: string[], callback: InstrumentationFn) {
16+
export function invokeOrchestrionInstrumentation<Callback extends InstrumentationFn>(
17+
client: Client,
18+
moduleNames: string[],
19+
callback: Callback,
20+
args: Parameters<Callback>,
21+
) {
22+
// `tracingChannel` is unavailable before Node 18.19 so do nothing in that case.
23+
if (!diagnosticsChannel.tracingChannel) {
24+
return;
25+
}
26+
27+
// If already injected, skip
1328
if (hasBeenInjected(callback)) {
1429
return;
1530
}
1631

17-
const modules = getOrchestrionInjectedModules();
18-
if (moduleNames.some(name => modules.includes(name))) {
19-
callback();
32+
const instrumentationFn = () => {
2033
markAsInjected(callback);
21-
}
22-
}
2334

24-
/**
25-
* Run the provided instrumentation callback when one of the provided module names has been orchestrion-injected at runtime.
26-
* This listens to the `orchestrion.module-runtime-injected` event and runs the callback when the event is emitted.
27-
* Make sure to pass the same callback instance to this as is passed to `instrumentOrchestrion`.
28-
*/
29-
export function instrumentOrchestrionLazy(client: Client, moduleNames: string[], callback: InstrumentationFn) {
30-
if (hasBeenInjected(callback)) {
35+
waitForTracingChannelBinding(() => {
36+
callback(...args);
37+
});
38+
};
39+
40+
// First, check if the modules have been injected already
41+
const modules = getOrchestrionInjectedModules();
42+
if (moduleNames.some(name => modules.includes(name))) {
43+
instrumentationFn();
3144
return;
3245
}
3346

47+
// Then, register a listener for the `orchestrion.module-runtime-injected` event
3448
const cleanup = client.on('orchestrion.module-runtime-injected', (moduleName: string) => {
49+
if (hasBeenInjected(callback)) {
50+
cleanup();
51+
return;
52+
}
53+
3554
if (moduleNames.includes(moduleName)) {
36-
if (hasBeenInjected(callback)) {
37-
cleanup();
38-
return;
39-
}
40-
callback();
41-
markAsInjected(callback);
55+
instrumentationFn();
4256
cleanup();
4357
}
4458
});

0 commit comments

Comments
 (0)