Skip to content

Commit 2e4ce76

Browse files
committed
lib: add tracing support for http2
PR-URL: #266 Reviewed-By: Juan José Arboleda <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]>
1 parent a170e11 commit 2e4ce76

13 files changed

+1058
-3
lines changed

lib/internal/nsolid_diag.js

Lines changed: 177 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,22 @@
33
const nsolidApi = internalBinding('nsolid_api');
44
const {
55
nsolid_counts,
6+
nsolid_span_id_s,
67
nsolid_tracer_s,
78
nsolid_consts,
89
} = nsolidApi;
910

1011
const { now } = require('internal/perf/utils');
12+
const { contextManager } = require('internal/otel/context');
13+
const {
14+
getApi,
15+
} = require('internal/otel/core');
16+
17+
const {
18+
extractSpanContextFromHttpHeaders,
19+
generateSpan,
20+
nsolidTracer,
21+
} = require('internal/nsolid_trace');
1122

1223
const dc = require('diagnostics_channel');
1324

@@ -16,11 +27,96 @@ const {
1627
kHttpClientCount,
1728
kHttpServerAbortCount,
1829
kHttpServerCount,
30+
kSpanHttpClient,
31+
kSpanHttpMethod,
32+
kSpanHttpReqUrl,
33+
kSpanHttpServer,
34+
kSpanHttpStatusCode,
1935
} = nsolid_consts;
2036

37+
const http2Client = dc.tracingChannel('http2.client');
38+
const http2Server = dc.tracingChannel('http2.server');
39+
2140
// To lazy load the http2 constants
2241
let http2Constants;
2342

43+
let tracingEnabled = false;
44+
45+
const subscribeListener = (message, name) => {};
46+
47+
function disableTracing() {
48+
http2Client.start.unbindStore();
49+
dc.unsubscribe('tracing:http2.client:start', subscribeListener);
50+
http2Server.start.unbindStore();
51+
dc.unsubscribe('tracing:http2.server:start', subscribeListener);
52+
}
53+
54+
function extractHttp2Url(headers, http2Constants) {
55+
const { HTTP2_HEADER_SCHEME, HTTP2_HEADER_PATH } = http2Constants;
56+
const authority = require('internal/http2/util').getAuthority(headers);
57+
return `${headers[HTTP2_HEADER_SCHEME]}://${authority}${headers[HTTP2_HEADER_PATH]}`;
58+
}
59+
60+
function enableTracing() {
61+
dc.subscribe('tracing:http2.client:start', subscribeListener);
62+
63+
http2Client.start.bindStore(contextManager._getALS(), (data) => {
64+
const api = getApi();
65+
const tracer = api.trace.getTracer('http2');
66+
http2Constants ||= require('internal/http2/core').constants;
67+
const { headers } = data;
68+
const method = headers[http2Constants.HTTP2_HEADER_METHOD];
69+
const url = extractHttp2Url(headers, http2Constants);
70+
const span = tracer.startSpan(`HTTP ${method}`,
71+
{ internal: true,
72+
kind: api.SpanKind.CLIENT,
73+
type: kSpanHttpClient });
74+
span._pushSpanDataString(kSpanHttpMethod, method);
75+
span._pushSpanDataString(kSpanHttpReqUrl, url);
76+
const { spanId, traceId } = span.spanContext();
77+
if (span._isSampled()) {
78+
headers.traceparent = `00-${traceId}-${spanId}-01`;
79+
} else {
80+
headers.traceparent = `00-${traceId}-${spanId}-00`;
81+
}
82+
83+
return api.trace.setSpan(api.context.active(), span);
84+
});
85+
86+
dc.subscribe('tracing:http2.client:start', subscribeListener);
87+
88+
http2Server.start.bindStore(contextManager._getALS(), (data) => {
89+
const api = getApi();
90+
const tracer = api.trace.getTracer('http2');
91+
http2Constants ||= require('internal/http2/core').constants;
92+
const { headers } = data;
93+
const method = headers[http2Constants.HTTP2_HEADER_METHOD];
94+
const url = extractHttp2Url(headers, http2Constants);
95+
const ctxt = extractSpanContextFromHttpHeaders(api.ROOT_CONTEXT, headers);
96+
const span = tracer.startSpan(`HTTP ${method}`,
97+
{ internal: true,
98+
kind: api.SpanKind.SERVER,
99+
type: kSpanHttpServer },
100+
ctxt);
101+
span._pushSpanDataString(kSpanHttpMethod, method);
102+
span._pushSpanDataString(kSpanHttpReqUrl, url);
103+
return api.trace.setSpan(api.context.active(), span);
104+
});
105+
106+
dc.subscribe('tracing:http2.server:start', subscribeListener);
107+
}
108+
109+
nsolidTracer.on('flagsUpdated', () => {
110+
const next = generateSpan(kSpanHttpClient);
111+
if (next && !tracingEnabled) {
112+
enableTracing();
113+
tracingEnabled = true;
114+
} else if (!next && tracingEnabled) {
115+
disableTracing();
116+
tracingEnabled = false;
117+
}
118+
});
119+
24120
dc.subscribe('undici:request:create', ({ request }) => {
25121
request[nsolid_tracer_s] = now();
26122
});
@@ -39,41 +135,119 @@ dc.subscribe('http2.client.stream.created', ({ stream }) => {
39135
start: now(),
40136
response: false,
41137
};
138+
139+
if (generateSpan(kSpanHttpClient)) {
140+
const api = getApi();
141+
const span = api.trace.getSpan(api.context.active());
142+
if (span) {
143+
stream[nsolid_span_id_s] = span;
144+
}
145+
}
42146
});
43147

44-
dc.subscribe('http2.client.stream.finish', ({ stream, flags }) => {
148+
dc.subscribe('http2.client.stream.finish', ({ stream, headers }) => {
45149
stream[nsolid_tracer_s].response = true;
150+
http2Constants ||= require('internal/http2/core').constants;
151+
if (generateSpan(kSpanHttpClient)) {
152+
const span = stream[nsolid_span_id_s];
153+
if (span) {
154+
const status = headers[http2Constants.HTTP2_HEADER_STATUS];
155+
span._pushSpanDataUint64(kSpanHttpStatusCode, status);
156+
if (status >= 400) {
157+
span.setStatus({ code: getApi().SpanStatusCode.ERROR });
158+
}
159+
}
160+
}
161+
});
162+
163+
dc.subscribe('http2.client.stream.error', ({ stream, error }) => {
164+
if (generateSpan(kSpanHttpClient)) {
165+
const span = stream[nsolid_span_id_s];
166+
if (span) {
167+
span.recordException(error);
168+
span.setStatus({
169+
code: getApi().SpanStatusCode.ERROR,
170+
message: error.message,
171+
});
172+
span.end();
173+
}
174+
}
46175
});
47176

48177
dc.subscribe('http2.client.stream.close', ({ stream, code }) => {
49178
http2Constants ||= require('internal/http2/core').constants;
50179
const tracingInfo = stream[nsolid_tracer_s];
180+
const span = stream[nsolid_span_id_s];
51181
if (code === http2Constants.NGHTTP2_NO_ERROR && tracingInfo.response) {
52182
nsolid_counts[kHttpClientCount]++;
53183
nsolidApi.pushClientBucket(now() - tracingInfo.start);
54184
} else {
55185
nsolid_counts[kHttpClientAbortCount]++;
186+
if (span) {
187+
span.setStatus({
188+
code: getApi().SpanStatusCode.ERROR,
189+
});
190+
}
56191
}
192+
193+
span?.end();
57194
});
58195

59-
dc.subscribe('http2.server.stream.start', ({ stream }) => {
196+
dc.subscribe('http2.server.stream.start', ({ stream, headers }) => {
60197
stream[nsolid_tracer_s] = {
61198
start: now(),
62199
response: false,
63200
};
201+
202+
if (generateSpan(kSpanHttpServer)) {
203+
const api = getApi();
204+
const span = api.trace.getSpan(api.context.active());
205+
if (span) {
206+
stream[nsolid_span_id_s] = span;
207+
}
208+
}
64209
});
65210

66-
dc.subscribe('http2.server.stream.finish', ({ stream, flags }) => {
211+
dc.subscribe('http2.server.stream.finish', ({ stream, headers }) => {
67212
stream[nsolid_tracer_s].response = true;
68213
});
69214

215+
dc.subscribe('http2.server.stream.error', ({ stream, error }) => {
216+
if (generateSpan(kSpanHttpServer)) {
217+
const span = stream[nsolid_span_id_s];
218+
if (span) {
219+
span.recordException(error);
220+
span.setStatus({
221+
code: getApi().SpanStatusCode.ERROR,
222+
message: error.message,
223+
});
224+
span.end();
225+
}
226+
}
227+
});
228+
70229
dc.subscribe('http2.server.stream.close', ({ stream, code }) => {
71230
http2Constants ||= require('internal/http2/core').constants;
72231
const tracingInfo = stream[nsolid_tracer_s];
232+
const span = stream[nsolid_span_id_s];
73233
if (code === http2Constants.NGHTTP2_NO_ERROR && tracingInfo.response) {
74234
nsolid_counts[kHttpServerCount]++;
75235
nsolidApi.pushServerBucket(now() - tracingInfo.start);
76236
} else {
77237
nsolid_counts[kHttpServerAbortCount]++;
238+
if (span) {
239+
span.setStatus({
240+
code: getApi().SpanStatusCode.ERROR,
241+
});
242+
}
243+
}
244+
245+
if (span) {
246+
if (stream.headersSent) {
247+
const status = stream.sentHeaders[http2Constants.HTTP2_HEADER_STATUS];
248+
span._pushSpanDataUint64(kSpanHttpStatusCode, status);
249+
}
250+
251+
span.end();
78252
}
79253
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Flags: --dns-result-order=ipv4first
2+
'use strict';
3+
const common = require('../../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
const { checkTracesOnExit } = require('../../common/nsolid-traces');
7+
const { setupNSolid } = require('./utils');
8+
const http2 = require('http2');
9+
const bindingPath = require.resolve(`./build/${common.buildType}/binding`);
10+
const binding = require(bindingPath);
11+
12+
function setupTracesCheck(port, addresses) {
13+
const expectedTraces = [
14+
{
15+
attributes: {
16+
'http.method': 'GET',
17+
'http.url': `http://localhost:${port}/`,
18+
},
19+
end_reason: binding.kSpanEndOk,
20+
name: 'HTTP GET',
21+
parentId: '0000000000000000',
22+
thread_id: 0,
23+
kind: binding.kClient,
24+
type: binding.kSpanHttpClient,
25+
events: [
26+
{
27+
attributes: {
28+
'exception.message': 'The operation was aborted',
29+
'exception.type': 'ABORT_ERR',
30+
},
31+
name: 'exception',
32+
},
33+
],
34+
status: {
35+
code: 2, // ERROR
36+
message: 'The operation was aborted',
37+
},
38+
},
39+
{
40+
attributes: {
41+
'dns.address': addresses[0].address,
42+
'dns.hostname': 'localhost',
43+
'dns.op_type': binding.kDnsLookup,
44+
},
45+
end_reason: binding.kSpanEndOk,
46+
name: 'DNS lookup',
47+
thread_id: 0,
48+
kind: binding.kClient,
49+
type: binding.kSpanDns,
50+
},
51+
];
52+
53+
checkTracesOnExit(binding, expectedTraces);
54+
}
55+
56+
setupNSolid(common.mustSucceed(({ addresses }) => {
57+
const server = http2.createServer();
58+
const controller = new AbortController();
59+
60+
server.on('stream', common.mustNotCall());
61+
62+
server.listen(0, common.mustCall(() => {
63+
const port = server.address().port;
64+
setupTracesCheck(port, addresses);
65+
const client = http2.connect(`http://localhost:${port}`);
66+
client.on('close', common.mustCall());
67+
68+
const { signal } = controller;
69+
controller.abort();
70+
71+
const req = client.request({}, { signal });
72+
73+
req.on('error', common.mustCall());
74+
req.on('close', common.mustCall(() => {
75+
client.close(common.mustCall(() => {
76+
server.close();
77+
}));
78+
}));
79+
}));
80+
}));

0 commit comments

Comments
 (0)