/
http.ts
177 lines (157 loc) · 5.12 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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import { getCurrentHub } from '@sentry/core';
import { Integration, Span } from '@sentry/types';
import { fill, parseSemver } from '@sentry/utils';
import * as http from 'http';
import * as https from 'https';
const NODE_VERSION = parseSemver(process.versions.node);
/** http module integration */
export class Http implements Integration {
/**
* @inheritDoc
*/
public name: string = Http.id;
/**
* @inheritDoc
*/
public static id: string = 'Http';
/**
* @inheritDoc
*/
private readonly _breadcrumbs: boolean;
/**
* @inheritDoc
*/
private readonly _tracing: boolean;
/**
* @inheritDoc
*/
public constructor(options: { breadcrumbs?: boolean; tracing?: boolean } = {}) {
this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
this._tracing = typeof options.tracing === 'undefined' ? false : options.tracing;
}
/**
* @inheritDoc
*/
public setupOnce(): void {
// No need to instrument if we don't want to track anything
if (!this._breadcrumbs && !this._tracing) {
return;
}
const handlerWrapper = createHandlerWrapper(this._breadcrumbs, this._tracing);
const httpModule = require('http');
fill(httpModule, 'get', handlerWrapper);
fill(httpModule, 'request', handlerWrapper);
// NOTE: Prior to Node 9, `https` used internals of `http` module, thus we don't patch it.
// If we do, we'd get double breadcrumbs and double spans for `https` calls.
// It has been changed in Node 9, so for all versions equal and above, we patch `https` separately.
if (NODE_VERSION.major && NODE_VERSION.major > 8) {
const httpsModule = require('https');
fill(httpsModule, 'get', handlerWrapper);
fill(httpsModule, 'request', handlerWrapper);
}
}
}
/**
* Wrapper function for internal `request` and `get` calls within `http` and `https` modules
*/
function createHandlerWrapper(
breadcrumbsEnabled: boolean,
tracingEnabled: boolean,
): (originalHandler: () => http.ClientRequest) => (options: string | http.ClientRequestArgs) => http.ClientRequest {
return function handlerWrapper(
originalHandler: () => http.ClientRequest,
): (options: string | http.ClientRequestArgs) => http.ClientRequest {
return function(this: typeof http | typeof https, options: string | http.ClientRequestArgs): http.ClientRequest {
const requestUrl = extractUrl(options);
if (isSentryRequest(requestUrl)) {
return originalHandler.apply(this, arguments);
}
let span: Span | undefined;
const scope = getCurrentHub().getScope();
if (scope && tracingEnabled) {
scope.getTransaction(transaction => {
span = transaction.startChild({
description: `${typeof options === 'string' || !options.method ? 'GET' : options.method} ${requestUrl}`,
op: 'request',
});
});
}
return originalHandler
.apply(this, arguments)
.once('response', function(this: http.IncomingMessage, res: http.ServerResponse): void {
if (breadcrumbsEnabled) {
addRequestBreadcrumb('response', requestUrl, this, res);
}
if (tracingEnabled && span) {
span.setHttpStatus(res.statusCode);
span.finish();
}
})
.once('error', function(this: http.IncomingMessage): void {
if (breadcrumbsEnabled) {
addRequestBreadcrumb('error', requestUrl, this);
}
if (tracingEnabled && span) {
span.setHttpStatus(500);
span.finish();
}
});
};
};
}
/**
* Captures Breadcrumb based on provided request/response pair
*/
function addRequestBreadcrumb(event: string, url: string, req: http.IncomingMessage, res?: http.ServerResponse): void {
if (!getCurrentHub().getIntegration(Http)) {
return;
}
getCurrentHub().addBreadcrumb(
{
category: 'http',
data: {
method: req.method,
status_code: res && res.statusCode,
url,
},
type: 'http',
},
{
event,
request: req,
response: res,
},
);
}
/**
* Function that can combine together a url that'll be used for our breadcrumbs.
*
* @param options url that should be returned or an object containing it's parts.
* @returns constructed url
*/
function extractUrl(options: string | http.ClientRequestArgs): string {
if (typeof options === 'string') {
return options;
}
const protocol = options.protocol || '';
const hostname = options.hostname || options.host || '';
// Don't log standard :80 (http) and :443 (https) ports to reduce the noise
const port = !options.port || options.port === 80 || options.port === 443 ? '' : `:${options.port}`;
const path = options.path || '/';
return `${protocol}//${hostname}${port}${path}`;
}
/**
* Checks whether given url points to Sentry server
* @param url url to verify
*/
function isSentryRequest(url: string): boolean {
const client = getCurrentHub().getClient();
if (!url || !client) {
return false;
}
const dsn = client.getDsn();
if (!dsn) {
return false;
}
return url.indexOf(dsn.host) !== -1;
}