Skip to content

Commit d5b3df6

Browse files
committed
feat: Express middleware tracing integration
1 parent 53a5d35 commit d5b3df6

File tree

1 file changed

+124
-0
lines changed

1 file changed

+124
-0
lines changed

packages/integrations/src/express.ts

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { EventProcessor, Hub, Integration } from '@sentry/types';
2+
import { logger } from '@sentry/utils';
3+
import { Application, ErrorRequestHandler, NextFunction, Request, RequestHandler, Response } from 'express';
4+
5+
/**
6+
* Express integration
7+
*
8+
* Provides an request and error handler for Express framework
9+
* as well as tracing capabilities
10+
*/
11+
export class Express implements Integration {
12+
/**
13+
* @inheritDoc
14+
*/
15+
public name: string = Express.id;
16+
17+
/**
18+
* @inheritDoc
19+
*/
20+
public static id: string = 'Express';
21+
22+
/**
23+
* Express App instance
24+
*/
25+
private readonly _app?: Application;
26+
27+
/**
28+
* @inheritDoc
29+
*/
30+
public constructor(options: { app?: Application } = {}) {
31+
this._app = options.app;
32+
}
33+
34+
/**
35+
* @inheritDoc
36+
*/
37+
public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void {
38+
if (!this._app) {
39+
logger.error('ExpressIntegration is missing an Express instancex');
40+
return;
41+
}
42+
instrumentMiddlewares(this._app, getCurrentHub);
43+
}
44+
}
45+
46+
/**
47+
* JSDoc
48+
*/
49+
function wrap(fn: Function, getCurrentHub: () => Hub): RequestHandler | ErrorRequestHandler {
50+
const arrity = fn.length;
51+
52+
switch (arrity) {
53+
case 2: {
54+
return function(this: NodeJS.Global, _req: Request, res: Response): any {
55+
const span = getCurrentHub().startSpan({
56+
description: fn.name,
57+
op: 'middleware',
58+
});
59+
res.once('finish', () => span.finish());
60+
return fn.apply(this, arguments);
61+
};
62+
}
63+
case 3: {
64+
return function(this: NodeJS.Global, req: Request, res: Response, next: NextFunction): any {
65+
const span = getCurrentHub().startSpan({
66+
description: fn.name,
67+
op: 'middleware',
68+
});
69+
fn.call(this, req, res, function(this: NodeJS.Global): any {
70+
span.finish();
71+
return next.apply(this, arguments);
72+
});
73+
};
74+
}
75+
case 4: {
76+
return function(this: NodeJS.Global, err: any, req: Request, res: Response, next: NextFunction): any {
77+
const span = getCurrentHub().startSpan({
78+
description: fn.name,
79+
op: 'middleware',
80+
});
81+
fn.call(this, err, req, res, function(this: NodeJS.Global): any {
82+
span.finish();
83+
return next.apply(this, arguments);
84+
});
85+
};
86+
}
87+
default: {
88+
throw new Error(`Express middleware takes 2-4 arguments. Got: ${arrity}`);
89+
}
90+
}
91+
}
92+
93+
/**
94+
* JSDoc
95+
*/
96+
function wrapUseArgs(args: IArguments, getCurrentHub: () => Hub): unknown[] {
97+
return Array.from(args).map((arg: unknown) => {
98+
if (typeof arg === 'function') {
99+
return wrap(arg, getCurrentHub);
100+
}
101+
102+
if (Array.isArray(arg)) {
103+
return arg.map((a: unknown) => {
104+
if (typeof a === 'function') {
105+
return wrap(a, getCurrentHub);
106+
}
107+
return a;
108+
});
109+
}
110+
111+
return arg;
112+
});
113+
}
114+
115+
/**
116+
* JSDoc
117+
*/
118+
function instrumentMiddlewares(app: Application, getCurrentHub: () => Hub): any {
119+
const originalAppUse = app.use;
120+
app.use = function(): any {
121+
return originalAppUse.apply(this, wrapUseArgs(arguments, getCurrentHub));
122+
};
123+
return app;
124+
}

0 commit comments

Comments
 (0)