@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,201 @@
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 { OTelBridgeRunContext } = require('./OTelBridgeRunContext');
13
+ const { OTelSpan } = require('./OTelSpan');
14
+ const Transaction = require('../instrumentation/transaction');
15
+ const { OTelBridgeNonRecordingSpan } = require('./OTelBridgeNonRecordingSpan');
16
+ const {
17
+ epochMsFromOTelTimeInput,
18
+ otelSpanContextFromTraceContext,
19
+ traceparentStrFromOTelSpanContext,
20
+ } = require('./otelutils');
21
+ const { OUTCOME_UNKNOWN } = require('../constants');
22
+
23
+ // Implements interface Tracer from:
24
+ // https://github.com/open-telemetry/opentelemetry-js-api/blob/v1.0.4/src/trace/tracer.ts
25
+ class OTelTracer {
26
+ constructor(agent) {
27
+ this._agent = agent;
28
+ this._ins = agent._instrumentation;
29
+ }
30
+
31
+ /**
32
+ * Starts a new {@link Span}. Start the span without setting it on context.
33
+ *
34
+ * This method do NOT modify the current Context.
35
+ *
36
+ * @param name The name of the span
37
+ * @param [options] SpanOptions used for span creation
38
+ * @param [context] Context to use to extract parent
39
+ * @returns Span The newly created span
40
+ * @example
41
+ * const span = tracer.startSpan('op');
42
+ * span.setAttribute('key', 'value');
43
+ * span.end();
44
+ */
45
+ startSpan(name, otelSpanOptions = {}, otelContext = otel.context.active()) {
46
+ oblog.apicall(
47
+ 'OTelTracer.startSpan(name=%s, options=%j, context=%s)',
48
+ name,
49
+ otelSpanOptions,
50
+ otelContext,
51
+ );
52
+
53
+ // Get the parent info for the new span.
54
+ // We want to get a core Transaction or Span as a parent, when possible,
55
+ // because that is required to support the span compression feature.
56
+ let parentGenericSpan;
57
+ let parentOTelSpanContext;
58
+ if (otelSpanOptions.root) {
59
+ // Pass through: explicitly want no parent.
60
+ } else if (otelContext instanceof OTelBridgeRunContext) {
61
+ parentGenericSpan =
62
+ otelContext.currSpan() || otelContext.currTransaction();
63
+ if (parentGenericSpan instanceof OTelBridgeNonRecordingSpan) {
64
+ // This isn't a real Transaction we can use. It is a placeholder
65
+ // to propagate its SpanContext. Grab just that.
66
+ parentOTelSpanContext = parentGenericSpan.spanContext();
67
+ parentGenericSpan = null;
68
+ }
69
+ } else {
70
+ // `otelContext` is any object that is meant to satisfy `interface
71
+ // Context`. This may hold an OTel `SpanContext` that should be
72
+ // propagated.
73
+ parentOTelSpanContext = otel.trace.getSpanContext(otelContext);
74
+ }
75
+
76
+ const createOpts = {};
77
+ if (otelSpanOptions.links) {
78
+ // Span link *attributes* are not currently supported, they are silently dropped.
79
+ createOpts.links = otelSpanOptions.links
80
+ .filter(
81
+ (otelLink) =>
82
+ otelLink &&
83
+ otelLink.context &&
84
+ otel.isSpanContextValid(otelLink.context),
85
+ )
86
+ .map((otelLink) => {
87
+ return {
88
+ context: traceparentStrFromOTelSpanContext(otelLink.context),
89
+ };
90
+ });
91
+ }
92
+
93
+ // Create the new Span/Transaction.
94
+ let newTransOrSpan = null;
95
+ if (parentGenericSpan) {
96
+ // New child span.
97
+ const trans =
98
+ parentGenericSpan instanceof Transaction
99
+ ? parentGenericSpan
100
+ : parentGenericSpan.transaction;
101
+ createOpts.childOf = parentGenericSpan;
102
+ if (otelSpanOptions.startTime) {
103
+ createOpts.startTime = epochMsFromOTelTimeInput(
104
+ otelSpanOptions.startTime,
105
+ );
106
+ }
107
+ if (
108
+ otelSpanOptions.kind === otel.SpanKind.CLIENT ||
109
+ otelSpanOptions.kind === otel.SpanKind.PRODUCER
110
+ ) {
111
+ createOpts.exitSpan = true;
112
+ }
113
+ newTransOrSpan = trans.createSpan(name, createOpts);
114
+
115
+ // There might be no span, e.g. if the span is a child of an exit span. We
116
+ // have to return some OTelSpan, and we also want to propagate the
117
+ // parent's trace-context, if any.
118
+ if (!newTransOrSpan) {
119
+ return otel.trace.wrapSpanContext(
120
+ otelSpanContextFromTraceContext(parentGenericSpan._context),
121
+ );
122
+ }
123
+ } else if (
124
+ parentOTelSpanContext &&
125
+ otel.isSpanContextValid(parentOTelSpanContext)
126
+ ) {
127
+ // New continuing transaction.
128
+ // Note: This is *not* using `SpanContext.isRemote`. I am not sure if it
129
+ // is relevant unless using something, like @opentelemetry/core's
130
+ // `W3CTraceContextPropagator`, that sets `isRemote = true`. Nothing in
131
+ // @opentelemetry/api itself sets isRemote.
132
+ createOpts.childOf = traceparentStrFromOTelSpanContext(
133
+ parentOTelSpanContext,
134
+ );
135
+ if (parentOTelSpanContext.traceState) {
136
+ createOpts.tracestate = parentOTelSpanContext.traceState.serialize();
137
+ }
138
+ if (otelSpanOptions.startTime !== undefined) {
139
+ createOpts.startTime = epochMsFromOTelTimeInput(
140
+ otelSpanOptions.startTime,
141
+ );
142
+ }
143
+ newTransOrSpan = this._ins.createTransaction(name, createOpts);
144
+ } else {
145
+ // New root transaction.
146
+ if (otelSpanOptions.startTime !== undefined) {
147
+ createOpts.startTime = epochMsFromOTelTimeInput(
148
+ otelSpanOptions.startTime,
149
+ );
150
+ }
151
+ newTransOrSpan = this._ins.createTransaction(name, createOpts);
152
+ }
153
+
154
+ newTransOrSpan._setOTelKind(
155
+ otel.SpanKind[otelSpanOptions.kind || otel.SpanKind.INTERNAL],
156
+ );
157
+
158
+ // Explicitly use the higher-priority user outcome API to prevent the agent
159
+ // inferring the outcome from any reported errors or HTTP status code.
160
+ newTransOrSpan.setOutcome(OUTCOME_UNKNOWN);
161
+
162
+ const otelSpan = new OTelSpan(newTransOrSpan);
163
+ otelSpan.setAttributes(otelSpanOptions.attributes);
164
+
165
+ return otelSpan;
166
+ }
167
+
168
+ // startActiveSpan(name[, options[, context]], fn)
169
+ //
170
+ // Interface: https://github.com/open-telemetry/opentelemetry-js-api/blob/main/src/trace/tracer.ts#L41
171
+ // Adapted from: https://github.com/open-telemetry/opentelemetry-js/blob/main/packages/opentelemetry-sdk-trace-base/src/Tracer.ts
172
+ startActiveSpan(name, otelSpanOptions, otelContext, fn) {
173
+ oblog.apicall(
174
+ 'OTelTracer.startActiveSpan(name=%s, options=%j, context=%s, fn)',
175
+ name,
176
+ otelSpanOptions,
177
+ otelContext,
178
+ );
179
+
180
+ if (arguments.length < 2) {
181
+ return;
182
+ } else if (arguments.length === 2) {
183
+ fn = otelSpanOptions;
184
+ otelSpanOptions = undefined;
185
+ otelContext = undefined;
186
+ } else if (arguments.length === 3) {
187
+ fn = otelContext;
188
+ otelContext = undefined;
189
+ }
190
+
191
+ const parentContext = otelContext || otel.context.active();
192
+ const span = this.startSpan(name, otelSpanOptions, parentContext);
193
+ const contextWithSpanSet = otel.trace.setSpan(parentContext, span);
194
+
195
+ return otel.context.with(contextWithSpanSet, fn, undefined, span);
196
+ }
197
+ }
198
+
199
+ module.exports = {
200
+ OTelTracer,
201
+ };
@@ -0,0 +1,25 @@
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 oblog = require('./oblog');
10
+
11
+ class OTelTracerProvider {
12
+ // @param {OTelTracer} tracer
13
+ constructor(tracer) {
14
+ this._tracer = tracer;
15
+ }
16
+
17
+ getTracer(_name, _version, _options) {
18
+ oblog.apicall('OTelTracerProvider.getTracer(...)');
19
+ return this._tracer;
20
+ }
21
+ }
22
+
23
+ module.exports = {
24
+ OTelTracerProvider,
25
+ };
@@ -0,0 +1,244 @@
1
+ # OpenTelemetry Bridge
2
+
3
+ This document includes design / developer / maintenance notes for the
4
+ Node.js APM Agent *OpenTelemetry Bridge*.
5
+
6
+ Spec: https://github.com/elastic/apm/blob/main/specs/agents/tracing-api-otel.md
7
+
8
+ ## Maintenance
9
+
10
+ - We should release a new agent version with an updated "@opentelemetry/api"
11
+ dependency relatively soon after any new *minor* release. Otherwise a user
12
+ upgrading their "@opentelemetry/api" dep to "1.x+1", e.g. "1.2.0", will find
13
+ that the OTel Bridge which uses version "1.x", e.g. "1.1.0" or lower, does
14
+ not work.
15
+
16
+ The reason is that the OTel Bridge registers global providers (e.g.
17
+ `otel.trace.setGlobalTracerProvider`) with its version of the OTel API. When
18
+ user code attempts to *get* a tracer with **its version** of the OTel API, the
19
+ [OTel API compatibility logic](https://github.com/open-telemetry/opentelemetry-js-api/blob/v1.1.0/src/internal/semver.ts#L24-L33)
20
+ decides that using a v1.1.x Tracer with a v1.2.0 Tracer API is not compatible
21
+ and falls back to a noop implementation.
22
+
23
+
24
+ ## Development / Debugging
25
+
26
+ When doing development on, or debugging the OTel Bridge, it might be helpful to
27
+ enable logging of (almost) every `@opentelemetry/api` call into the bridge.
28
+ This is done by setting this in `lib/opentelemetry-bridge/setup.js`.
29
+
30
+ const LOG_OTEL_API_CALLS = true
31
+
32
+ It looks like this:
33
+
34
+ ```
35
+ % cd test/opentelemetry-bridge/fixtures
36
+ % ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true node -r ../../../start.js start-span.js
37
+ otelapi: OTelTracerProvider.getTracer(...)
38
+ otelapi: OTelContextManager.active()
39
+ otelapi: OTelTracer.startSpan(name=mySpan, options={}, context=OTelBridgeRunContext<>)
40
+ otelapi: OTelContextManager.active()
41
+ otelapi: OTelBridgeRunContext.getValue(Symbol(OpenTelemetry Context Key SPAN))
42
+ otelapi: OTelSpan<Transaction<52260136515317aa, "mySpan">>.end(endTime=undefined)
43
+ ```
44
+
45
+ Together with the agent's usual debug logging, this can help show how the bridge
46
+ is working.
47
+
48
+ ```
49
+ % ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true \
50
+ ELASTIC_APM_LOG_LEVEL=debug \
51
+ node -r ../../../start.js start-span.js | ecslog
52
+ ...
53
+ ```
54
+
55
+
56
+ ## Naming
57
+
58
+ In general, the following variable/class/file naming is used:
59
+
60
+ - A class that implements an OTel interface is prefixed with "OTel". For
61
+ example `class OTelSpan` implements OTel `interface Span`.
62
+ - A class that bridges between an OTel interface and an object in the APM
63
+ agent is prefixed with `OTelBridge`. For example `OTelBridgeRunContext`
64
+ bridges between an OTel `interface Context` and the APM agent's `RunContext`,
65
+ i.e. it implements both interfaces/APIs.
66
+ - A variable that holds an OpenTelemetry object is prefixed with `otel`, or
67
+ `...OTel...` if it in the middle of the var name. Some examples:
68
+ - `otelSpanOptions` holds an OTel `SpanOptions` instance
69
+ - `parentOTelSpanContext`
70
+ - `epochMsFromOTelTimeInput()` converts from an OTel `TimeInput` to a number
71
+ of milliseconds since the Unix epoch
72
+
73
+
74
+ ## Design Overview
75
+
76
+ The OpenTelemetry API is, currently, [these four interfaces](https://github.com/open-telemetry/opentelemetry-js-api/tree/main/src/api/):
77
+
78
+ - `otel.context.*` - API for managing Context, i.e. what the APM agent calls
79
+ "run context". More below.
80
+ - `otel.trace.*` - API for manipulating spans, and getting a `Tracer` to
81
+ create spans. More below.
82
+ - `otel.diag.*` - This is used to hook into internal OpenTelemetry diagnostics,
83
+ i.e. internal logging. There is very little `otel.diag` usage in
84
+ `@opentelemetry/api`, more in the SDK. The APM agent hooks up `otel.diag`
85
+ logging to its own logger **if `logLevel=trace`**.
86
+ - `otel.propagation.*` - Used for abstracting trace-context propagation
87
+ (reading/writing "traceparent" et al headers) and Baggage handling. This
88
+ isn't touched by the OTel Bridge, and shouldn't be necessary until either
89
+ the bridge supports Baggage or TextMapPropagator implementations like
90
+ `W3CTraceContextPropagator`. The APM agent implements its own internally.
91
+
92
+ In `Agent#start()`, if the `opentelemetryBridgeEnabled` config is true, then
93
+ a global [`ContextManager`](./OTelContextManager.js) and a global [`TracerProvider`](./OTelTracerProvider.js) are registered, which "enables" the bridge.
94
+
95
+ From the OTel Bridge spec:
96
+
97
+ > In order to avoid potentially complex and tedious synchronization issues
98
+ > between OTel and our existing agent implementations, the bridge implementation
99
+ > SHOULD provide an abstraction to have a single "active context" storage.
100
+
101
+ For this bridge, the agent's `RunContext` class was extended to support the
102
+ small [`interface Context`](https://github.com/open-telemetry/opentelemetry-js-api/blob/v1.1.0/src/context/types.ts#L17-L41)
103
+ API and the agent's run context managers were updated to allow passing in a
104
+ subclass of `RunContext` to use. So the "single active context storage" is
105
+ instances of [`OTelBridgeRunContext`](./OTelBridgeRunContext.js) in the agent's
106
+ usual run context managers.
107
+
108
+ The way the "active span" is tracked by the OTel API is to call
109
+ `context.setValue(SPAN_KEY, span)`. The `OTelBridgeRunContext` class translates
110
+ calls using `SPAN_KEY` into the API that the agent's RunContext class uses.
111
+ Roughly this:
112
+
113
+ - `context.setValue(SPAN_KEY, span)` -> `this.enterSpan(span)`
114
+ - `context.getValue(SPAN_KEY)` -> `return new OTelSpan(this.currSpan())`
115
+
116
+ Otherwise the `*RunContextManager` classes in the agent map very well to the
117
+ OpenTelemetry `ContextManager` interface: the [`OTelContextManager`](./OTelContextManager.js)
118
+ implementation is very straightforward.
119
+
120
+ The `@opentelemetry/api` supports two ways to create objects that are internally
121
+ implemented and do not call the registered global providers.
122
+
123
+ 1. `otel.trace.wrapSpanContext(...)` supports creating a `NonRecordingSpan` (a
124
+ class that isn't exported) instance that implements `interface Span`. [This
125
+ test fixture](../../test/opentelemetry-bridge/fixtures/nonrecordingspan-parent.js)
126
+ shows a use case. The bridge wraps this in an `OTelBridgeNonRecordingSpan`
127
+ that implements both OTel `interface Span` and the agent's Transaction API.
128
+ 2. `otel.ROOT_CONTEXT` is a singleton object (an internal `BaseContext` class
129
+ instance) that implements `interface Context` but is not created via any
130
+ bridge API. That means bridge code cannot rely on a given `context` argument
131
+ being an instance of its `OTelBridgeRunContext` class.
132
+ [This test fixtures](../../test/opentelemetry-bridge/fixtures/using-root-context.js)
133
+ shows an example.
134
+
135
+ The trickiest part of the bridge is handling these two cases, especially at
136
+ the top of `startSpan` in [`OTelTracer`](./OTelTracer.js)
137
+
138
+
139
+ ## Limitations / Differences with OpenTelemetry SDK
140
+
141
+ - The OpenTelemetry SDK defines [SpanLimits](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#span-limits).
142
+ This OpenTelemetry Bridge differs as follows:
143
+ - Attribute count is not limited. The OTel SDK defaults to a limit of 128.
144
+ (To implement this, start at `maybeSetOTelAttr` in "OTelSpan.js".)
145
+ - Attribute value strings are truncated at 1024 bytes. The OpenTelemetry SDK
146
+ uses `AttributeValueLengthLimit (Default=Infinity)`.
147
+ (We could consider using the configurable `longFieldMaxLength` for the
148
+ attribute value truncation limit, if there is a need.)
149
+ - Span events are not currently supported by this bridge.
150
+ - Span link *attributes* are not supported by the bridge (Elastic APM
151
+ supports span links, but not span link attributes).
152
+
153
+ - The OpenTelemetry Bridge spec says APM agents
154
+ ["MAY"](https://github.com/elastic/apm/blob/main/specs/agents/tracing-api-otel.md#attributes-mapping)
155
+ report OTel span attributes as spad and transaction *labels* if the upstream
156
+ APM Server is less than version 7.16. This implementation opts *not* to do
157
+ that. The OTel spec allows a larger range of types for span attributes values
158
+ than is allowed for "tags" (aka labels) in the APM Server intake API, so some
159
+ further filtering of attributes would be required.
160
+
161
+ - There is a semantic difference between this OTel Bridge and the OpenTelemetry
162
+ SDK with `span.end()` that could impact parent/child relationships of spans.
163
+ This demonstrates the different:
164
+
165
+ ```js
166
+ const otel = require('@opentelemetry/api')
167
+ const tracer = otel.trace.getTracer()
168
+ tracer.startActiveSpan('s1', s1 => {
169
+ tracer.startActiveSpan('s2', s2 => {
170
+ s2.end()
171
+ })
172
+ s1.end()
173
+ tracer.startActiveSpan('s3', s3 => {
174
+ s3.end()
175
+ })
176
+ })
177
+ ```
178
+
179
+ With the OTel SDK that will yield:
180
+
181
+ ```
182
+ span s1
183
+ `- span s2
184
+ `- span s3
185
+ ```
186
+
187
+ With the Elastic APM agent:
188
+
189
+ ```
190
+ transaction s1
191
+ `- span s2
192
+ transaction s3
193
+ ```
194
+
195
+ In current Elastic APM semantics, when a span is ended (e.g. `s1` above) it is
196
+ *no longer the current/active span in that async context*. This is historical
197
+ and allows a stack of current spans in sync code, e.g.:
198
+
199
+ ```js
200
+ const t1 = apm.startTransaction('t1')
201
+ const s2 = apm.startSpan('s2')
202
+ const s3 = apm.startSpan('s3') // s3 is a child of s2
203
+ s3.end() // s3 is no longer active (popped off the stack)
204
+ const s4 = apm.startSpan('s4') // s4 is a child of s2
205
+ s4.end()
206
+ s2.end()
207
+ t1.end()
208
+ ```
209
+
210
+ This semantic difference is not expected to be common, because it is expected
211
+ that typically OTel API user code will end a span only at the end of its
212
+ function:
213
+
214
+ ```js
215
+ tracer.startActiveSpan('mySpan', mySpan => {
216
+ // ...
217
+ mySpan.end() // .end() only at end of function block
218
+ })
219
+ ```
220
+
221
+ Note that active span context *is* properly maintained when a new async task
222
+ is created (e.g. with `setTimeout`, etc.), so the following code produces
223
+ the expected trace:
224
+
225
+ ```js
226
+ tracer.startActiveSpan('s1', s1 => {
227
+ setImmediate(() => {
228
+ tracer.startActiveSpan('s2', s2 => {
229
+ s2.end()
230
+ })
231
+ setTimeout(() => { // s1 is bound as the active span in this async task.
232
+ tracer.startActiveSpan('s3', s3 => {
233
+ s3.end()
234
+ })
235
+ }, 100)
236
+ s1.end()
237
+ })
238
+ ```
239
+
240
+ If this *does* turn out to be a common issue, the OTel semantics for span.end()
241
+ can likely be accommodated.
242
+
243
+
244
+
@@ -0,0 +1,15 @@
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 { OTelBridgeRunContext } = require('./OTelBridgeRunContext');
10
+ const { setupOTelBridge } = require('./setup');
11
+
12
+ module.exports = {
13
+ OTelBridgeRunContext,
14
+ setupOTelBridge,
15
+ };
@@ -0,0 +1,23 @@
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
+ // This is the (O)penTelemetry (B)ridge (LOG) facility.
10
+ //
11
+ // It is used for development/debugging of the OTel Bridge to emit a log line
12
+ // for (almost) every OTel API call. OTel Bridge implementations typically
13
+ // call `oblog.apicall(...)`. During development/debugging there is a block in
14
+ // "setup.js" that is enabled to turn this logging on. This should always be
15
+ // disabled for any release code.
16
+
17
+ module.exports = {
18
+ setApiCallLogFn(logFn) {
19
+ module.exports.apicall = logFn;
20
+ },
21
+
22
+ apicall() {},
23
+ };
@@ -0,0 +1,3 @@
1
+ This holds a very small subset of the `@opentelemetry/core` package, instead of
2
+ having a dependency on it. "Very small" here is just the `TraceState` class, as
3
+ opposed to a ~2.6M dependency.
@@ -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
+ // Adapted from @opentelemetry/core.
8
+
9
+ "use strict";
10
+ /*
11
+ * Copyright The OpenTelemetry Authors
12
+ *
13
+ * Licensed under the Apache License, Version 2.0 (the "License");
14
+ * you may not use this file except in compliance with the License.
15
+ * You may obtain a copy of the License at
16
+ *
17
+ * https://www.apache.org/licenses/LICENSE-2.0
18
+ *
19
+ * Unless required by applicable law or agreed to in writing, software
20
+ * distributed under the License is distributed on an "AS IS" BASIS,
21
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
+ * See the License for the specific language governing permissions and
23
+ * limitations under the License.
24
+ */
25
+ const VALID_KEY_CHAR_RANGE = '[_0-9a-z-*/]';
26
+ const VALID_KEY = `[a-z]${VALID_KEY_CHAR_RANGE}{0,255}`;
27
+ const VALID_VENDOR_KEY = `[a-z0-9]${VALID_KEY_CHAR_RANGE}{0,240}@[a-z]${VALID_KEY_CHAR_RANGE}{0,13}`;
28
+ const VALID_KEY_REGEX = new RegExp(`^(?:${VALID_KEY}|${VALID_VENDOR_KEY})$`);
29
+ const VALID_VALUE_BASE_REGEX = /^[ -~]{0,255}[!-~]$/;
30
+ const INVALID_VALUE_COMMA_EQUAL_REGEX = /,|=/;
31
+ /**
32
+ * Key is opaque string up to 256 characters printable. It MUST begin with a
33
+ * lowercase letter, and can only contain lowercase letters a-z, digits 0-9,
34
+ * underscores _, dashes -, asterisks *, and forward slashes /.
35
+ * For multi-tenant vendor scenarios, an at sign (@) can be used to prefix the
36
+ * vendor name. Vendors SHOULD set the tenant ID at the beginning of the key.
37
+ * see https://www.w3.org/TR/trace-context/#key
38
+ */
39
+ function validateKey(key) {
40
+ return VALID_KEY_REGEX.test(key);
41
+ }
42
+ exports.validateKey = validateKey;
43
+ /**
44
+ * Value is opaque string up to 256 characters printable ASCII RFC0020
45
+ * characters (i.e., the range 0x20 to 0x7E) except comma , and =.
46
+ */
47
+ function validateValue(value) {
48
+ return (VALID_VALUE_BASE_REGEX.test(value) &&
49
+ !INVALID_VALUE_COMMA_EQUAL_REGEX.test(value));
50
+ }
51
+ exports.validateValue = validateValue;
52
+ //# sourceMappingURL=validators.js.map
@@ -0,0 +1,109 @@
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
+ // Adapted from @opentelemetry/core.
8
+
9
+ "use strict";
10
+ /*
11
+ * Copyright The OpenTelemetry Authors
12
+ *
13
+ * Licensed under the Apache License, Version 2.0 (the "License");
14
+ * you may not use this file except in compliance with the License.
15
+ * You may obtain a copy of the License at
16
+ *
17
+ * https://www.apache.org/licenses/LICENSE-2.0
18
+ *
19
+ * Unless required by applicable law or agreed to in writing, software
20
+ * distributed under the License is distributed on an "AS IS" BASIS,
21
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
+ * See the License for the specific language governing permissions and
23
+ * limitations under the License.
24
+ */
25
+ const validators_1 = require("../internal/validators");
26
+ const MAX_TRACE_STATE_ITEMS = 32;
27
+ const MAX_TRACE_STATE_LEN = 512;
28
+ const LIST_MEMBERS_SEPARATOR = ',';
29
+ const LIST_MEMBER_KEY_VALUE_SPLITTER = '=';
30
+ /**
31
+ * TraceState must be a class and not a simple object type because of the spec
32
+ * requirement (https://www.w3.org/TR/trace-context/#tracestate-field).
33
+ *
34
+ * Here is the list of allowed mutations:
35
+ * - New key-value pair should be added into the beginning of the list
36
+ * - The value of any key can be updated. Modified keys MUST be moved to the
37
+ * beginning of the list.
38
+ */
39
+ class TraceState {
40
+ constructor(rawTraceState) {
41
+ this._internalState = new Map();
42
+ if (rawTraceState)
43
+ this._parse(rawTraceState);
44
+ }
45
+ set(key, value) {
46
+ // TODO: Benchmark the different approaches(map vs list) and
47
+ // use the faster one.
48
+ const traceState = this._clone();
49
+ if (traceState._internalState.has(key)) {
50
+ traceState._internalState.delete(key);
51
+ }
52
+ traceState._internalState.set(key, value);
53
+ return traceState;
54
+ }
55
+ unset(key) {
56
+ const traceState = this._clone();
57
+ traceState._internalState.delete(key);
58
+ return traceState;
59
+ }
60
+ get(key) {
61
+ return this._internalState.get(key);
62
+ }
63
+ serialize() {
64
+ return this._keys()
65
+ .reduce((agg, key) => {
66
+ agg.push(key + LIST_MEMBER_KEY_VALUE_SPLITTER + this.get(key));
67
+ return agg;
68
+ }, [])
69
+ .join(LIST_MEMBERS_SEPARATOR);
70
+ }
71
+ _parse(rawTraceState) {
72
+ if (rawTraceState.length > MAX_TRACE_STATE_LEN)
73
+ return;
74
+ this._internalState = rawTraceState
75
+ .split(LIST_MEMBERS_SEPARATOR)
76
+ .reverse() // Store in reverse so new keys (.set(...)) will be placed at the beginning
77
+ .reduce((agg, part) => {
78
+ const listMember = part.trim(); // Optional Whitespace (OWS) handling
79
+ const i = listMember.indexOf(LIST_MEMBER_KEY_VALUE_SPLITTER);
80
+ if (i !== -1) {
81
+ const key = listMember.slice(0, i);
82
+ const value = listMember.slice(i + 1, part.length);
83
+ if ((0, validators_1.validateKey)(key) && (0, validators_1.validateValue)(value)) {
84
+ agg.set(key, value);
85
+ }
86
+ else {
87
+ // TODO: Consider to add warning log
88
+ }
89
+ }
90
+ return agg;
91
+ }, new Map());
92
+ // Because of the reverse() requirement, trunc must be done after map is created
93
+ if (this._internalState.size > MAX_TRACE_STATE_ITEMS) {
94
+ this._internalState = new Map(Array.from(this._internalState.entries())
95
+ .reverse() // Use reverse same as original tracestate parse chain
96
+ .slice(0, MAX_TRACE_STATE_ITEMS));
97
+ }
98
+ }
99
+ _keys() {
100
+ return Array.from(this._internalState.keys()).reverse();
101
+ }
102
+ _clone() {
103
+ const traceState = new TraceState();
104
+ traceState._internalState = new Map(this._internalState);
105
+ return traceState;
106
+ }
107
+ }
108
+ exports.TraceState = TraceState;
109
+ //# sourceMappingURL=TraceState.js.map