-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
http.ts
139 lines (120 loc) · 5.3 KB
/
http.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
import type { AddRequestDataToEventOptions } from '@sentry/node';
import { captureException, flush, getCurrentHub } from '@sentry/node';
import { isString, isThenable, logger, stripUrlQueryAndFragment, tracingContextFromHeaders } from '@sentry/utils';
import { DEBUG_BUILD } from '../debug-build';
import { domainify, markEventUnhandled, proxyFunction } from './../utils';
import type { HttpFunction, WrapperOptions } from './general';
// TODO (v8 / #5257): Remove this whole old/new business and just use the new stuff
type ParseRequestOptions = AddRequestDataToEventOptions['include'] & {
serverName?: boolean;
version?: boolean;
};
interface OldHttpFunctionWrapperOptions extends WrapperOptions {
/**
* @deprecated Use `addRequestDataToEventOptions` instead.
*/
parseRequestOptions: ParseRequestOptions;
}
interface NewHttpFunctionWrapperOptions extends WrapperOptions {
addRequestDataToEventOptions: AddRequestDataToEventOptions;
}
export type HttpFunctionWrapperOptions = OldHttpFunctionWrapperOptions | NewHttpFunctionWrapperOptions;
/**
* Wraps an HTTP function handler adding it error capture and tracing capabilities.
*
* @param fn HTTP Handler
* @param options Options
* @returns HTTP handler
*/
export function wrapHttpFunction(
fn: HttpFunction,
wrapOptions: Partial<HttpFunctionWrapperOptions> = {},
): HttpFunction {
const wrap = (f: HttpFunction): HttpFunction => domainify(_wrapHttpFunction(f, wrapOptions));
let overrides: Record<PropertyKey, unknown> | undefined;
// Functions emulator from firebase-tools has a hack-ish workaround that saves the actual function
// passed to `onRequest(...)` and in fact runs it so we need to wrap it too.
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
const emulatorFunc = (fn as any).__emulator_func as HttpFunction | undefined;
if (emulatorFunc) {
overrides = { __emulator_func: proxyFunction(emulatorFunc, wrap) };
}
return proxyFunction(fn, wrap, overrides);
}
/** */
function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial<HttpFunctionWrapperOptions> = {}): HttpFunction {
// TODO (v8 / #5257): Switch to using `addRequestDataToEventOptions`
// eslint-disable-next-line deprecation/deprecation
const { parseRequestOptions } = wrapOptions as OldHttpFunctionWrapperOptions;
const options: HttpFunctionWrapperOptions = {
flushTimeout: 2000,
// TODO (v8 / xxx): Remove this line, since `addRequestDataToEventOptions` will be included in the spread of `wrapOptions`
addRequestDataToEventOptions: parseRequestOptions ? { include: parseRequestOptions } : {},
...wrapOptions,
};
return (req, res) => {
const hub = getCurrentHub();
const reqMethod = (req.method || '').toUpperCase();
const reqUrl = stripUrlQueryAndFragment(req.originalUrl || req.url || '');
const sentryTrace = req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined;
const baggage = req.headers?.baggage;
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
sentryTrace,
baggage,
);
hub.getScope().setPropagationContext(propagationContext);
const transaction = hub.startTransaction({
name: `${reqMethod} ${reqUrl}`,
op: 'function.gcp.http',
origin: 'auto.function.serverless.gcp_http',
...traceparentData,
metadata: {
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
source: 'route',
},
}) as ReturnType<typeof hub.startTransaction> | undefined;
// getCurrentHub() is expected to use current active domain as a carrier
// since functions-framework creates a domain for each incoming request.
// So adding of event processors every time should not lead to memory bloat.
hub.configureScope(scope => {
scope.setSDKProcessingMetadata({
request: req,
requestDataOptionsFromGCPWrapper: options.addRequestDataToEventOptions,
});
// We put the transaction on the scope so users can attach children to it
scope.setSpan(transaction);
});
// We also set __sentry_transaction on the response so people can grab the transaction there to add
// spans to it later.
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
(res as any).__sentry_transaction = transaction;
// eslint-disable-next-line @typescript-eslint/unbound-method
const _end = res.end;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
res.end = function (chunk?: any | (() => void), encoding?: string | (() => void), cb?: () => void): any {
transaction?.setHttpStatus(res.statusCode);
transaction?.finish();
void flush(options.flushTimeout)
.then(null, e => {
DEBUG_BUILD && logger.error(e);
})
.then(() => {
_end.call(this, chunk, encoding, cb);
});
};
let fnResult;
try {
fnResult = fn(req, res);
} catch (err) {
captureException(err, scope => markEventUnhandled(scope));
throw err;
}
if (isThenable(fnResult)) {
fnResult.then(null, err => {
captureException(err, scope => markEventUnhandled(scope));
throw err;
});
}
return fnResult;
};
}