1- // Automatic istrumentation for Express using OTel
2- import type { InstrumentationConfig } from '@opentelemetry/instrumentation' ;
3- import { InstrumentationBase , InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation' ;
41import { context } from '@opentelemetry/api' ;
52import { getRPCMetadata , RPCType } from '@opentelemetry/core' ;
63
7- import { ensureIsWrapped , generateInstrumentOnce } from '@sentry/node-core' ;
4+ import { ensureIsWrapped , registerModuleWrapper } from '@sentry/node-core' ;
85import {
96 type ExpressIntegrationOptions ,
107 type IntegrationFn ,
118 debug ,
129 patchExpressModule ,
13- SDK_VERSION ,
1410 defineIntegration ,
1511 setupExpressErrorHandler as coreSetupExpressErrorHandler ,
1612 type ExpressHandlerOptions ,
1713} from '@sentry/core' ;
1814export { expressErrorHandler } from '@sentry/core' ;
1915import { DEBUG_BUILD } from '../../debug-build' ;
2016
17+ type ExpressModuleExport = Parameters < typeof patchExpressModule > [ 0 ] ;
18+
2119const INTEGRATION_NAME = 'Express' ;
2220const SUPPORTED_VERSIONS = [ '>=4.0.0 <6' ] ;
2321
@@ -30,47 +28,46 @@ export function setupExpressErrorHandler(
3028 ensureIsWrapped ( app . use , 'express' ) ;
3129}
3230
33- export type ExpressInstrumentationConfig = InstrumentationConfig &
34- Omit < ExpressIntegrationOptions , 'express' | 'onRouteResolved' > ;
35-
36- export const instrumentExpress = generateInstrumentOnce (
37- INTEGRATION_NAME ,
38- ( options ?: ExpressInstrumentationConfig ) => new ExpressInstrumentation ( options ) ,
39- ) ;
31+ export type ExpressInstrumentationConfig = Omit < ExpressIntegrationOptions , 'onRouteResolved' > ;
4032
41- export class ExpressInstrumentation extends InstrumentationBase < ExpressInstrumentationConfig > {
42- public constructor ( config : ExpressInstrumentationConfig = { } ) {
43- super ( 'sentry-express' , SDK_VERSION , config ) ;
44- }
45- public init ( ) : InstrumentationNodeModuleDefinition {
46- const module = new InstrumentationNodeModuleDefinition (
47- 'express' ,
48- SUPPORTED_VERSIONS ,
49- express => {
50- try {
51- patchExpressModule ( {
52- ...this . getConfig ( ) ,
53- express,
54- onRouteResolved ( route ) {
55- const rpcMetadata = getRPCMetadata ( context . active ( ) ) ;
56- if ( route && rpcMetadata ?. type === RPCType . HTTP ) {
57- rpcMetadata . route = route ;
58- }
59- } ,
60- } ) ;
61- } catch ( e ) {
62- DEBUG_BUILD && debug . error ( 'Failed to patch express module:' , e ) ;
63- }
64- return express ;
65- } ,
66- // we do not ever actually unpatch in our SDKs
67- express => express ,
68- ) ;
69- return module ;
70- }
33+ /**
34+ * Instrument Express using registerModuleWrapper.
35+ * This registers hooks for both CJS and ESM module loading.
36+ *
37+ * Calling this multiple times is safe:
38+ * - Hooks are only registered once (first call)
39+ * - Options are updated on each call
40+ * - Use getOptions() in the patch to access current options at runtime
41+ */
42+ export function instrumentExpress ( options : ExpressInstrumentationConfig = { } ) : void {
43+ registerModuleWrapper < ExpressInstrumentationConfig > ( {
44+ moduleName : 'express' ,
45+ supportedVersions : SUPPORTED_VERSIONS ,
46+ options,
47+ patch : ( moduleExports , getOptions ) => {
48+ const express = moduleExports as ExpressModuleExport ;
49+ try {
50+ patchExpressModule ( express , ( ) => ( {
51+ ...getOptions ( ) ,
52+ onRouteResolved ( route ) {
53+ const rpcMetadata = getRPCMetadata ( context . active ( ) ) ;
54+ if ( route && rpcMetadata ?. type === RPCType . HTTP ) {
55+ rpcMetadata . route = route ;
56+ }
57+ } ,
58+ } ) ) ;
59+ } catch ( e ) {
60+ DEBUG_BUILD && debug . error ( 'Failed to patch express module:' , e ) ;
61+ }
62+ return moduleExports ;
63+ } ,
64+ } ) ;
7165}
7266
73- const _expressInstrumentation = ( ( options ?: ExpressInstrumentationConfig ) => {
67+ // Add id property for compatibility with preloadOpenTelemetry logging
68+ instrumentExpress . id = INTEGRATION_NAME ;
69+
70+ const _expressIntegration = ( ( options ?: ExpressInstrumentationConfig ) => {
7471 return {
7572 name : INTEGRATION_NAME ,
7673 setupOnce ( ) {
@@ -79,4 +76,4 @@ const _expressInstrumentation = ((options?: ExpressInstrumentationConfig) => {
7976 } ;
8077} ) satisfies IntegrationFn ;
8178
82- export const expressIntegration = defineIntegration ( _expressInstrumentation ) ;
79+ export const expressIntegration = defineIntegration ( _expressIntegration ) ;
0 commit comments