@depup/elastic-apm-node 4.15.0-depup.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.
Files changed (154) hide show
  1. package/LICENSE +26 -0
  2. package/NOTICE.md +442 -0
  3. package/README.md +48 -0
  4. package/changes.json +78 -0
  5. package/index.d.ts +398 -0
  6. package/index.js +11 -0
  7. package/lib/InflightEventSet.js +53 -0
  8. package/lib/activation-method.js +119 -0
  9. package/lib/agent.js +941 -0
  10. package/lib/apm-client/apm-client.js +313 -0
  11. package/lib/apm-client/http-apm-client/CHANGELOG.md +271 -0
  12. package/lib/apm-client/http-apm-client/README.md +485 -0
  13. package/lib/apm-client/http-apm-client/central-config.js +41 -0
  14. package/lib/apm-client/http-apm-client/container-info.js +111 -0
  15. package/lib/apm-client/http-apm-client/detect-hostname.js +96 -0
  16. package/lib/apm-client/http-apm-client/index.js +1975 -0
  17. package/lib/apm-client/http-apm-client/logging.js +31 -0
  18. package/lib/apm-client/http-apm-client/ndjson.js +20 -0
  19. package/lib/apm-client/http-apm-client/truncate.js +434 -0
  20. package/lib/apm-client/noop-apm-client.js +73 -0
  21. package/lib/async-hooks-polyfill.js +58 -0
  22. package/lib/cloud-metadata/aws.js +175 -0
  23. package/lib/cloud-metadata/azure.js +123 -0
  24. package/lib/cloud-metadata/callback-coordination.js +159 -0
  25. package/lib/cloud-metadata/gcp.js +133 -0
  26. package/lib/cloud-metadata/index.js +175 -0
  27. package/lib/config/config.js +458 -0
  28. package/lib/config/normalizers.js +701 -0
  29. package/lib/config/schema.js +1007 -0
  30. package/lib/constants.js +35 -0
  31. package/lib/errors.js +303 -0
  32. package/lib/filters/sanitize-field-names.js +69 -0
  33. package/lib/http-request.js +249 -0
  34. package/lib/instrumentation/azure-functions.js +519 -0
  35. package/lib/instrumentation/context.js +56 -0
  36. package/lib/instrumentation/dropped-spans-stats.js +112 -0
  37. package/lib/instrumentation/elasticsearch-shared.js +63 -0
  38. package/lib/instrumentation/express-utils.js +91 -0
  39. package/lib/instrumentation/generic-span.js +322 -0
  40. package/lib/instrumentation/http-shared.js +424 -0
  41. package/lib/instrumentation/ids.js +39 -0
  42. package/lib/instrumentation/index.js +1127 -0
  43. package/lib/instrumentation/modules/@apollo/server.js +30 -0
  44. package/lib/instrumentation/modules/@aws-sdk/client-dynamodb.js +143 -0
  45. package/lib/instrumentation/modules/@aws-sdk/client-s3.js +230 -0
  46. package/lib/instrumentation/modules/@aws-sdk/client-sns.js +197 -0
  47. package/lib/instrumentation/modules/@aws-sdk/client-sqs.js +336 -0
  48. package/lib/instrumentation/modules/@elastic/elasticsearch.js +343 -0
  49. package/lib/instrumentation/modules/@hapi/hapi.js +221 -0
  50. package/lib/instrumentation/modules/@opentelemetry/api.js +86 -0
  51. package/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js +79 -0
  52. package/lib/instrumentation/modules/@redis/client/dist/lib/client/commands-queue.js +178 -0
  53. package/lib/instrumentation/modules/@redis/client/dist/lib/client/index.js +49 -0
  54. package/lib/instrumentation/modules/@smithy/smithy-client.js +198 -0
  55. package/lib/instrumentation/modules/_lambda-handler.js +40 -0
  56. package/lib/instrumentation/modules/apollo-server-core.js +49 -0
  57. package/lib/instrumentation/modules/aws-sdk/dynamodb.js +155 -0
  58. package/lib/instrumentation/modules/aws-sdk/s3.js +184 -0
  59. package/lib/instrumentation/modules/aws-sdk/sns.js +232 -0
  60. package/lib/instrumentation/modules/aws-sdk/sqs.js +361 -0
  61. package/lib/instrumentation/modules/aws-sdk.js +76 -0
  62. package/lib/instrumentation/modules/bluebird.js +93 -0
  63. package/lib/instrumentation/modules/cassandra-driver.js +280 -0
  64. package/lib/instrumentation/modules/elasticsearch.js +191 -0
  65. package/lib/instrumentation/modules/express-graphql.js +66 -0
  66. package/lib/instrumentation/modules/express-queue.js +28 -0
  67. package/lib/instrumentation/modules/express.js +162 -0
  68. package/lib/instrumentation/modules/fastify.js +172 -0
  69. package/lib/instrumentation/modules/finalhandler.js +41 -0
  70. package/lib/instrumentation/modules/generic-pool.js +85 -0
  71. package/lib/instrumentation/modules/graphql.js +256 -0
  72. package/lib/instrumentation/modules/handlebars.js +22 -0
  73. package/lib/instrumentation/modules/http.js +112 -0
  74. package/lib/instrumentation/modules/http2.js +320 -0
  75. package/lib/instrumentation/modules/https.js +68 -0
  76. package/lib/instrumentation/modules/ioredis.js +94 -0
  77. package/lib/instrumentation/modules/jade.js +18 -0
  78. package/lib/instrumentation/modules/kafkajs.js +476 -0
  79. package/lib/instrumentation/modules/knex.js +91 -0
  80. package/lib/instrumentation/modules/koa-router.js +74 -0
  81. package/lib/instrumentation/modules/koa.js +15 -0
  82. package/lib/instrumentation/modules/memcached.js +99 -0
  83. package/lib/instrumentation/modules/mimic-response.js +45 -0
  84. package/lib/instrumentation/modules/mongodb/lib/cmap/connection_pool.js +40 -0
  85. package/lib/instrumentation/modules/mongodb-core.js +206 -0
  86. package/lib/instrumentation/modules/mongodb.js +259 -0
  87. package/lib/instrumentation/modules/mysql.js +200 -0
  88. package/lib/instrumentation/modules/mysql2.js +140 -0
  89. package/lib/instrumentation/modules/pg.js +148 -0
  90. package/lib/instrumentation/modules/pug.js +18 -0
  91. package/lib/instrumentation/modules/redis.js +176 -0
  92. package/lib/instrumentation/modules/restify.js +52 -0
  93. package/lib/instrumentation/modules/tedious.js +159 -0
  94. package/lib/instrumentation/modules/undici.js +270 -0
  95. package/lib/instrumentation/modules/ws.js +59 -0
  96. package/lib/instrumentation/noop-transaction.js +81 -0
  97. package/lib/instrumentation/run-context/AbstractRunContextManager.js +215 -0
  98. package/lib/instrumentation/run-context/AsyncHooksRunContextManager.js +106 -0
  99. package/lib/instrumentation/run-context/AsyncLocalStorageRunContextManager.js +73 -0
  100. package/lib/instrumentation/run-context/BasicRunContextManager.js +82 -0
  101. package/lib/instrumentation/run-context/RunContext.js +151 -0
  102. package/lib/instrumentation/run-context/index.js +23 -0
  103. package/lib/instrumentation/shimmer.js +123 -0
  104. package/lib/instrumentation/span-compression.js +239 -0
  105. package/lib/instrumentation/span.js +621 -0
  106. package/lib/instrumentation/template-shared.js +43 -0
  107. package/lib/instrumentation/timer.js +84 -0
  108. package/lib/instrumentation/transaction.js +571 -0
  109. package/lib/lambda.js +992 -0
  110. package/lib/load-source-map.js +100 -0
  111. package/lib/logging.js +212 -0
  112. package/lib/metrics/index.js +92 -0
  113. package/lib/metrics/platforms/generic/index.js +40 -0
  114. package/lib/metrics/platforms/generic/process-cpu.js +22 -0
  115. package/lib/metrics/platforms/generic/process-top.js +157 -0
  116. package/lib/metrics/platforms/generic/stats.js +34 -0
  117. package/lib/metrics/platforms/generic/system-cpu.js +51 -0
  118. package/lib/metrics/platforms/linux/index.js +19 -0
  119. package/lib/metrics/platforms/linux/stats.js +213 -0
  120. package/lib/metrics/queue.js +90 -0
  121. package/lib/metrics/registry.js +52 -0
  122. package/lib/metrics/reporter.js +119 -0
  123. package/lib/metrics/runtime.js +77 -0
  124. package/lib/middleware/connect.js +16 -0
  125. package/lib/opentelemetry-bridge/OTelBridgeNonRecordingSpan.js +150 -0
  126. package/lib/opentelemetry-bridge/OTelBridgeRunContext.js +124 -0
  127. package/lib/opentelemetry-bridge/OTelContextManager.js +82 -0
  128. package/lib/opentelemetry-bridge/OTelSpan.js +344 -0
  129. package/lib/opentelemetry-bridge/OTelTracer.js +201 -0
  130. package/lib/opentelemetry-bridge/OTelTracerProvider.js +25 -0
  131. package/lib/opentelemetry-bridge/README.md +244 -0
  132. package/lib/opentelemetry-bridge/index.js +15 -0
  133. package/lib/opentelemetry-bridge/oblog.js +23 -0
  134. package/lib/opentelemetry-bridge/opentelemetry-core-mini/README.md +3 -0
  135. package/lib/opentelemetry-bridge/opentelemetry-core-mini/internal/validators.js +52 -0
  136. package/lib/opentelemetry-bridge/opentelemetry-core-mini/trace/TraceState.js +109 -0
  137. package/lib/opentelemetry-bridge/otelutils.js +99 -0
  138. package/lib/opentelemetry-bridge/setup.js +76 -0
  139. package/lib/opentelemetry-metrics/ElasticApmMetricExporter.js +285 -0
  140. package/lib/opentelemetry-metrics/index.js +50 -0
  141. package/lib/parsers.js +225 -0
  142. package/lib/propwrap.js +147 -0
  143. package/lib/stacktraces.js +537 -0
  144. package/lib/symbols.js +15 -0
  145. package/lib/tracecontext/index.js +118 -0
  146. package/lib/tracecontext/traceparent.js +185 -0
  147. package/lib/tracecontext/tracestate.js +388 -0
  148. package/lib/wildcard-matcher.js +52 -0
  149. package/loader.mjs +7 -0
  150. package/package.json +299 -0
  151. package/start.d.ts +8 -0
  152. package/start.js +29 -0
  153. package/types/aws-lambda.d.ts +98 -0
  154. package/types/connect.d.ts +23 -0
@@ -0,0 +1,150 @@
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 otel = require('@opentelemetry/api');
10
+
11
+ const agent = require('../..');
12
+ const { OUTCOME_UNKNOWN } = require('../constants');
13
+ const { traceparentStrFromOTelSpanContext } = require('./otelutils');
14
+
15
+ // This class is used to handle OTel's concept of a `NonRecordingSpan` -- a
16
+ // span that is never sent/exported, but can carry SpanContext (i.e. W3C
17
+ // trace-context) that should be propagated. For a use case, see:
18
+ // "test/opentelemetry-bridge/fixtures/nonrecordingspan-parent.js"
19
+ //
20
+ // This masquerades as a Transaction on the agent's internal run-context
21
+ // tracking. Therefore it needs to support enough of Transaction's interface
22
+ // for that to work.
23
+ //
24
+ // This also needs to support enough of OTel API's `interface Span` -- mostly
25
+ // mimicking the behavior of OTel's internal `NonRecordingSpan`:
26
+ // https://github.com/open-telemetry/opentelemetry-js-api/blob/main/src/trace/NonRecordingSpan.ts
27
+ class OTelBridgeNonRecordingSpan {
28
+ constructor(otelNonRecordingSpan) {
29
+ this._spanContext = otelNonRecordingSpan.spanContext();
30
+ this.name = '';
31
+ this.type = null;
32
+ this.subtype = null;
33
+ this.action = null;
34
+ this.outcome = OUTCOME_UNKNOWN;
35
+ this.ended = false;
36
+ this.result = '';
37
+ }
38
+
39
+ get id() {
40
+ return this._spanContext.spanId;
41
+ }
42
+
43
+ get traceparent() {
44
+ return traceparentStrFromOTelSpanContext(this._spanContext);
45
+ }
46
+
47
+ get ids() {
48
+ return {
49
+ 'trace.id': this._spanContext.traceId,
50
+ 'transaction.id': this._spanContext.spanId,
51
+ };
52
+ }
53
+
54
+ setType() {}
55
+
56
+ setLabel(_key, _value, _stringify) {
57
+ return false;
58
+ }
59
+
60
+ addLabels(_labels, _stringify) {
61
+ return false;
62
+ }
63
+
64
+ setOutcome(_outcome) {}
65
+
66
+ startSpan() {
67
+ return null;
68
+ }
69
+
70
+ ensureParentId() {
71
+ return '';
72
+ }
73
+
74
+ // ---- private class Transaction API
75
+ // Only the parts of that API that are used on instances of this class.
76
+
77
+ createSpan() {
78
+ return null;
79
+ }
80
+
81
+ // GenericSpan#propagateTraceContextHeaders()
82
+ //
83
+ // Implementation adapted from OTel's W3CTraceContextPropagator#inject().
84
+ propagateTraceContextHeaders(carrier, setter) {
85
+ if (!carrier || !setter) {
86
+ return;
87
+ }
88
+ if (!this._spanContext || !otel.isSpanContextValid(this._spanContext)) {
89
+ return;
90
+ }
91
+
92
+ const traceparentStr = traceparentStrFromOTelSpanContext(this._spanContext);
93
+ setter(carrier, 'traceparent', traceparentStr);
94
+ if (agent._conf.useElasticTraceparentHeader) {
95
+ setter(carrier, 'elastic-apm-traceparent', traceparentStr);
96
+ }
97
+
98
+ if (this._spanContext.traceState) {
99
+ setter(carrier, 'tracestate', this._spanContext.traceState.serialize());
100
+ }
101
+ }
102
+
103
+ // ---- OTel interface Span
104
+ // Implementation adapted from opentelemetry-js-api/src/trace/NonRecordingSpan.ts
105
+
106
+ spanContext() {
107
+ return this._spanContext;
108
+ }
109
+
110
+ setAttribute(_key, _value) {
111
+ return this;
112
+ }
113
+
114
+ setAttributes(_attributes) {
115
+ return this;
116
+ }
117
+
118
+ addEvent(_name, _attributes) {
119
+ return this;
120
+ }
121
+
122
+ setStatus(_status) {
123
+ return this;
124
+ }
125
+
126
+ updateName(_name) {
127
+ return this;
128
+ }
129
+
130
+ addLink(_link) {
131
+ return this;
132
+ }
133
+
134
+ addLinks(_links) {
135
+ return this;
136
+ }
137
+
138
+ end(_endTime) {}
139
+
140
+ // isRecording always returns false for NonRecordingSpan.
141
+ isRecording() {
142
+ return false;
143
+ }
144
+
145
+ recordException(_exception, _time) {}
146
+ }
147
+
148
+ module.exports = {
149
+ OTelBridgeNonRecordingSpan,
150
+ };
@@ -0,0 +1,124 @@
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 otel = require('@opentelemetry/api');
10
+
11
+ const oblog = require('./oblog');
12
+ const { OTelBridgeNonRecordingSpan } = require('./OTelBridgeNonRecordingSpan');
13
+ const { OTelSpan } = require('./OTelSpan');
14
+ const { RunContext } = require('../instrumentation/run-context');
15
+ const Span = require('../instrumentation/span');
16
+
17
+ const OTEL_CONTEXT_KEY = otel.createContextKey(
18
+ 'Elastic APM Context Key OTEL CONTEXT',
19
+ );
20
+ let SPAN_KEY = null;
21
+
22
+ // `fetchSpanKey()` is called once during OTel SDK setup to get the `SPAN_KEY`
23
+ // that will be used by the OTel JS API during tracing -- when
24
+ // `otel.trace.setSpan(context, span)` et al are called.
25
+ //
26
+ // The fetched SPAN_KEY is used later by OTelBridgeRunContext to intercept
27
+ // `Context.{get,set,delete}Value` and translate to the agent's internal
28
+ // RunContext semantics for controlling the active/current span.
29
+ function fetchSpanKey() {
30
+ const capturingContext = {
31
+ spanKey: null,
32
+ setValue(key, _value) {
33
+ this.spanKey = key;
34
+ },
35
+ };
36
+ const fakeSpan = {};
37
+ otel.trace.setSpan(capturingContext, fakeSpan);
38
+ SPAN_KEY = capturingContext.spanKey;
39
+ if (!SPAN_KEY) {
40
+ throw new Error('could not fetch OTel API "SPAN_KEY"');
41
+ }
42
+ }
43
+
44
+ // This is a subclass of RunContext that is used when the agent's OTel SDK
45
+ // is enabled. It bridges between the OTel API's `Context` and the agent's
46
+ // `RunContext`.
47
+ //
48
+ // 1. It bridges between `<Context>.setValue(SPAN_KEY, ...)`, `.getValue(SPAN_KEY)`
49
+ // used by the OTel API and the RunContext methods used to track the current
50
+ // transaction and span.
51
+ // 2. It can propagate an OTel API `Context` instance (e.g. the internal
52
+ // `BaseContext` that is exposed by `otel.ROOT_CONTEXT`) and proxy
53
+ // `.getValue(key)` calls to it. See `OTEL_CONTEXT_KEY` below.
54
+ class OTelBridgeRunContext extends RunContext {
55
+ setOTelContext(otelContext) {
56
+ // First, save the `Context` instance in case it holds keys other than SPAN_KEY.
57
+ let runContext = this.setValue(OTEL_CONTEXT_KEY, otelContext);
58
+ // Second, if the `Context` holds a span, then pass that to our `setValue`
59
+ // that knows how to translate that to RunContext semantics.
60
+ const span = otel.trace.getSpan(otelContext);
61
+ if (span) {
62
+ runContext = runContext.setValue(SPAN_KEY, span);
63
+ }
64
+ return runContext;
65
+ }
66
+
67
+ getValue(key) {
68
+ oblog.apicall('OTelBridgeRunContext.getValue(%o)', key);
69
+ if (key === SPAN_KEY) {
70
+ const curr = this.currSpan() || this.currTransaction();
71
+ if (!curr) {
72
+ return undefined;
73
+ } else if (curr instanceof OTelBridgeNonRecordingSpan) {
74
+ return curr;
75
+ } else {
76
+ return new OTelSpan(curr);
77
+ }
78
+ }
79
+ const value = super.getValue(key);
80
+ if (value !== undefined) {
81
+ return value;
82
+ } else {
83
+ // Fallback to possibly-stashed OTel API Context instance.
84
+ const otelContext = super.getValue(OTEL_CONTEXT_KEY);
85
+ if (otelContext) {
86
+ return otelContext.getValue(key);
87
+ }
88
+ }
89
+ }
90
+
91
+ setValue(key, value) {
92
+ oblog.apicall('OTelBridgeRunContext.setValue(%o, %s)', key, value);
93
+ if (key === SPAN_KEY) {
94
+ if (value instanceof OTelSpan) {
95
+ if (value._span instanceof Span) {
96
+ return this.enterSpan(value._span);
97
+ } else {
98
+ // assert(value._span instanceof Transaction || value._span instanceof OTelBridgeNonRecordingSpan)
99
+ return this.enterTrans(value._span);
100
+ }
101
+ } else if (
102
+ typeof value.isRecording === 'function' &&
103
+ !value.isRecording()
104
+ ) {
105
+ return this.enterTrans(new OTelBridgeNonRecordingSpan(value));
106
+ }
107
+ }
108
+ return super.setValue(key, value);
109
+ }
110
+
111
+ deleteValue(key) {
112
+ oblog.apicall('OTelBridgeRunContext.deleteValue(%o)', key);
113
+ if (key === SPAN_KEY) {
114
+ return this.leaveTrans();
115
+ }
116
+ // TODO: Should perhaps proxy deleteValue(key) to the possible underlying OTEL_CONTEXT_KEY entry.
117
+ return super.deleteValue(key);
118
+ }
119
+ }
120
+
121
+ module.exports = {
122
+ fetchSpanKey,
123
+ OTelBridgeRunContext,
124
+ };
@@ -0,0 +1,82 @@
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 EventEmitter = require('events');
10
+ const oblog = require('./oblog');
11
+ const { OTelBridgeRunContext } = require('./OTelBridgeRunContext');
12
+
13
+ // Implements interface ContextManager from:
14
+ // https://github.com/open-telemetry/opentelemetry-js-api/blob/v1.0.4/src/context/types.ts#L43
15
+ class OTelContextManager {
16
+ constructor(agent) {
17
+ this._agent = agent;
18
+ this._ins = agent._instrumentation;
19
+ }
20
+
21
+ active() {
22
+ oblog.apicall('OTelContextManager.active()');
23
+ return this._ins.currRunContext();
24
+ }
25
+
26
+ _runContextFromOTelContext(otelContext) {
27
+ let runContext;
28
+ if (otelContext instanceof OTelBridgeRunContext) {
29
+ runContext = otelContext;
30
+ } else {
31
+ // `otelContext` is some object implementing OTel's `interface Context`
32
+ // (typically a `BaseContext` from @opentelemetry/api). We derive a new
33
+ // OTelBridgeRunContext from the root run-context that properly uses
34
+ // the Context.
35
+ runContext = this._ins._runCtxMgr.root().setOTelContext(otelContext);
36
+ }
37
+ return runContext;
38
+ }
39
+
40
+ with(otelContext, fn, thisArg, ...args) {
41
+ oblog.apicall(
42
+ 'OTelContextManager.with(%s<...>, function %s, ...)',
43
+ otelContext.constructor.name,
44
+ fn.name || '<anonymous>',
45
+ );
46
+ const runContext = this._runContextFromOTelContext(otelContext);
47
+ return this._ins._runCtxMgr.with(runContext, fn, thisArg, ...args);
48
+ }
49
+
50
+ bind(otelContext, target) {
51
+ oblog.apicall(
52
+ 'OTelContextManager.bind(%s, %s type)',
53
+ otelContext,
54
+ typeof target,
55
+ );
56
+ if (target instanceof EventEmitter) {
57
+ const runContext = this._runContextFromOTelContext(otelContext);
58
+ return this._ins._runCtxMgr.bindEE(runContext, target);
59
+ }
60
+ if (typeof target === 'function') {
61
+ const runContext = this._runContextFromOTelContext(otelContext);
62
+ return this._ins._runCtxMgr.bindFn(runContext, target);
63
+ }
64
+ return target;
65
+ }
66
+
67
+ enable() {
68
+ oblog.apicall('OTelContextManager.enable()');
69
+ this._ins._runCtxMgr.enable();
70
+ return this;
71
+ }
72
+
73
+ disable() {
74
+ oblog.apicall('OTelContextManager.disable()');
75
+ this._ins._runCtxMgr.disable();
76
+ return this;
77
+ }
78
+ }
79
+
80
+ module.exports = {
81
+ OTelContextManager,
82
+ };
@@ -0,0 +1,344 @@
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 assert = require('assert');
10
+ const URL = require('url').URL;
11
+
12
+ const otel = require('@opentelemetry/api');
13
+
14
+ const GenericSpan = require('../instrumentation/generic-span');
15
+ const oblog = require('./oblog');
16
+ const {
17
+ otelSpanContextFromTraceContext,
18
+ epochMsFromOTelTimeInput,
19
+ } = require('./otelutils');
20
+ const {
21
+ RESULT_SUCCESS,
22
+ OUTCOME_UNKNOWN,
23
+ OUTCOME_SUCCESS,
24
+ RESULT_FAILURE,
25
+ OUTCOME_FAILURE,
26
+ } = require('../constants');
27
+ const Span = require('../instrumentation/span');
28
+ const Transaction = require('../instrumentation/transaction');
29
+
30
+ // Based on `isHomogeneousAttributeValueArray` from
31
+ // packages/opentelemetry-core/src/common/attributes.ts
32
+ function isHomogeneousArrayOfStrNumBool(arr) {
33
+ const len = arr.length;
34
+ let elemType = null;
35
+ for (let i = 0; i < len; i++) {
36
+ const elem = arr[i];
37
+ if (elem === undefined || elem === null) {
38
+ continue;
39
+ }
40
+ if (!elemType) {
41
+ elemType = typeof elem;
42
+ if (
43
+ !(
44
+ elemType === 'string' ||
45
+ elemType === 'number' ||
46
+ elemType === 'boolean'
47
+ )
48
+ ) {
49
+ return false;
50
+ }
51
+ } else if (typeof elem !== elemType) {
52
+ return false;
53
+ }
54
+ }
55
+ return true;
56
+ }
57
+
58
+ // Set the given attribute key `k` and `v` according to these OTel rules:
59
+ // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/common/README.md#attribute
60
+ function maybeSetOTelAttr(attrs, k, v) {
61
+ if (Array.isArray(v)) {
62
+ // Is it homogeneous? Nulls and undefineds are allowed.
63
+ if (isHomogeneousArrayOfStrNumBool(v)) {
64
+ attrs[k] = v.slice();
65
+ }
66
+ } else {
67
+ switch (typeof v) {
68
+ case 'number':
69
+ case 'boolean':
70
+ attrs[k] = v;
71
+ break;
72
+ case 'string':
73
+ // Truncation (at 1024 bytes) is done in elastic-apm-http-client.
74
+ attrs[k] = v;
75
+ break;
76
+ }
77
+ }
78
+ }
79
+
80
+ // This wraps a core Transaction or Span in the OTel API's `inteface Span`.
81
+ class OTelSpan {
82
+ constructor(span) {
83
+ assert(span instanceof GenericSpan);
84
+ this._span = span;
85
+ this._spanContext = null;
86
+ }
87
+
88
+ toString() {
89
+ return `OTelSpan<${this._span.constructor.name}<${this._span.id}, "${this._span.name}">>`;
90
+ }
91
+
92
+ // ---- OTel interface Span
93
+ // https://github.com/open-telemetry/opentelemetry-js-api/blob/v1.0.4/src/trace/span.ts
94
+
95
+ spanContext() {
96
+ oblog.apicall('%s.spanContext()', this);
97
+ if (!this._spanContext) {
98
+ this._spanContext = otelSpanContextFromTraceContext(this._span._context);
99
+ }
100
+ return this._spanContext;
101
+ }
102
+
103
+ setAttribute(key, value) {
104
+ if (this._span.ended || !key || typeof key !== 'string') {
105
+ return this;
106
+ }
107
+
108
+ const attrs = this._span._getOTelAttributes();
109
+ maybeSetOTelAttr(attrs, key, value);
110
+
111
+ return this;
112
+ }
113
+
114
+ setAttributes(attributes) {
115
+ if (this._span.ended || !attributes || typeof attributes !== 'object') {
116
+ return this;
117
+ }
118
+
119
+ const attrs = this._span._getOTelAttributes();
120
+ for (const k in attributes) {
121
+ if (k.length === 0) continue;
122
+ maybeSetOTelAttr(attrs, k, attributes[k]);
123
+ }
124
+
125
+ return this;
126
+ }
127
+
128
+ // Span events are not currently supported.
129
+ addEvent(name, attributesOrStartTime, startTime) {
130
+ return this;
131
+ }
132
+
133
+ setStatus(otelSpanStatus) {
134
+ if (this._span.ended) {
135
+ return this;
136
+ }
137
+ switch (otelSpanStatus) {
138
+ case otel.SpanStatusCode.ERROR:
139
+ this._span.setOutcome(OUTCOME_FAILURE);
140
+ break;
141
+ case otel.SpanStatusCode.OK:
142
+ this._span.setOutcome(OUTCOME_SUCCESS);
143
+ break;
144
+ case otel.SpanStatusCode.UNSET:
145
+ this._span.setOutcome(OUTCOME_UNKNOWN);
146
+ break;
147
+ }
148
+ // Also set transaction.result, similar to the Java APM agent.
149
+ if (this._span instanceof Transaction) {
150
+ switch (otelSpanStatus) {
151
+ case otel.SpanStatusCode.ERROR:
152
+ this._span.result = RESULT_FAILURE;
153
+ break;
154
+ case otel.SpanStatusCode.OK:
155
+ this._span.result = RESULT_SUCCESS;
156
+ break;
157
+ }
158
+ }
159
+ return this;
160
+ }
161
+
162
+ updateName(name) {
163
+ if (this._span.ended) {
164
+ return this;
165
+ }
166
+ this._span.name = name;
167
+ return this;
168
+ }
169
+
170
+ addLink(link) {
171
+ this._span.addLink(link);
172
+ return this;
173
+ }
174
+
175
+ addLinks(links) {
176
+ this._span.addLinks(links);
177
+ return this;
178
+ }
179
+
180
+ end(otelEndTime) {
181
+ oblog.apicall('%s.end(endTime=%s)', this, otelEndTime);
182
+ const endTime =
183
+ otelEndTime === undefined
184
+ ? undefined
185
+ : epochMsFromOTelTimeInput(otelEndTime);
186
+ if (this._span instanceof Transaction) {
187
+ this._transCompatMapping();
188
+ this._span.end(undefined, endTime);
189
+ } else {
190
+ assert(this._span instanceof Span);
191
+ this._spanCompatMapping();
192
+ this._span.end(endTime);
193
+ }
194
+ }
195
+
196
+ // https://github.com/elastic/apm/blob/main/specs/agents/tracing-api-otel.md#compatibility-mapping
197
+ _transCompatMapping() {
198
+ const attrs = this._span._otelAttributes;
199
+ let type = 'unknown';
200
+ if (!attrs) {
201
+ this._span.type = type;
202
+ return;
203
+ }
204
+
205
+ const otelKind = otel.SpanKind[this._span._otelKind]; // map from string to int form
206
+ const isRpc = attrs['rpc.system'] !== undefined;
207
+ const isHttp =
208
+ attrs['http.url'] !== undefined || attrs['http.scheme'] !== undefined;
209
+ const isMessaging = attrs['messaging.system'] !== undefined;
210
+ if (otelKind === otel.SpanKind.SERVER && (isRpc || isHttp)) {
211
+ type = 'request';
212
+ } else if (otelKind === otel.SpanKind.CONSUMER && isMessaging) {
213
+ type = 'messaging';
214
+ } else {
215
+ type = 'unknown';
216
+ }
217
+ this._span.type = type;
218
+ }
219
+
220
+ // https://github.com/elastic/apm/blob/main/specs/agents/tracing-api-otel.md#compatibility-mapping
221
+ _spanCompatMapping() {
222
+ const attrs = this._span._otelAttributes;
223
+ const otelKind = otel.SpanKind[this._span._otelKind]; // map from string to int form
224
+ if (!attrs) {
225
+ if (otelKind === otel.SpanKind.INTERNAL) {
226
+ this._span.type = 'app';
227
+ this._span.subtype = 'internal';
228
+ } else {
229
+ this._span.type = 'unknown';
230
+ }
231
+ return;
232
+ }
233
+
234
+ let type;
235
+ let subtype;
236
+ let serviceTargetType = null;
237
+ let serviceTargetName = null;
238
+
239
+ const httpPortFromScheme = function (scheme) {
240
+ if (scheme === 'http') {
241
+ return 80;
242
+ } else if (scheme === 'https') {
243
+ return 443;
244
+ }
245
+ return -1;
246
+ };
247
+
248
+ // Extracts 'host:port' from URL.
249
+ const parseNetName = function (url) {
250
+ let u;
251
+ try {
252
+ u = new URL(url); // https://developer.mozilla.org/en-US/docs/Web/API/URL
253
+ } catch (_err) {
254
+ return undefined;
255
+ }
256
+ if (u.port !== '') {
257
+ return u.host; // host:port already in URL
258
+ } else {
259
+ var port = httpPortFromScheme(
260
+ u.protocol.substring(0, u.protocol.length - 1),
261
+ );
262
+ return port > 0 ? u.hostname + ':' + port : u.hostname;
263
+ }
264
+ };
265
+
266
+ let netPort = attrs['net.peer.port'] || -1;
267
+ const netPeer = attrs['net.peer.name'] || attrs['net.peer.ip'];
268
+ let netName = netPeer; // netName includes port, if provided
269
+ if (netName && netPort > 0) {
270
+ netName += ':' + netPort;
271
+ }
272
+
273
+ if (attrs['db.system']) {
274
+ type = 'db';
275
+ subtype = attrs['db.system'];
276
+ serviceTargetType = subtype;
277
+ serviceTargetName = attrs['db.name'] || null;
278
+ } else if (attrs['messaging.system']) {
279
+ type = 'messaging';
280
+ subtype = attrs['messaging.system'];
281
+ if (!netName && attrs['messaging.url']) {
282
+ netName = parseNetName(attrs['messaging.url']);
283
+ }
284
+ serviceTargetType = subtype;
285
+ serviceTargetName = attrs['messaging.destination'] || null;
286
+ } else if (attrs['rpc.system']) {
287
+ type = 'external';
288
+ subtype = attrs['rpc.system'];
289
+ serviceTargetType = subtype;
290
+ serviceTargetName = netName || attrs['rpc.service'] || null;
291
+ } else if (attrs['http.url'] || attrs['http.scheme']) {
292
+ type = 'external';
293
+ subtype = 'http';
294
+ serviceTargetType = 'http';
295
+ const httpHost = attrs['http.host'] || netPeer;
296
+ if (httpHost) {
297
+ if (netPort < 0) {
298
+ netPort = httpPortFromScheme(attrs['http.scheme']);
299
+ }
300
+ serviceTargetName = netPort < 0 ? httpHost : httpHost + ':' + netPort;
301
+ } else if (attrs['http.url']) {
302
+ serviceTargetName = parseNetName(attrs['http.url']);
303
+ }
304
+ }
305
+
306
+ if (type === undefined) {
307
+ if (otelKind === otel.SpanKind.INTERNAL) {
308
+ type = 'app';
309
+ subtype = 'internal';
310
+ } else {
311
+ type = 'unknown';
312
+ }
313
+ }
314
+
315
+ this._span.type = type;
316
+ if (subtype) {
317
+ this._span.subtype = subtype;
318
+ }
319
+ if (serviceTargetType || serviceTargetName) {
320
+ this._span.setServiceTarget(serviceTargetType, serviceTargetName);
321
+ }
322
+ }
323
+
324
+ isRecording() {
325
+ return !this._span.ended && this._span.sampled;
326
+ }
327
+
328
+ recordException(otelException, otelTime) {
329
+ const errOpts = {
330
+ parent: this._span,
331
+ captureAttributes: false,
332
+ skipOutcome: true,
333
+ };
334
+ if (otelTime !== undefined) {
335
+ errOpts.timestamp = epochMsFromOTelTimeInput(otelTime);
336
+ }
337
+
338
+ this._span._agent.captureError(otelException, errOpts);
339
+ }
340
+ }
341
+
342
+ module.exports = {
343
+ OTelSpan,
344
+ };