@redthreadlabs/tracelog 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/README.md +126 -0
- package/index.d.ts +464 -0
- package/index.js +11 -0
- package/lib/InflightEventSet.js +53 -0
- package/lib/activation-method.js +97 -0
- package/lib/agent.js +1226 -0
- package/lib/apm-client/apm-client.js +107 -0
- package/lib/apm-client/channel-writer.js +334 -0
- package/lib/apm-client/jsonl-file-client.js +241 -0
- package/lib/apm-client/ndjson.js +20 -0
- package/lib/apm-client/noop-apm-client.js +79 -0
- package/lib/apm-client/s3-uploader.js +308 -0
- package/lib/apm-client/truncate.js +507 -0
- package/lib/async-hooks-polyfill.js +58 -0
- package/lib/cloud-metadata/aws.js +175 -0
- package/lib/cloud-metadata/azure.js +123 -0
- package/lib/cloud-metadata/callback-coordination.js +159 -0
- package/lib/cloud-metadata/gcp.js +133 -0
- package/lib/cloud-metadata/index.js +175 -0
- package/lib/config/config.js +431 -0
- package/lib/config/normalizers.js +649 -0
- package/lib/config/schema.js +946 -0
- package/lib/constants.js +35 -0
- package/lib/errors.js +303 -0
- package/lib/filters/sanitize-field-names.js +69 -0
- package/lib/http-request.js +249 -0
- package/lib/instrumentation/context.js +56 -0
- package/lib/instrumentation/dropped-spans-stats.js +112 -0
- package/lib/instrumentation/elasticsearch-shared.js +63 -0
- package/lib/instrumentation/express-utils.js +91 -0
- package/lib/instrumentation/generic-span.js +322 -0
- package/lib/instrumentation/http-shared.js +424 -0
- package/lib/instrumentation/ids.js +39 -0
- package/lib/instrumentation/index.js +1078 -0
- package/lib/instrumentation/modules/@apollo/server.js +39 -0
- package/lib/instrumentation/modules/@aws-sdk/client-dynamodb.js +143 -0
- package/lib/instrumentation/modules/@aws-sdk/client-s3.js +230 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sns.js +197 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sqs.js +336 -0
- package/lib/instrumentation/modules/@elastic/elasticsearch.js +343 -0
- package/lib/instrumentation/modules/@hapi/hapi.js +221 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/commands-queue.js +178 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/index.js +49 -0
- package/lib/instrumentation/modules/@smithy/smithy-client.js +198 -0
- package/lib/instrumentation/modules/apollo-server-core.js +49 -0
- package/lib/instrumentation/modules/aws-sdk/dynamodb.js +155 -0
- package/lib/instrumentation/modules/aws-sdk/s3.js +184 -0
- package/lib/instrumentation/modules/aws-sdk/sns.js +232 -0
- package/lib/instrumentation/modules/aws-sdk/sqs.js +361 -0
- package/lib/instrumentation/modules/aws-sdk.js +76 -0
- package/lib/instrumentation/modules/bluebird.js +93 -0
- package/lib/instrumentation/modules/cassandra-driver.js +280 -0
- package/lib/instrumentation/modules/elasticsearch.js +200 -0
- package/lib/instrumentation/modules/express-graphql.js +66 -0
- package/lib/instrumentation/modules/express-queue.js +28 -0
- package/lib/instrumentation/modules/express.js +162 -0
- package/lib/instrumentation/modules/fastify.js +179 -0
- package/lib/instrumentation/modules/finalhandler.js +41 -0
- package/lib/instrumentation/modules/generic-pool.js +85 -0
- package/lib/instrumentation/modules/graphql.js +256 -0
- package/lib/instrumentation/modules/handlebars.js +33 -0
- package/lib/instrumentation/modules/http.js +112 -0
- package/lib/instrumentation/modules/http2.js +320 -0
- package/lib/instrumentation/modules/https.js +68 -0
- package/lib/instrumentation/modules/ioredis.js +94 -0
- package/lib/instrumentation/modules/jade.js +29 -0
- package/lib/instrumentation/modules/kafkajs.js +476 -0
- package/lib/instrumentation/modules/knex.js +91 -0
- package/lib/instrumentation/modules/koa-router.js +74 -0
- package/lib/instrumentation/modules/koa.js +15 -0
- package/lib/instrumentation/modules/memcached.js +100 -0
- package/lib/instrumentation/modules/mimic-response.js +45 -0
- package/lib/instrumentation/modules/mongodb/lib/cmap/connection_pool.js +40 -0
- package/lib/instrumentation/modules/mongodb-core.js +206 -0
- package/lib/instrumentation/modules/mongodb.js +259 -0
- package/lib/instrumentation/modules/mysql.js +200 -0
- package/lib/instrumentation/modules/mysql2.js +140 -0
- package/lib/instrumentation/modules/pg.js +148 -0
- package/lib/instrumentation/modules/pug.js +29 -0
- package/lib/instrumentation/modules/redis.js +176 -0
- package/lib/instrumentation/modules/restify.js +52 -0
- package/lib/instrumentation/modules/tedious.js +159 -0
- package/lib/instrumentation/modules/undici.js +270 -0
- package/lib/instrumentation/modules/ws.js +59 -0
- package/lib/instrumentation/noop-transaction.js +81 -0
- package/lib/instrumentation/run-context/AbstractRunContextManager.js +215 -0
- package/lib/instrumentation/run-context/AsyncHooksRunContextManager.js +106 -0
- package/lib/instrumentation/run-context/AsyncLocalStorageRunContextManager.js +73 -0
- package/lib/instrumentation/run-context/BasicRunContextManager.js +82 -0
- package/lib/instrumentation/run-context/RunContext.js +151 -0
- package/lib/instrumentation/run-context/index.js +23 -0
- package/lib/instrumentation/shimmer.js +123 -0
- package/lib/instrumentation/span-compression.js +239 -0
- package/lib/instrumentation/span.js +621 -0
- package/lib/instrumentation/template-shared.js +43 -0
- package/lib/instrumentation/timer.js +84 -0
- package/lib/instrumentation/transaction.js +571 -0
- package/lib/load-source-map.js +100 -0
- package/lib/logging.js +212 -0
- package/lib/metrics/index.js +92 -0
- package/lib/metrics/platforms/generic/index.js +40 -0
- package/lib/metrics/platforms/generic/process-cpu.js +22 -0
- package/lib/metrics/platforms/generic/process-top.js +157 -0
- package/lib/metrics/platforms/generic/stats.js +34 -0
- package/lib/metrics/platforms/generic/system-cpu.js +51 -0
- package/lib/metrics/platforms/linux/index.js +19 -0
- package/lib/metrics/platforms/linux/stats.js +213 -0
- package/lib/metrics/queue.js +90 -0
- package/lib/metrics/registry.js +52 -0
- package/lib/metrics/reporter.js +119 -0
- package/lib/metrics/runtime.js +77 -0
- package/lib/middleware/connect.js +16 -0
- package/lib/parsers.js +225 -0
- package/lib/propwrap.js +147 -0
- package/lib/stacktraces.js +537 -0
- package/lib/symbols.js +15 -0
- package/lib/tracecontext/index.js +115 -0
- package/lib/tracecontext/traceparent.js +185 -0
- package/lib/tracecontext/tracestate.js +388 -0
- package/lib/wildcard-matcher.js +52 -0
- package/loader.mjs +7 -0
- package/package.json +98 -0
- package/start.d.ts +8 -0
- package/start.js +29 -0
- package/types/aws-lambda.d.ts +98 -0
- package/types/connect.d.ts +23 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const semver = require('semver');
|
|
10
|
+
|
|
11
|
+
const shimmer = require('../shimmer');
|
|
12
|
+
|
|
13
|
+
module.exports = function (restify, agent, { version, enabled }) {
|
|
14
|
+
if (!enabled) {
|
|
15
|
+
return restify;
|
|
16
|
+
}
|
|
17
|
+
if (!semver.satisfies(version, '>=5.2.0 <12.0.0')) {
|
|
18
|
+
agent.logger.debug('restify version %s not supported, skipping', version);
|
|
19
|
+
return restify;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
agent.setFramework({ name: 'restify', version, overwrite: false });
|
|
23
|
+
|
|
24
|
+
function patchServer(server) {
|
|
25
|
+
if (semver.gte(version, '7.0.0')) {
|
|
26
|
+
shimmer.wrap(server, '_onHandlerError', function (orig) {
|
|
27
|
+
return function _wrappedOnHandlerError(err, req, res, isUncaught) {
|
|
28
|
+
if (err)
|
|
29
|
+
agent.captureError(err, { request: req, handled: !isUncaught });
|
|
30
|
+
return orig.apply(this, arguments);
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
shimmer.wrap(server, '_emitErrorEvents', function (orig) {
|
|
35
|
+
return function _wrappedOnHandlerError(req, res, route, err, cb) {
|
|
36
|
+
if (err) agent.captureError(err, { request: req });
|
|
37
|
+
return orig.apply(this, arguments);
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
shimmer.wrap(restify, 'createServer', function (fn) {
|
|
44
|
+
return function wrappedCreateServer() {
|
|
45
|
+
const server = fn.apply(this, arguments);
|
|
46
|
+
patchServer(server);
|
|
47
|
+
return server;
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return restify;
|
|
52
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
var semver = require('semver');
|
|
10
|
+
var clone = require('shallow-clone-shim');
|
|
11
|
+
var sqlSummary = require('sql-summary');
|
|
12
|
+
|
|
13
|
+
var { getDBDestination } = require('../context');
|
|
14
|
+
|
|
15
|
+
module.exports = function (tedious, agent, { version, enabled }) {
|
|
16
|
+
if (!enabled) return tedious;
|
|
17
|
+
if (
|
|
18
|
+
semver.satisfies(version, '>=19') &&
|
|
19
|
+
!semver.satisfies(process.version, '>=18.17')
|
|
20
|
+
) {
|
|
21
|
+
agent.logger.debug(
|
|
22
|
+
'tedious version %s not supported for node %s - aborting...',
|
|
23
|
+
version,
|
|
24
|
+
process.version,
|
|
25
|
+
);
|
|
26
|
+
return tedious;
|
|
27
|
+
}
|
|
28
|
+
if (version === '4.0.0' || !semver.satisfies(version, '>=1.9.0 <20')) {
|
|
29
|
+
agent.logger.debug(
|
|
30
|
+
'tedious version %s not supported - aborting...',
|
|
31
|
+
version,
|
|
32
|
+
);
|
|
33
|
+
return tedious;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ins = agent._instrumentation;
|
|
37
|
+
|
|
38
|
+
return clone({}, tedious, {
|
|
39
|
+
Connection(descriptor) {
|
|
40
|
+
const getter = descriptor.get;
|
|
41
|
+
if (getter) {
|
|
42
|
+
// tedious v6.5.0+
|
|
43
|
+
descriptor.get = function get() {
|
|
44
|
+
return wrapConnection(getter());
|
|
45
|
+
};
|
|
46
|
+
} else if (typeof descriptor.value === 'function') {
|
|
47
|
+
descriptor.value = wrapConnection(descriptor.value);
|
|
48
|
+
} else {
|
|
49
|
+
agent.logger.debug(
|
|
50
|
+
'could not patch `tedious.Connection` property for tedious version %s - aborting...',
|
|
51
|
+
version,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return descriptor;
|
|
55
|
+
},
|
|
56
|
+
Request(descriptor) {
|
|
57
|
+
const getter = descriptor.get;
|
|
58
|
+
if (getter) {
|
|
59
|
+
// tedious v6.5.0+
|
|
60
|
+
descriptor.get = function get() {
|
|
61
|
+
return wrapRequest(getter());
|
|
62
|
+
};
|
|
63
|
+
} else if (typeof descriptor.value === 'function') {
|
|
64
|
+
descriptor.value = wrapRequest(descriptor.value);
|
|
65
|
+
} else {
|
|
66
|
+
agent.logger.debug(
|
|
67
|
+
'could not patch `tedious.Request` property for tedious version %s - aborting...',
|
|
68
|
+
version,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return descriptor;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function wrapRequest(OriginalRequest) {
|
|
76
|
+
class Request extends OriginalRequest {
|
|
77
|
+
constructor() {
|
|
78
|
+
super(...arguments);
|
|
79
|
+
ins.bindEmitter(this);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return Request;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function wrapConnection(OriginalConnection) {
|
|
87
|
+
class Connection extends OriginalConnection {
|
|
88
|
+
constructor() {
|
|
89
|
+
super(...arguments);
|
|
90
|
+
ins.bindEmitter(this);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
makeRequest(request, _packetType, payload) {
|
|
94
|
+
// if not a Request object (i.e. a BulkLoad), then bail
|
|
95
|
+
if (!request.parametersByName) {
|
|
96
|
+
return super.makeRequest(...arguments);
|
|
97
|
+
}
|
|
98
|
+
const span = ins.createSpan(null, 'db', 'mssql', 'query', {
|
|
99
|
+
exitSpan: true,
|
|
100
|
+
});
|
|
101
|
+
if (!span) {
|
|
102
|
+
return super.makeRequest(...arguments);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let host, port, instanceName;
|
|
106
|
+
if (typeof this.config === 'object') {
|
|
107
|
+
// http://tediousjs.github.io/tedious/api-connection.html#function_newConnection
|
|
108
|
+
host = this.config.server;
|
|
109
|
+
if (this.config.options) {
|
|
110
|
+
port = this.config.options.port;
|
|
111
|
+
instanceName = this.config.options.instanceName;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
span._setDestinationContext(getDBDestination(host, port));
|
|
115
|
+
|
|
116
|
+
let sql;
|
|
117
|
+
let preparing;
|
|
118
|
+
if (payload.parameters !== undefined) {
|
|
119
|
+
// This looks for tedious instance with `RpcRequestPayload` started
|
|
120
|
+
// since version >=v11.0.10, when RPC parameter handling was refactored
|
|
121
|
+
// (https://github.com/tediousjs/tedious/pull/1275).
|
|
122
|
+
preparing =
|
|
123
|
+
typeof payload.procedure === 'number'
|
|
124
|
+
? // tedious@16.2.0 starts using stored procedure *IDs*
|
|
125
|
+
// (https://github.com/tediousjs/tedious/pull/1327)
|
|
126
|
+
payload.procedure === 11
|
|
127
|
+
: payload.procedure === 'sp_prepare';
|
|
128
|
+
const stmtParam =
|
|
129
|
+
payload.parameters.find(({ name }) => name === 'statement') ||
|
|
130
|
+
payload.parameters.find(({ name }) => name === 'stmt');
|
|
131
|
+
sql = stmtParam ? stmtParam.value : request.sqlTextOrProcedure;
|
|
132
|
+
} else {
|
|
133
|
+
preparing = request.sqlTextOrProcedure === 'sp_prepare';
|
|
134
|
+
const params = request.parametersByName;
|
|
135
|
+
sql = (params.statement || params.stmt || {}).value;
|
|
136
|
+
}
|
|
137
|
+
span.name = sqlSummary(sql) + (preparing ? ' (prepare)' : '');
|
|
138
|
+
const dbContext = { type: 'sql', statement: sql };
|
|
139
|
+
if (instanceName) {
|
|
140
|
+
dbContext.instance = instanceName;
|
|
141
|
+
}
|
|
142
|
+
span.setDbContext(dbContext);
|
|
143
|
+
|
|
144
|
+
const origCallback = request.userCallback;
|
|
145
|
+
request.userCallback = ins.bindFunction(function tracedCallback() {
|
|
146
|
+
// TODO: captureError and setOutcome on err first arg here
|
|
147
|
+
span.end();
|
|
148
|
+
if (origCallback) {
|
|
149
|
+
return origCallback.apply(this, arguments);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return super.makeRequest(...arguments);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return Connection;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// Instrument the undici module.
|
|
10
|
+
//
|
|
11
|
+
// This uses undici's diagnostics_channel support for instrumentation.
|
|
12
|
+
// https://github.com/nodejs/undici/blob/main/docs/api/DiagnosticsChannel.md
|
|
13
|
+
// Undici is also used for Node >=v18.0.0's `fetch()` implementation, via
|
|
14
|
+
// an esbuild bundle. This instrumentation is enabled if either `global.fetch`
|
|
15
|
+
// is present or `require('undici')`.
|
|
16
|
+
//
|
|
17
|
+
// Limitations:
|
|
18
|
+
// - Currently this isn't subscribing to 'undici:client:...' messages.
|
|
19
|
+
// With typical undici usage a connection will only be initiated for a
|
|
20
|
+
// request. However, if a user manually does `client.connect(...)` then it is
|
|
21
|
+
// possible for this instrumentation to miss a connection error from
|
|
22
|
+
// 'undici:client:connectError'. It would eventually be nice to heuristically
|
|
23
|
+
// add 'connect' spans as children of request spans.
|
|
24
|
+
// - This doesn't instrument HTTP CONNECT, as exposed by `undici.connect(...)`.
|
|
25
|
+
// I don't think the current undici diagnostics_channel messages provide a
|
|
26
|
+
// way to watch the completion of the CONNECT request.
|
|
27
|
+
// - This hasn't been tested with `undici.upgrade()`.
|
|
28
|
+
//
|
|
29
|
+
// Some notes on if/when we want to collect some HTTP client metrics:
|
|
30
|
+
// - The time between 'undici:client:connected' and 'undici:client:sendHeaders'
|
|
31
|
+
// could be a measure of client-side latency. I'm not sure if client-side
|
|
32
|
+
// queueing of requests would show a time gap here.
|
|
33
|
+
// - The time between 'undici:client:sendHeaders' and 'undici:client:bodySent'
|
|
34
|
+
// might be interesting for large bodies, or perhaps for streaming requests.
|
|
35
|
+
// - The time between 'undici:client:bodySent' and 'undici:request:headers'
|
|
36
|
+
// could be a measure of response TTFB latency.
|
|
37
|
+
|
|
38
|
+
let diagch = null;
|
|
39
|
+
try {
|
|
40
|
+
diagch = require('diagnostics_channel');
|
|
41
|
+
} catch (_importErr) {
|
|
42
|
+
// pass
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const semver = require('semver');
|
|
46
|
+
|
|
47
|
+
// Search an undici@5 request.headers string for a 'traceparent' header.
|
|
48
|
+
const headersStrHasTraceparentRe = /^traceparent:/im;
|
|
49
|
+
|
|
50
|
+
let isInstrumented = false;
|
|
51
|
+
let spanFromReq = null;
|
|
52
|
+
let chans = null;
|
|
53
|
+
|
|
54
|
+
// Get the content-length from undici response headers.
|
|
55
|
+
// `headers` is an Array of buffers: [k, v, k, v, ...].
|
|
56
|
+
// If the header is not present, or has an invalid value, this returns null.
|
|
57
|
+
function contentLengthFromResponseHeaders(headers) {
|
|
58
|
+
const name = 'content-length';
|
|
59
|
+
for (let i = 0; i < headers.length; i += 2) {
|
|
60
|
+
const k = headers[i];
|
|
61
|
+
if (k.length === name.length && k.toString().toLowerCase() === name) {
|
|
62
|
+
const v = Number(headers[i + 1]);
|
|
63
|
+
if (!isNaN(v)) {
|
|
64
|
+
return v;
|
|
65
|
+
} else {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function uninstrumentUndici() {
|
|
74
|
+
if (!isInstrumented) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
isInstrumented = false;
|
|
78
|
+
|
|
79
|
+
spanFromReq = null;
|
|
80
|
+
chans.forEach(({ chan, onMessage }) => {
|
|
81
|
+
chan.unsubscribe(onMessage);
|
|
82
|
+
});
|
|
83
|
+
chans = null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Setup instrumentation for undici. The instrumentation is based entirely on
|
|
88
|
+
* diagnostics_channel usage, so no reference to the loaded undici module is
|
|
89
|
+
* required.
|
|
90
|
+
*/
|
|
91
|
+
function instrumentUndici(agent) {
|
|
92
|
+
if (isInstrumented) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
isInstrumented = true;
|
|
96
|
+
|
|
97
|
+
const ins = agent._instrumentation;
|
|
98
|
+
spanFromReq = new WeakMap();
|
|
99
|
+
|
|
100
|
+
// Keep ref to avoid https://github.com/nodejs/node/issues/42170 bug and for
|
|
101
|
+
// unsubscribing.
|
|
102
|
+
chans = [];
|
|
103
|
+
function diagchSub(name, onMessage) {
|
|
104
|
+
const chan = diagch.channel(name);
|
|
105
|
+
chan.subscribe(onMessage);
|
|
106
|
+
chans.push({
|
|
107
|
+
name,
|
|
108
|
+
chan,
|
|
109
|
+
onMessage,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
diagchSub('undici:request:create', ({ request }) => {
|
|
114
|
+
// We do not handle instrumenting HTTP CONNECT. See limitation notes above.
|
|
115
|
+
if (request.method === 'CONNECT') {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const url = new URL(request.origin);
|
|
120
|
+
const span = ins.createSpan(
|
|
121
|
+
`${request.method} ${url.host}`,
|
|
122
|
+
'external',
|
|
123
|
+
'http',
|
|
124
|
+
request.method,
|
|
125
|
+
{ exitSpan: true },
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// W3C trace-context propagation.
|
|
129
|
+
// If the span is null (e.g. hit `transactionMaxSpans`, unsampled
|
|
130
|
+
// transaction), then fallback to the current run context's span or
|
|
131
|
+
// transaction, if any.
|
|
132
|
+
const parentRunContext = ins.currRunContext();
|
|
133
|
+
const propSpan =
|
|
134
|
+
span || parentRunContext.currSpan() || parentRunContext.currTransaction();
|
|
135
|
+
if (propSpan) {
|
|
136
|
+
// Guard against adding a duplicate 'traceparent' header, because that
|
|
137
|
+
// breaks ES. https://github.com/elastic/apm-agent-nodejs/issues/3964
|
|
138
|
+
// Dev Note: This cheats a little and assumes the header names to add
|
|
139
|
+
// will include 'traceparent'.
|
|
140
|
+
let alreadyHasTp = false;
|
|
141
|
+
if (Array.isArray(request.headers)) {
|
|
142
|
+
// undici@6
|
|
143
|
+
for (let i = 0; i < request.headers.length; i += 2) {
|
|
144
|
+
if (request.headers[i].toLowerCase() === 'traceparent') {
|
|
145
|
+
alreadyHasTp = true;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} else if (typeof request.headers === 'string') {
|
|
150
|
+
// undici@5
|
|
151
|
+
alreadyHasTp = headersStrHasTraceparentRe.test(request.headers);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!alreadyHasTp) {
|
|
155
|
+
propSpan.propagateTraceContextHeaders(
|
|
156
|
+
request,
|
|
157
|
+
function (req, name, value) {
|
|
158
|
+
if (typeof request.addHeader === 'function') {
|
|
159
|
+
req.addHeader(name, value);
|
|
160
|
+
} else if (Array.isArray(request.headers)) {
|
|
161
|
+
// undici@6.11.0 accidentally, briefly removed `request.addHeader()`.
|
|
162
|
+
req.headers.push(name, value);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (span) {
|
|
170
|
+
spanFromReq.set(request, span);
|
|
171
|
+
|
|
172
|
+
// Set some initial HTTP context, in case the request errors out before a response.
|
|
173
|
+
span.setHttpContext({
|
|
174
|
+
method: request.method,
|
|
175
|
+
url: request.origin + request.path,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const destContext = {
|
|
179
|
+
address: url.hostname,
|
|
180
|
+
};
|
|
181
|
+
const port =
|
|
182
|
+
Number(url.port) ||
|
|
183
|
+
(url.protocol === 'https:' && 443) ||
|
|
184
|
+
(url.protocol === 'http:' && 80);
|
|
185
|
+
if (port) {
|
|
186
|
+
destContext.port = port;
|
|
187
|
+
}
|
|
188
|
+
span._setDestinationContext(destContext);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
diagchSub('undici:request:headers', ({ request, response }) => {
|
|
193
|
+
const span = spanFromReq.get(request);
|
|
194
|
+
if (span !== undefined) {
|
|
195
|
+
// We are currently *not* capturing response headers, even though the
|
|
196
|
+
// intake API does allow it, because none of the other `setHttpContext`
|
|
197
|
+
// uses currently do.
|
|
198
|
+
|
|
199
|
+
const httpContext = {
|
|
200
|
+
method: request.method,
|
|
201
|
+
status_code: response.statusCode,
|
|
202
|
+
url: request.origin + request.path,
|
|
203
|
+
};
|
|
204
|
+
const cLen = contentLengthFromResponseHeaders(response.headers);
|
|
205
|
+
if (cLen !== null) {
|
|
206
|
+
httpContext.response = { encoded_body_size: cLen };
|
|
207
|
+
}
|
|
208
|
+
span.setHttpContext(httpContext);
|
|
209
|
+
|
|
210
|
+
span._setOutcomeFromHttpStatusCode(response.statusCode);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
diagchSub('undici:request:trailers', ({ request }) => {
|
|
215
|
+
const span = spanFromReq.get(request);
|
|
216
|
+
if (span !== undefined) {
|
|
217
|
+
span.end();
|
|
218
|
+
spanFromReq.delete(request);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
diagchSub('undici:request:error', ({ request, error }) => {
|
|
223
|
+
const span = spanFromReq.get(request);
|
|
224
|
+
const errOpts = {};
|
|
225
|
+
if (span !== undefined) {
|
|
226
|
+
errOpts.parent = span;
|
|
227
|
+
// Cases where we won't have an undici parent span:
|
|
228
|
+
// - We've hit transactionMaxSpans.
|
|
229
|
+
// - The undici HTTP span was suppressed because it is a child of an
|
|
230
|
+
// exit span (e.g. when used as the transport for the Elasticsearch
|
|
231
|
+
// client).
|
|
232
|
+
// It might be debatable whether we want to capture the error in the
|
|
233
|
+
// latter case. This could be revisited later.
|
|
234
|
+
}
|
|
235
|
+
agent.captureError(error, errOpts);
|
|
236
|
+
if (span !== undefined) {
|
|
237
|
+
span.end();
|
|
238
|
+
spanFromReq.delete(request);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function shimUndici(undici, agent, { version, enabled }) {
|
|
244
|
+
if (!enabled) {
|
|
245
|
+
return undici;
|
|
246
|
+
}
|
|
247
|
+
if (semver.lt(version, '4.7.1')) {
|
|
248
|
+
// Undici added its diagnostics_channel messages in v4.7.0. In v4.7.1 the
|
|
249
|
+
// `request.origin` property, that we need, was added.
|
|
250
|
+
agent.logger.debug(
|
|
251
|
+
'cannot instrument undici: undici version %s is not supported',
|
|
252
|
+
version,
|
|
253
|
+
);
|
|
254
|
+
return undici;
|
|
255
|
+
}
|
|
256
|
+
if (!diagch) {
|
|
257
|
+
agent.logger.debug(
|
|
258
|
+
'cannot instrument undici: there is no "diagnostics_channel" module',
|
|
259
|
+
process.version,
|
|
260
|
+
);
|
|
261
|
+
return undici;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
instrumentUndici(agent);
|
|
265
|
+
return undici;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = shimUndici;
|
|
269
|
+
module.exports.instrumentUndici = instrumentUndici;
|
|
270
|
+
module.exports.uninstrumentUndici = uninstrumentUndici;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
var semver = require('semver');
|
|
10
|
+
|
|
11
|
+
var shimmer = require('../shimmer');
|
|
12
|
+
|
|
13
|
+
module.exports = function (ws, agent, { version, enabled }) {
|
|
14
|
+
if (!enabled) return ws;
|
|
15
|
+
if (!semver.satisfies(version, '>=1 <8')) {
|
|
16
|
+
agent.logger.debug('ws version %s not supported - aborting...', version);
|
|
17
|
+
return ws;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ins = agent._instrumentation;
|
|
21
|
+
|
|
22
|
+
agent.logger.debug('shimming ws.prototype.send function');
|
|
23
|
+
shimmer.wrap(ws.prototype, 'send', wrapSend);
|
|
24
|
+
|
|
25
|
+
return ws;
|
|
26
|
+
|
|
27
|
+
function wrapSend(orig) {
|
|
28
|
+
return function wrappedSend() {
|
|
29
|
+
agent.logger.debug('intercepted call to ws.prototype.send');
|
|
30
|
+
const span = ins.createSpan(
|
|
31
|
+
'Send WebSocket Message',
|
|
32
|
+
'websocket',
|
|
33
|
+
'send',
|
|
34
|
+
{ exitSpan: true },
|
|
35
|
+
);
|
|
36
|
+
if (!span) {
|
|
37
|
+
return orig.apply(this, arguments);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const args = Array.prototype.slice.call(arguments);
|
|
41
|
+
let cb = args[args.length - 1];
|
|
42
|
+
const onDone = function () {
|
|
43
|
+
span.end();
|
|
44
|
+
if (cb) {
|
|
45
|
+
cb.apply(this, arguments);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
if (typeof cb === 'function') {
|
|
49
|
+
args[args.length - 1] = ins.bindFunction(onDone);
|
|
50
|
+
} else {
|
|
51
|
+
cb = null;
|
|
52
|
+
args.push(ins.bindFunction(onDone));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const spanRunContext = ins.currRunContext().enterSpan(span);
|
|
56
|
+
return ins.withRunContext(spanRunContext, orig, this, ...args);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const constants = require('../constants');
|
|
10
|
+
|
|
11
|
+
const NOOP_TRANSACTION_ID = '0000000000000000';
|
|
12
|
+
const NOOP_TRACEID = '00000000000000000000000000000000';
|
|
13
|
+
const NOOP_TRACEPARENT = '00-00000000000000000000000000000-0000000000000000-00';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A do-nothing Transaction object.
|
|
17
|
+
* https://www.elastic.co/guide/en/apm/agent/nodejs/current/transaction-api.html
|
|
18
|
+
*/
|
|
19
|
+
class NoopTransaction {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.name = 'unnamed';
|
|
22
|
+
this.type = 'noop';
|
|
23
|
+
this.traceparent = NOOP_TRACEPARENT;
|
|
24
|
+
this.result = constants.RESULT_SUCCESS;
|
|
25
|
+
this.outcome = constants.OUTCOME_UNKNOWN;
|
|
26
|
+
this.ids = {
|
|
27
|
+
'trace.id': NOOP_TRACEID,
|
|
28
|
+
'transaction.id': NOOP_TRANSACTION_ID,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Unofficial properties mentioned in a comment in index.d.ts.
|
|
32
|
+
this.timestamp = Date.now();
|
|
33
|
+
this.id = NOOP_TRANSACTION_ID;
|
|
34
|
+
this.traceId = NOOP_TRACEID;
|
|
35
|
+
this.sampled = false;
|
|
36
|
+
this.ended = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Public methods.
|
|
40
|
+
setType() {}
|
|
41
|
+
setLabel() {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
addLabels() {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
setOutcome() {}
|
|
48
|
+
startSpan() {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
end() {}
|
|
52
|
+
ensureParentId() {
|
|
53
|
+
return NOOP_TRANSACTION_ID;
|
|
54
|
+
}
|
|
55
|
+
toString() {
|
|
56
|
+
return `Transaction(${this.id}, '${this.name}'${
|
|
57
|
+
this.ended ? ', ended' : ''
|
|
58
|
+
})`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Non-public methods mentioned in a comment in index.d.ts.
|
|
62
|
+
setUserContext() {}
|
|
63
|
+
setCustomContext() {}
|
|
64
|
+
setDefaultName() {}
|
|
65
|
+
setDefaultNameFromRequest() {}
|
|
66
|
+
toJSON() {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
duration() {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
setFaas() {}
|
|
73
|
+
setMessageContext() {}
|
|
74
|
+
setServiceContext() {}
|
|
75
|
+
setCloudContext() {}
|
|
76
|
+
_setOutcomeFromHttpStatusCode() {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
NoopTransaction,
|
|
81
|
+
};
|