Skip to content

Commit ed36b0d

Browse files
theanarkhdanielleadams
authored andcommitted
http: trace http request / response
PR-URL: #44102 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 9fc9534 commit ed36b0d

File tree

5 files changed

+111
-1
lines changed

5 files changed

+111
-1
lines changed

doc/api/tracing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ The available categories are:
3636
* `node.vm.script`: Enables capture of trace data for the `node:vm` module's
3737
`runInNewContext()`, `runInContext()`, and `runInThisContext()` methods.
3838
* `v8`: The [V8][] events are GC, compiling, and execution related.
39+
* `node.http`: Enables capture of trace data for http request / response.
3940

4041
By default the `node`, `node.async_hooks`, and `v8` categories are enabled.
4142

lib/_http_client.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,14 @@ const Agent = require('_http_agent');
6464
const { Buffer } = require('buffer');
6565
const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
6666
const { URL, urlToHttpOptions, searchParamsSymbol } = require('internal/url');
67-
const { kOutHeaders, kNeedDrain } = require('internal/http');
67+
const {
68+
kOutHeaders,
69+
kNeedDrain,
70+
isTraceHTTPEnabled,
71+
traceBegin,
72+
traceEnd,
73+
getNextTraceEventId,
74+
} = require('internal/http');
6875
const { connResetException, codes } = require('internal/errors');
6976
const {
7077
ERR_HTTP_HEADERS_SENT,
@@ -106,6 +113,8 @@ const kError = Symbol('kError');
106113
const kLenientAll = HTTPParser.kLenientAll | 0;
107114
const kLenientNone = HTTPParser.kLenientNone | 0;
108115

116+
const HTTP_CLIENT_TRACE_EVENT_NAME = 'http.client.request';
117+
109118
function validateHost(host, name) {
110119
if (host !== null && host !== undefined && typeof host !== 'string') {
111120
throw new ERR_INVALID_ARG_TYPE(`options.${name}`,
@@ -376,6 +385,10 @@ ClientRequest.prototype._finish = function _finish() {
376385
request: this,
377386
});
378387
}
388+
if (isTraceHTTPEnabled()) {
389+
this._traceEventId = getNextTraceEventId();
390+
traceBegin(HTTP_CLIENT_TRACE_EVENT_NAME, this._traceEventId);
391+
}
379392
};
380393

381394
ClientRequest.prototype._implicitHeader = function _implicitHeader() {
@@ -660,6 +673,12 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
660673
response: res,
661674
});
662675
}
676+
if (isTraceHTTPEnabled() && typeof req._traceEventId === 'number') {
677+
traceEnd(HTTP_CLIENT_TRACE_EVENT_NAME, req._traceEventId, {
678+
path: req.path,
679+
statusCode: res.statusCode,
680+
});
681+
}
663682
req.res = res;
664683
res.req = req;
665684

lib/_http_server.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ const {
5555
const {
5656
kOutHeaders,
5757
kNeedDrain,
58+
isTraceHTTPEnabled,
59+
traceBegin,
60+
traceEnd,
61+
getNextTraceEventId,
5862
} = require('internal/http');
5963
const {
6064
defaultTriggerAsyncIdScope,
@@ -174,6 +178,8 @@ const kLenientNone = HTTPParser.kLenientNone | 0;
174178
const kConnections = Symbol('http.server.connections');
175179
const kConnectionsCheckingInterval = Symbol('http.server.connectionsCheckingInterval');
176180

181+
const HTTP_SERVER_TRACE_EVENT_NAME = 'http.server.request';
182+
177183
class HTTPServerAsyncResource {
178184
constructor(type, socket) {
179185
this.type = type;
@@ -210,6 +216,10 @@ function ServerResponse(req) {
210216
},
211217
});
212218
}
219+
if (isTraceHTTPEnabled()) {
220+
this._traceEventId = getNextTraceEventId();
221+
traceBegin(HTTP_SERVER_TRACE_EVENT_NAME, this._traceEventId);
222+
}
213223
}
214224
ObjectSetPrototypeOf(ServerResponse.prototype, OutgoingMessage.prototype);
215225
ObjectSetPrototypeOf(ServerResponse, OutgoingMessage);
@@ -228,6 +238,13 @@ ServerResponse.prototype._finish = function _finish() {
228238
});
229239
}
230240
OutgoingMessage.prototype._finish.call(this);
241+
if (isTraceHTTPEnabled() && typeof this._traceEventId === 'number') {
242+
const data = {
243+
url: this.req?.url,
244+
statusCode: this.statusCode,
245+
};
246+
traceEnd(HTTP_SERVER_TRACE_EVENT_NAME, this._traceEventId, data);
247+
}
231248
};
232249

233250

lib/internal/http.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ const {
88
} = primordials;
99

1010
const { setUnrefTimeout } = require('internal/timers');
11+
const { trace, isTraceCategoryEnabled } = internalBinding('trace_events');
12+
const {
13+
CHAR_LOWERCASE_B,
14+
CHAR_LOWERCASE_E,
15+
} = require('internal/constants');
1116

1217
let utcCache;
1318

@@ -26,8 +31,32 @@ function resetCache() {
2631
utcCache = undefined;
2732
}
2833

34+
let traceEventId = 0;
35+
36+
function getNextTraceEventId() {
37+
return ++traceEventId;
38+
}
39+
40+
function isTraceHTTPEnabled() {
41+
return isTraceCategoryEnabled('node.http');
42+
}
43+
44+
const traceEventCategory = 'node,node.http';
45+
46+
function traceBegin(...args) {
47+
trace(CHAR_LOWERCASE_B, traceEventCategory, ...args);
48+
}
49+
50+
function traceEnd(...args) {
51+
trace(CHAR_LOWERCASE_E, traceEventCategory, ...args);
52+
}
53+
2954
module.exports = {
3055
kOutHeaders: Symbol('kOutHeaders'),
3156
kNeedDrain: Symbol('kNeedDrain'),
3257
utcDate,
58+
traceBegin,
59+
traceEnd,
60+
getNextTraceEventId,
61+
isTraceHTTPEnabled,
3362
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
const fs = require('fs');
6+
const path = require('path');
7+
const tmpdir = require('../common/tmpdir');
8+
9+
const CODE = `
10+
const http = require('http');
11+
const server = http.createServer((req, res) => {
12+
res.end('ok');
13+
server.close();
14+
}).listen(0, () => {
15+
http.get({port: server.address().port});
16+
});
17+
`;
18+
19+
tmpdir.refresh();
20+
const FILE_NAME = path.join(tmpdir.path, 'node_trace.1.log');
21+
22+
const proc = cp.spawn(process.execPath,
23+
[ '--trace-events-enabled',
24+
'--trace-event-categories', 'node.http',
25+
'-e', CODE ],
26+
{ cwd: tmpdir.path });
27+
28+
proc.once('exit', common.mustCall(() => {
29+
assert(fs.existsSync(FILE_NAME));
30+
fs.readFile(FILE_NAME, common.mustCall((err, data) => {
31+
assert(!err);
32+
const traces = JSON.parse(data.toString()).traceEvents;
33+
assert(traces.length > 0);
34+
let count = 0;
35+
traces.forEach((trace) => {
36+
if (trace.cat === 'node,node.http' &&
37+
['http.server.request', 'http.client.request'].includes(trace.name)) {
38+
count++;
39+
}
40+
});
41+
// Two begin, two end
42+
assert.strictEqual(count, 4);
43+
}));
44+
}));

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy