dd-trace 5.108.0 → 5.110.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/ci/init.js +4 -3
- package/index.d.ts +88 -25
- package/loader-hook.mjs +88 -2
- package/package.json +19 -14
- package/packages/datadog-esbuild/index.js +26 -0
- package/packages/datadog-esbuild/src/utils.js +46 -3
- package/packages/datadog-instrumentations/src/ai.js +43 -48
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js-context-methods.js +18 -0
- package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +111 -0
- package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
- package/packages/datadog-instrumentations/src/connect.js +4 -3
- package/packages/datadog-instrumentations/src/electron.js +1 -1
- package/packages/datadog-instrumentations/src/fastify.js +4 -12
- package/packages/datadog-instrumentations/src/graphql.js +17 -6
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/aws-durable-execution-sdk-js.js +31 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
- package/packages/datadog-instrumentations/src/http/client.js +12 -2
- package/packages/datadog-instrumentations/src/ioredis.js +0 -1
- package/packages/datadog-instrumentations/src/iovalkey.js +1 -2
- package/packages/datadog-instrumentations/src/koa.js +3 -2
- package/packages/datadog-instrumentations/src/mariadb.js +4 -2
- package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
- package/packages/datadog-instrumentations/src/next.js +44 -10
- package/packages/datadog-instrumentations/src/openai.js +77 -18
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
- package/packages/datadog-instrumentations/src/playwright.js +1 -1
- package/packages/datadog-instrumentations/src/redis.js +0 -1
- package/packages/datadog-instrumentations/src/restify.js +4 -3
- package/packages/datadog-instrumentations/src/router.js +18 -5
- package/packages/datadog-instrumentations/src/selenium.js +1 -2
- package/packages/datadog-instrumentations/src/tedious.js +28 -0
- package/packages/datadog-instrumentations/src/vitest.js +323 -7
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/checkpoint.js +31 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +55 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +114 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +128 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/index.js +19 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +224 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +43 -0
- package/packages/datadog-plugin-aws-sdk/src/base.js +14 -16
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +100 -37
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +108 -99
- package/packages/datadog-plugin-bullmq/src/filter.js +35 -0
- package/packages/datadog-plugin-bullmq/src/producer.js +84 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -1
- package/packages/datadog-plugin-fs/src/index.js +1 -0
- package/packages/datadog-plugin-http/src/client.js +8 -3
- package/packages/datadog-plugin-http2/src/client.js +5 -2
- package/packages/datadog-plugin-mysql/src/index.js +7 -2
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
- package/packages/datadog-plugin-redis/src/index.js +1 -2
- package/packages/datadog-plugin-undici/src/index.js +5 -2
- package/packages/datadog-plugin-vitest/src/index.js +11 -2
- package/packages/datadog-webpack/index.js +17 -1
- package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
- package/packages/dd-trace/src/aiguard/channels.js +0 -1
- package/packages/dd-trace/src/aiguard/index.js +11 -49
- package/packages/dd-trace/src/aiguard/integrations/evaluate.js +46 -0
- package/packages/dd-trace/src/aiguard/integrations/openai.js +66 -0
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +78 -0
- package/packages/{datadog-instrumentations/src/helpers/ai-messages.js → dd-trace/src/aiguard/messages/openai.js} +85 -193
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +185 -0
- package/packages/dd-trace/src/aiguard/sdk.js +13 -1
- package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
- package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/downstream_requests.js +111 -58
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +54 -12
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +5 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +29 -4
- package/packages/dd-trace/src/appsec/index.js +10 -6
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +19 -11
- package/packages/dd-trace/src/appsec/reporter.js +1 -0
- package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +26 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
- package/packages/dd-trace/src/config/defaults.js +22 -3
- package/packages/dd-trace/src/config/generated-config-types.d.ts +538 -1
- package/packages/dd-trace/src/config/helper.js +133 -33
- package/packages/dd-trace/src/config/index.js +4 -1
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/supported-configurations.json +45 -5
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +49 -35
- package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
- package/packages/dd-trace/src/debugger/index.js +5 -0
- package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
- package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
- package/packages/dd-trace/src/dogstatsd.js +15 -8
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
- package/packages/dd-trace/src/exporters/agentless/index.js +7 -5
- package/packages/dd-trace/src/exporters/agentless/intake.js +43 -0
- package/packages/dd-trace/src/exporters/agentless/writer.js +5 -4
- package/packages/dd-trace/src/exporters/common/request.js +1 -20
- package/packages/dd-trace/src/exporters/common/url.js +34 -0
- package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
- package/packages/dd-trace/src/guardrails/index.js +8 -5
- package/packages/dd-trace/src/index.js +6 -4
- package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
- package/packages/dd-trace/src/llmobs/index.js +16 -5
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
- package/packages/dd-trace/src/llmobs/sdk.js +5 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +4 -0
- package/packages/dd-trace/src/llmobs/tagger.js +55 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
- package/packages/dd-trace/src/log/index.js +18 -20
- package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -1
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
- package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
- package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
- package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +11 -4
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
- package/packages/dd-trace/src/opentelemetry/span.js +14 -3
- package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +2 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
- package/packages/dd-trace/src/opentracing/span_context.js +17 -0
- package/packages/dd-trace/src/plugin_manager.js +7 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +36 -3
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
- package/packages/dd-trace/src/plugins/util/test.js +80 -0
- package/packages/dd-trace/src/plugins/util/url.js +68 -1
- package/packages/dd-trace/src/plugins/util/web.js +19 -36
- package/packages/dd-trace/src/priority_sampler.js +1 -13
- package/packages/dd-trace/src/profiling/config.js +129 -198
- package/packages/dd-trace/src/profiling/constants.js +4 -7
- package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
- package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
- package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
- package/packages/dd-trace/src/profiling/oom.js +71 -0
- package/packages/dd-trace/src/profiling/profiler.js +35 -23
- package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
- package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
- package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
- package/packages/dd-trace/src/ritm.js +4 -2
- package/packages/dd-trace/src/serverless.js +1 -2
- package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
- package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
- package/packages/dd-trace/src/span_format.js +2 -1
- package/packages/dd-trace/src/span_processor.js +6 -0
- package/packages/dd-trace/src/telemetry/send-data.js +12 -10
- package/packages/dd-trace/src/tracer.js +15 -1
- package/packages/dd-trace/src/util.js +14 -0
- package/register.js +58 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +0 -284
- package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { storage } = require('../../datadog-core')
|
|
4
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
5
|
+
const { addOpMeta, unwrapDurableError } = require('./util')
|
|
6
|
+
|
|
7
|
+
// Span names whose direct children must keep the default resource.
|
|
8
|
+
// These can have very high cardinality which is undesireable in the resource.
|
|
9
|
+
const HIGH_CARDINALITY_PARENT_SPAN_NAMES = new Set([
|
|
10
|
+
'aws.durable.map',
|
|
11
|
+
'aws.durable.parallel',
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
// The SDK emits these subTypes as internal scaffolding around map/parallel iterations
|
|
15
|
+
// and waitForCallback; not user-visible operations.
|
|
16
|
+
const SUPPRESSED_CHILD_CONTEXT_SUBTYPES = new Set([
|
|
17
|
+
'Map',
|
|
18
|
+
'Parallel',
|
|
19
|
+
'MapIteration',
|
|
20
|
+
'ParallelBranch',
|
|
21
|
+
'WaitForCallback',
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
class BaseContextPlugin extends TracingPlugin {
|
|
25
|
+
static id = 'aws-durable-execution-sdk-js'
|
|
26
|
+
static type = 'serverless'
|
|
27
|
+
static kind = 'internal'
|
|
28
|
+
|
|
29
|
+
constructor (...args) {
|
|
30
|
+
super(...args)
|
|
31
|
+
this.addSub(this.constructor.settleChannel, ctx => this.settle(ctx))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
bindStart (ctx) {
|
|
35
|
+
const spanName = this.constructor.spanName
|
|
36
|
+
const parentName = this.activeSpan?.context()._name
|
|
37
|
+
const operationName = this.getOperationName(ctx)
|
|
38
|
+
const resource = HIGH_CARDINALITY_PARENT_SPAN_NAMES.has(parentName) ? undefined : operationName
|
|
39
|
+
|
|
40
|
+
const meta = {}
|
|
41
|
+
if (operationName) {
|
|
42
|
+
meta['aws.durable.operation_name'] = operationName
|
|
43
|
+
}
|
|
44
|
+
addOpMeta(meta, ctx.self)
|
|
45
|
+
|
|
46
|
+
this.startSpan(spanName, {
|
|
47
|
+
resource,
|
|
48
|
+
kind: this.constructor.kind,
|
|
49
|
+
meta,
|
|
50
|
+
}, ctx)
|
|
51
|
+
|
|
52
|
+
return ctx.currentStore
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// All context methods have two overloads: method(name, …) and method(…); args[0] is the name in the first form.
|
|
56
|
+
getOperationName (ctx) {
|
|
57
|
+
const args = ctx.arguments || []
|
|
58
|
+
return typeof args[0] === 'string' ? args[0] : undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
settle (ctx) {
|
|
62
|
+
if (ctx.suppressed) return
|
|
63
|
+
if (ctx.error !== undefined) {
|
|
64
|
+
ctx.currentStore?.span?.setTag('error', unwrapDurableError(ctx))
|
|
65
|
+
}
|
|
66
|
+
this.finish(ctx)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
error (ctxOrError) {
|
|
70
|
+
this.settle(ctxOrError)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function makeContextPlugin (method, spanName) {
|
|
75
|
+
return class extends BaseContextPlugin {
|
|
76
|
+
static prefix = `tracing:orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_${method}`
|
|
77
|
+
static settleChannel = `apm:aws-durable-execution-sdk-js:${method}:settle`
|
|
78
|
+
static spanName = spanName
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class RunInChildContextPlugin extends BaseContextPlugin {
|
|
83
|
+
static prefix = 'tracing:orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_runInChildContext'
|
|
84
|
+
static settleChannel = 'apm:aws-durable-execution-sdk-js:runInChildContext:settle'
|
|
85
|
+
static spanName = 'aws.durable.child_context'
|
|
86
|
+
|
|
87
|
+
bindStart (ctx) {
|
|
88
|
+
if (SUPPRESSED_CHILD_CONTEXT_SUBTYPES.has(getRunInChildContextSubType(ctx))) {
|
|
89
|
+
// Pass the active store through unchanged so any nested spans
|
|
90
|
+
// remain parented to the surrounding map/parallel span
|
|
91
|
+
ctx.suppressed = true
|
|
92
|
+
return storage('legacy').getStore()
|
|
93
|
+
}
|
|
94
|
+
return super.bindStart(ctx)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// runInChildContext has two overloads: `(name, fn, options)` and `(fn, options)`.
|
|
99
|
+
function getRunInChildContextSubType (ctx) {
|
|
100
|
+
const args = ctx.arguments || []
|
|
101
|
+
const opts = typeof args[0] === 'function' ? args[1] : args[2]
|
|
102
|
+
return opts?.subType
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
step: makeContextPlugin('step', 'aws.durable.step'),
|
|
107
|
+
wait: makeContextPlugin('wait', 'aws.durable.wait'),
|
|
108
|
+
waitForCondition: makeContextPlugin('waitForCondition', 'aws.durable.wait_for_condition'),
|
|
109
|
+
waitForCallback: makeContextPlugin('waitForCallback', 'aws.durable.wait_for_callback'),
|
|
110
|
+
createCallback: makeContextPlugin('createCallback', 'aws.durable.create_callback'),
|
|
111
|
+
map: makeContextPlugin('map', 'aws.durable.map'),
|
|
112
|
+
parallel: makeContextPlugin('parallel', 'aws.durable.parallel'),
|
|
113
|
+
runInChildContext: RunInChildContextPlugin,
|
|
114
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../dd-trace/src/log')
|
|
4
|
+
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
5
|
+
const { saveTraceContextCheckpointIfUpdated } = require('./trace-checkpoint')
|
|
6
|
+
|
|
7
|
+
// Termination reasons that indicate the execution is suspending rather than exiting permanently.
|
|
8
|
+
// Sourced from (`@aws/durable-execution-sdk-js`'s termination-manager/types.ts).
|
|
9
|
+
const PENDING_TERMINATION_REASONS = new Set([
|
|
10
|
+
'OPERATION_TERMINATED',
|
|
11
|
+
'RETRY_SCHEDULED',
|
|
12
|
+
'RETRY_INTERRUPTED_STEP',
|
|
13
|
+
'WAIT_SCHEDULED',
|
|
14
|
+
'CALLBACK_PENDING',
|
|
15
|
+
'CUSTOM',
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
const DEFAULT_TERMINATION_REASON = 'OPERATION_TERMINATED'
|
|
19
|
+
|
|
20
|
+
// Published by the instrumentation when the SDK's terminationManager.terminate() is called.
|
|
21
|
+
// The instrumentation owns the wrapping; this plugin only reacts.
|
|
22
|
+
const TERMINATE_CHANNEL = 'apm:aws-durable-execution-sdk-js:terminate'
|
|
23
|
+
|
|
24
|
+
class AwsDurableExecutionSdkJsHandlerPlugin extends TracingPlugin {
|
|
25
|
+
static id = 'aws-durable-execution-sdk-js'
|
|
26
|
+
static type = 'serverless'
|
|
27
|
+
static kind = 'internal'
|
|
28
|
+
static prefix = 'tracing:orchestrion:@aws/durable-execution-sdk-js:withDurableExecution'
|
|
29
|
+
|
|
30
|
+
constructor (...args) {
|
|
31
|
+
super(...args)
|
|
32
|
+
// Gate the subscription on the feature flag: the instrumentation only wraps terminate() while
|
|
33
|
+
// this channel has subscribers, so not subscribing keeps the wrapping off entirely.
|
|
34
|
+
if (this._tracerConfig.DD_DURABLE_CROSS_INVOCATION_TRACING_ENABLED) {
|
|
35
|
+
this.addSub(TERMINATE_CHANNEL, ctx => this.#onTerminate(ctx))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
bindStart (ctx) {
|
|
40
|
+
const args = ctx.arguments || []
|
|
41
|
+
const event = args[0]
|
|
42
|
+
const durableExecutionMode = args[3]
|
|
43
|
+
const handler = args[5]
|
|
44
|
+
|
|
45
|
+
const meta = {
|
|
46
|
+
'aws.durable.replayed': durableExecutionMode === 'ReplayMode' ? 'true' : 'false',
|
|
47
|
+
}
|
|
48
|
+
const arn = event?.DurableExecutionArn
|
|
49
|
+
if (arn) {
|
|
50
|
+
meta['aws.durable.execution_arn'] = arn
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.startSpan(this.operationName(), {
|
|
54
|
+
resource: handler?.name,
|
|
55
|
+
kind: this.constructor.kind,
|
|
56
|
+
meta,
|
|
57
|
+
}, ctx)
|
|
58
|
+
|
|
59
|
+
return ctx.currentStore
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Fired (synchronously, before the SDK's terminate() runs) when the execution suspends. On a
|
|
63
|
+
// PENDING reason we persist the current trace context as a `_datadog` checkpoint, which
|
|
64
|
+
// subsequent invocations consume to extract the parent trace context. `ctx` is the shared
|
|
65
|
+
// withDurableExecution context: bindStart put the execute span on it, and the instrumentation
|
|
66
|
+
// put the captured durableContext and termination reason on it.
|
|
67
|
+
#onTerminate (ctx) {
|
|
68
|
+
const reason = ctx.terminationReason ?? DEFAULT_TERMINATION_REASON
|
|
69
|
+
if (!PENDING_TERMINATION_REASONS.has(reason)) return
|
|
70
|
+
void maybeSaveCheckpoint(this.tracer, ctx)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
asyncEnd (ctx) {
|
|
74
|
+
const span = ctx?.currentStore?.span
|
|
75
|
+
const status = ctx?.result?.Status
|
|
76
|
+
if (span && typeof status === 'string') {
|
|
77
|
+
span.setTag('aws.durable.invocation_status', status.toLowerCase())
|
|
78
|
+
}
|
|
79
|
+
// Operation child spans rely on user code awaiting the returned DurablePromise to settle;
|
|
80
|
+
// suspended (PENDING) ops never settle, and fire-and-forget ops on terminal handler exits
|
|
81
|
+
// are never awaited at all. Finish any still-open owned children so the trace can flush.
|
|
82
|
+
if (span) finishOpenChildSpans(span)
|
|
83
|
+
super.finish(ctx)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function finishOpenChildSpans (executeSpan) {
|
|
88
|
+
const trace = executeSpan?._spanContext?._trace
|
|
89
|
+
if (!trace?.started) return
|
|
90
|
+
|
|
91
|
+
for (const span of trace.started) {
|
|
92
|
+
if (span === executeSpan) continue
|
|
93
|
+
if (span._integrationName !== AwsDurableExecutionSdkJsHandlerPlugin.id) continue
|
|
94
|
+
if (span._duration === undefined) {
|
|
95
|
+
span.finish()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Save state is kept on the shared `ctx` so repeated terminate() calls within one execution
|
|
101
|
+
// save at most once. The execute span is also the anchor we propagate, so its span id is the
|
|
102
|
+
// `firstExecutionSpanId` passed downstream.
|
|
103
|
+
function maybeSaveCheckpoint (tracer, ctx) {
|
|
104
|
+
if (ctx.checkpointSaved || ctx.checkpointSavePromise) return ctx.checkpointSavePromise
|
|
105
|
+
|
|
106
|
+
const span = ctx.currentStore?.span
|
|
107
|
+
const durableContext = ctx.durableContext
|
|
108
|
+
if (!span || !durableContext) return
|
|
109
|
+
|
|
110
|
+
// Fire-and-forget boundary (#onTerminate calls us with `void`): swallow every failure here so a
|
|
111
|
+
// rejected checkpoint-manager call can never surface as an unhandled rejection in customer code.
|
|
112
|
+
ctx.checkpointSavePromise = saveTraceContextCheckpointIfUpdated(
|
|
113
|
+
tracer,
|
|
114
|
+
span,
|
|
115
|
+
durableContext,
|
|
116
|
+
span.context?.()?.toSpanId?.(),
|
|
117
|
+
ctx.arguments?.[0],
|
|
118
|
+
).catch(error => {
|
|
119
|
+
log.debug('Failed to save trace context checkpoint', error)
|
|
120
|
+
}).finally(() => {
|
|
121
|
+
ctx.checkpointSaved = true
|
|
122
|
+
ctx.checkpointSavePromise = undefined
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return ctx.checkpointSavePromise
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = AwsDurableExecutionSdkJsHandlerPlugin
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const CompositePlugin = require('../../dd-trace/src/plugins/composite')
|
|
4
|
+
const checkpointPlugin = require('./checkpoint')
|
|
5
|
+
const clientPlugin = require('./client')
|
|
6
|
+
const contextPlugins = require('./context')
|
|
7
|
+
const handlerPlugin = require('./handler')
|
|
8
|
+
|
|
9
|
+
class AwsDurableExecutionSdkJsPlugin extends CompositePlugin {
|
|
10
|
+
static id = 'aws-durable-execution-sdk-js'
|
|
11
|
+
static plugins = {
|
|
12
|
+
handler: handlerPlugin,
|
|
13
|
+
client: clientPlugin,
|
|
14
|
+
checkpoint: checkpointPlugin,
|
|
15
|
+
...contextPlugins,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = AwsDurableExecutionSdkJsPlugin
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto')
|
|
4
|
+
const log = require('../../dd-trace/src/log')
|
|
5
|
+
const TextMapPropagator = require('../../dd-trace/src/opentracing/propagation/text_map')
|
|
6
|
+
|
|
7
|
+
const CHECKPOINT_NAME_PREFIX = '_datadog_'
|
|
8
|
+
|
|
9
|
+
// Propagator that injects only Datadog-style headers (`x-datadog-*`) regardless of the user's
|
|
10
|
+
// `DD_TRACE_PROPAGATION_STYLE_INJECT`. Checkpoints are written and read entirely by Datadog code,
|
|
11
|
+
// so honoring user style preferences would only complicate the payload contract. AWS runs a single
|
|
12
|
+
// tracer, so one lazily-built propagator suffices.
|
|
13
|
+
let datadogOnlyPropagator
|
|
14
|
+
|
|
15
|
+
function getDatadogOnlyPropagator (tracer) {
|
|
16
|
+
if (datadogOnlyPropagator) return datadogOnlyPropagator
|
|
17
|
+
const config = tracer._config
|
|
18
|
+
// Shadow `tracePropagationStyle.inject` while inheriting every other field (x-datadog-tags length
|
|
19
|
+
// cap, etc.) from the live config. Disable `legacyBaggageEnabled` only to keep `ot-baggage-*` out
|
|
20
|
+
// of the checkpoint payload we persist (sensitive-data concern) — not a serverless-wide change.
|
|
21
|
+
const shadowConfig = Object.create(config)
|
|
22
|
+
shadowConfig.tracePropagationStyle = {
|
|
23
|
+
...config.tracePropagationStyle,
|
|
24
|
+
inject: ['datadog'],
|
|
25
|
+
}
|
|
26
|
+
shadowConfig.legacyBaggageEnabled = false
|
|
27
|
+
datadogOnlyPropagator = new TextMapPropagator(shadowConfig)
|
|
28
|
+
return datadogOnlyPropagator
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build the Datadog-format headers dict from a span context.
|
|
33
|
+
* Mirrors ddtrace-py HTTPPropagator.inject output so the same payload
|
|
34
|
+
* can be consumed by either language's datadog-lambda wrapper.
|
|
35
|
+
* @param {object} tracer
|
|
36
|
+
* @param {object} span
|
|
37
|
+
* @returns {Record<string, string>}
|
|
38
|
+
*/
|
|
39
|
+
function injectHeaders (tracer, span) {
|
|
40
|
+
const headers = {}
|
|
41
|
+
const propagator = getDatadogOnlyPropagator(tracer)
|
|
42
|
+
const ctx = typeof span?.context === 'function' ? span.context() : span
|
|
43
|
+
propagator.inject(ctx, headers)
|
|
44
|
+
return headers
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Mutate headers in-place to set parent_id to the provided value.
|
|
49
|
+
* @param {Record<string, string>} headers
|
|
50
|
+
* @param {string | number | undefined} parentId
|
|
51
|
+
*/
|
|
52
|
+
function overrideParentId (headers, parentId) {
|
|
53
|
+
if (!parentId) return
|
|
54
|
+
if (headers['x-datadog-trace-id']) {
|
|
55
|
+
headers['x-datadog-parent-id'] = String(parentId)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether the current trace context warrants a new checkpoint over the previously-saved one.
|
|
61
|
+
* @param {Record<string, string>} currentHeaders
|
|
62
|
+
* @param {Record<string, string>} previousHeaders
|
|
63
|
+
* @returns {boolean}
|
|
64
|
+
*/
|
|
65
|
+
function needsCheckpointUpdate (currentHeaders, previousHeaders) {
|
|
66
|
+
for (const key of Object.keys(currentHeaders)) {
|
|
67
|
+
if (currentHeaders[key] !== previousHeaders[key]) return true
|
|
68
|
+
}
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Find the checkpoint with the highest N for _datadog_{N} in the event's operations.
|
|
74
|
+
* @param {unknown} event
|
|
75
|
+
* @returns {{ checkpointNumber: number, operation: object } | undefined}
|
|
76
|
+
*/
|
|
77
|
+
function findLastCheckpoint (event) {
|
|
78
|
+
if (!event || typeof event !== 'object') return
|
|
79
|
+
|
|
80
|
+
const operations = event.InitialExecutionState?.Operations
|
|
81
|
+
if (!Array.isArray(operations)) return
|
|
82
|
+
|
|
83
|
+
let highest
|
|
84
|
+
for (const op of operations) {
|
|
85
|
+
const name = op?.Name
|
|
86
|
+
if (typeof name !== 'string' || !name.startsWith(CHECKPOINT_NAME_PREFIX)) continue
|
|
87
|
+
const suffix = name.slice(CHECKPOINT_NAME_PREFIX.length)
|
|
88
|
+
const checkpointNumber = Number.parseInt(suffix, 10)
|
|
89
|
+
if (Number.isNaN(checkpointNumber) || String(checkpointNumber) !== suffix) continue
|
|
90
|
+
|
|
91
|
+
if (!highest || checkpointNumber > highest.checkpointNumber) {
|
|
92
|
+
highest = { checkpointNumber, operation: op }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return highest
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Parse the JSON payload from a checkpoint STEP operation's Payload or StepDetails.Result.
|
|
101
|
+
* @param {object} op
|
|
102
|
+
* @returns {Record<string, string> | undefined}
|
|
103
|
+
*/
|
|
104
|
+
function parseCheckpointPayload (op) {
|
|
105
|
+
try {
|
|
106
|
+
const raw = op?.Payload ?? op?.StepDetails?.Result
|
|
107
|
+
if (!raw || typeof raw !== 'string') return
|
|
108
|
+
const parsed = JSON.parse(raw)
|
|
109
|
+
return parsed && typeof parsed === 'object' ? parsed : undefined
|
|
110
|
+
} catch {
|
|
111
|
+
log.debug('Failed to parse checkpoint payload')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Save a _datadog_{number} STEP operation via the SDK's checkpoint manager.
|
|
117
|
+
* Uses a deterministic blake2b hash of (name:arn) as the stepId so the save is
|
|
118
|
+
* idempotent within an execution.
|
|
119
|
+
* @param {object} checkpointManager
|
|
120
|
+
* @param {string} executionArn
|
|
121
|
+
* @param {number} number
|
|
122
|
+
* @param {Record<string, string>} headers
|
|
123
|
+
* @returns {Promise<void>}
|
|
124
|
+
*/
|
|
125
|
+
async function saveCheckpoint (checkpointManager, executionArn, number, headers) {
|
|
126
|
+
const name = `${CHECKPOINT_NAME_PREFIX}${number}`
|
|
127
|
+
const stepId = crypto
|
|
128
|
+
.createHash('blake2b512')
|
|
129
|
+
.update(`${name}:${executionArn}`)
|
|
130
|
+
.digest('hex')
|
|
131
|
+
.slice(0, 64)
|
|
132
|
+
const payload = JSON.stringify(headers)
|
|
133
|
+
|
|
134
|
+
// Queue START and SUCCEED back-to-back before awaiting. This allows callers
|
|
135
|
+
// to trigger save right before termination without losing the second update.
|
|
136
|
+
const startPromise = checkpointManager.checkpoint(stepId, {
|
|
137
|
+
Id: stepId,
|
|
138
|
+
Action: 'START',
|
|
139
|
+
Type: 'STEP',
|
|
140
|
+
SubType: 'STEP',
|
|
141
|
+
Name: name,
|
|
142
|
+
})
|
|
143
|
+
const succeedPromise = checkpointManager.checkpoint(stepId, {
|
|
144
|
+
Id: stepId,
|
|
145
|
+
Action: 'SUCCEED',
|
|
146
|
+
Type: 'STEP',
|
|
147
|
+
SubType: 'STEP',
|
|
148
|
+
Name: name,
|
|
149
|
+
Payload: payload,
|
|
150
|
+
})
|
|
151
|
+
await Promise.all([
|
|
152
|
+
startPromise,
|
|
153
|
+
succeedPromise,
|
|
154
|
+
])
|
|
155
|
+
log.debug('Saved trace context checkpoint %s', name)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Save a new trace-context checkpoint when the current context differs from
|
|
160
|
+
* the most recent `_datadog_{N}` operation already in the event.
|
|
161
|
+
*
|
|
162
|
+
* Every checkpoint across the durable execution carries the same
|
|
163
|
+
* `x-datadog-parent-id` so all resumed invocations attach to the same anchor:
|
|
164
|
+
* - First checkpoint (no previous): anchor at `firstExecutionSpanId`.
|
|
165
|
+
* - Subsequent: reuse the prior checkpoint's `x-datadog-parent-id` verbatim —
|
|
166
|
+
* that value originated from the first save and is the anchor we've been
|
|
167
|
+
* carrying forward.
|
|
168
|
+
*
|
|
169
|
+
* Caller is responsible for invoking this only when a save is appropriate — i.e.
|
|
170
|
+
* the SDK is about to return Status: PENDING (see PENDING_TERMINATION_REASONS in
|
|
171
|
+
* handler.js). This function does not re-check that.
|
|
172
|
+
*
|
|
173
|
+
* Errors (including a rejected checkpoint-manager call) propagate to the caller, which swallows
|
|
174
|
+
* them at the fire-and-forget boundary.
|
|
175
|
+
*
|
|
176
|
+
* @param {object} tracer
|
|
177
|
+
* @param {object} span - aws.durable.execute span
|
|
178
|
+
* @param {object} durableContext - SDK's DurableContextImpl
|
|
179
|
+
* @param {string | undefined} firstExecutionSpanId - span id of the first
|
|
180
|
+
* invocation's `aws.durable.execute` span. Only consulted on the very
|
|
181
|
+
* first save; ignored once a prior `_datadog_{N}` exists. We anchor at
|
|
182
|
+
* this span (which this integration owns) rather than its parent so the
|
|
183
|
+
* anchor doesn't depend on whatever upstream context happens to be
|
|
184
|
+
* active when `bindStart` fires.
|
|
185
|
+
* @param {unknown} event - raw invocation event (has InitialExecutionState)
|
|
186
|
+
* @returns {Promise<void>}
|
|
187
|
+
*/
|
|
188
|
+
async function saveTraceContextCheckpointIfUpdated (
|
|
189
|
+
tracer, span, durableContext, firstExecutionSpanId, event,
|
|
190
|
+
) {
|
|
191
|
+
const checkpointManager = durableContext.checkpoint ?? durableContext.checkpointManager
|
|
192
|
+
if (typeof checkpointManager?.checkpoint !== 'function') return
|
|
193
|
+
|
|
194
|
+
const currentHeaders = injectHeaders(tracer, span)
|
|
195
|
+
if (currentHeaders['x-datadog-trace-id'] === undefined) return
|
|
196
|
+
|
|
197
|
+
const latest = findLastCheckpoint(event)
|
|
198
|
+
|
|
199
|
+
let newNumber
|
|
200
|
+
if (latest) {
|
|
201
|
+
const previousHeaders = parseCheckpointPayload(latest.operation)
|
|
202
|
+
if (!previousHeaders) return
|
|
203
|
+
|
|
204
|
+
// x-datadog-parent-id reflects the active span at save time and always differs, so exclude it
|
|
205
|
+
// from the comparison. Capture the previous anchor first to carry it forward on a real update.
|
|
206
|
+
// needsCheckpointUpdate only reads currentHeaders' keys, so deleting it from there is enough.
|
|
207
|
+
const anchoredSpanId = previousHeaders['x-datadog-parent-id']
|
|
208
|
+
delete currentHeaders['x-datadog-parent-id']
|
|
209
|
+
if (!needsCheckpointUpdate(currentHeaders, previousHeaders)) return
|
|
210
|
+
|
|
211
|
+
newNumber = latest.checkpointNumber + 1
|
|
212
|
+
overrideParentId(currentHeaders, anchoredSpanId)
|
|
213
|
+
} else {
|
|
214
|
+
newNumber = 0
|
|
215
|
+
if (firstExecutionSpanId) overrideParentId(currentHeaders, firstExecutionSpanId)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const executionArn = event?.DurableExecutionArn || ''
|
|
219
|
+
await saveCheckpoint(checkpointManager, executionArn, newNumber, currentHeaders)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = {
|
|
223
|
+
saveTraceContextCheckpointIfUpdated,
|
|
224
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { createHash } = require('node:crypto')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Populates the replay and operation_id tags for the op the DurableContextImpl is about to
|
|
7
|
+
* run, deriving both from a single `getNextStepId()` call. `aws.durable.replayed` is always
|
|
8
|
+
* set ('true' when the next stepId already has a SUCCEEDED checkpoint entry, i.e. the op will
|
|
9
|
+
* be served from the SDK's checkpoint). `aws.durable.operation_id` — the 16-hex-char MD5 of
|
|
10
|
+
* the stepId, mirroring the SDK's internal calculation — is only added when a stepId exists.
|
|
11
|
+
* @param {Record<string, string>} meta - The span meta/tags object to populate.
|
|
12
|
+
* @param {object} [ctxImpl] - The DurableContextImpl about to run the op.
|
|
13
|
+
* @returns {void}
|
|
14
|
+
*/
|
|
15
|
+
function addOpMeta (meta, ctxImpl) {
|
|
16
|
+
const stepId = ctxImpl?.getNextStepId?.()
|
|
17
|
+
if (!stepId) {
|
|
18
|
+
meta['aws.durable.replayed'] = 'false'
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
const stepData = ctxImpl?._executionContext?.getStepData?.(stepId)
|
|
22
|
+
meta['aws.durable.replayed'] = String(stepData?.Status === 'SUCCEEDED')
|
|
23
|
+
meta['aws.durable.operation_id'] = createHash('md5').update(stepId).digest('hex').slice(0, 16)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The SDK wraps user errors in typed classes (StepError, ChildContextError, etc.); we follow the
|
|
28
|
+
* `.cause` chain to recover the user's original Error. SDK wrappers expose a string `errorType`
|
|
29
|
+
* field, so the loop stops once we leave the wrapper hierarchy.
|
|
30
|
+
* @param {{ error?: unknown }} ctx
|
|
31
|
+
* @returns {unknown} the unwrapped error, or `ctx.error` unchanged when it isn't an Error
|
|
32
|
+
*/
|
|
33
|
+
function unwrapDurableError (ctx) {
|
|
34
|
+
let err = ctx?.error
|
|
35
|
+
if (!(err instanceof Error)) return err
|
|
36
|
+
|
|
37
|
+
while (typeof err.errorType === 'string' && err.cause instanceof Error) {
|
|
38
|
+
err = err.cause
|
|
39
|
+
}
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { addOpMeta, unwrapDurableError }
|
|
@@ -108,7 +108,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
108
108
|
const span = this.startSpan(this.operationFromRequest(request), {
|
|
109
109
|
childOf,
|
|
110
110
|
meta,
|
|
111
|
-
service: this.serviceName(),
|
|
111
|
+
service: this.serviceName(request),
|
|
112
112
|
integrationName: 'aws-sdk',
|
|
113
113
|
}, ctx)
|
|
114
114
|
|
|
@@ -172,18 +172,12 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
172
172
|
})
|
|
173
173
|
|
|
174
174
|
this.addSub(`apm:aws:request:complete:${this.serviceIdentifier}`, ctx => {
|
|
175
|
-
const { response,
|
|
175
|
+
const { response, currentStore } = ctx
|
|
176
176
|
if (!currentStore) return
|
|
177
177
|
const { span } = currentStore
|
|
178
178
|
if (!span) return
|
|
179
179
|
|
|
180
180
|
storage('legacy').run(currentStore, () => {
|
|
181
|
-
// try to extract DSM context from response if no callback exists as extraction normally happens in CB
|
|
182
|
-
if (!cbExists && this.serviceIdentifier === 'sqs') {
|
|
183
|
-
const params = response.request.params
|
|
184
|
-
const operation = response.request.operation
|
|
185
|
-
this.responseExtractDSMContext(operation, params, response.data ?? response, span)
|
|
186
|
-
}
|
|
187
181
|
this.addResponseTags(span, response)
|
|
188
182
|
|
|
189
183
|
if (this._tracerConfig?.DD_TRACE_AWS_ADD_SPAN_POINTERS) {
|
|
@@ -222,14 +216,18 @@ class BaseAwsSdkPlugin extends ClientPlugin {
|
|
|
222
216
|
})
|
|
223
217
|
}
|
|
224
218
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
219
|
+
/**
|
|
220
|
+
* @param {{ params?: object }} request
|
|
221
|
+
*/
|
|
222
|
+
serviceName (request) {
|
|
223
|
+
return super.serviceName({
|
|
224
|
+
id: 'aws',
|
|
225
|
+
type: 'web',
|
|
226
|
+
kind: 'client',
|
|
227
|
+
awsService: this.serviceIdentifier,
|
|
228
|
+
pluginConfig: this.config,
|
|
229
|
+
params: request.params,
|
|
230
|
+
})
|
|
233
231
|
}
|
|
234
232
|
|
|
235
233
|
isEnabled (request) {
|
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
const log = require('../../../dd-trace/src/log')
|
|
3
3
|
const BaseAwsSdkPlugin = require('../base')
|
|
4
4
|
|
|
5
|
+
// EventBridge enforces this limit over the whole PutEvents request (the sum of
|
|
6
|
+
// every entry), not over a single entry. 1 MiB == 1,048,576 bytes.
|
|
7
|
+
const MAX_PUT_EVENTS_BYTES = 1024 * 1024
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Size a single `PutEventsRequestEntry` the way EventBridge does server-side:
|
|
11
|
+
* the UTF-8 byte length of `Source`, `DetailType`, `Detail`, and each
|
|
12
|
+
* `Resources` ARN, plus a flat 14 bytes when `Time` is set.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} entry a single PutEvents request entry
|
|
15
|
+
* @param {string} [detail] overrides `entry.Detail`, used to size the entry as
|
|
16
|
+
* it would be sent with the injected `_datadog` context
|
|
17
|
+
* @returns {number}
|
|
18
|
+
* @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-putevents.html
|
|
19
|
+
*/
|
|
20
|
+
function putEventEntrySize (entry, detail = entry.Detail) {
|
|
21
|
+
let size = entry.Time == null ? 0 : 14
|
|
22
|
+
if (entry.Source != null) size += Buffer.byteLength(entry.Source)
|
|
23
|
+
if (entry.DetailType != null) size += Buffer.byteLength(entry.DetailType)
|
|
24
|
+
if (detail != null) size += Buffer.byteLength(detail)
|
|
25
|
+
if (entry.Resources != null) {
|
|
26
|
+
for (const resource of entry.Resources) {
|
|
27
|
+
if (resource != null) size += Buffer.byteLength(resource)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return size
|
|
31
|
+
}
|
|
32
|
+
|
|
5
33
|
class EventBridge extends BaseAwsSdkPlugin {
|
|
6
34
|
static id = 'eventbridge'
|
|
7
35
|
static isPayloadReporter = true
|
|
@@ -25,7 +53,8 @@ class EventBridge extends BaseAwsSdkPlugin {
|
|
|
25
53
|
* Docs: https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEventsRequestEntry.html
|
|
26
54
|
* We cannot use the traceHeader field as that's reserved for X-Ray.
|
|
27
55
|
* Detail must be a valid JSON string
|
|
28
|
-
* Max
|
|
56
|
+
* Max PutEvents request size is 1mb, summed over all entries
|
|
57
|
+
* (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-putevent-size.html)
|
|
29
58
|
*/
|
|
30
59
|
requestInject (span, request) {
|
|
31
60
|
const operation = request.operation
|
|
@@ -34,6 +63,7 @@ class EventBridge extends BaseAwsSdkPlugin {
|
|
|
34
63
|
request.params.Entries &&
|
|
35
64
|
request.params.Entries.length > 0 &&
|
|
36
65
|
request.params.Entries[0].Detail) {
|
|
66
|
+
const entries = request.params.Entries
|
|
37
67
|
const injected = {}
|
|
38
68
|
this.tracer.inject(span, 'text_map', injected)
|
|
39
69
|
|
|
@@ -42,19 +72,26 @@ class EventBridge extends BaseAwsSdkPlugin {
|
|
|
42
72
|
// it so the rest of the body stays in V8's optimisable surface.
|
|
43
73
|
let finalData
|
|
44
74
|
try {
|
|
45
|
-
finalData = BaseAwsSdkPlugin.injectFieldIntoJsonObject(
|
|
46
|
-
request.params.Entries[0].Detail, '_datadog', injected
|
|
47
|
-
)
|
|
75
|
+
finalData = BaseAwsSdkPlugin.injectFieldIntoJsonObject(entries[0].Detail, '_datadog', injected)
|
|
48
76
|
} catch (error) {
|
|
49
77
|
log.error('EventBridge error injecting request', error)
|
|
50
78
|
return
|
|
51
79
|
}
|
|
52
80
|
|
|
53
|
-
|
|
81
|
+
// EventBridge applies the 1 MiB cap to the whole request, so size every
|
|
82
|
+
// entry as it would be sent (the first with `_datadog` injected) and skip
|
|
83
|
+
// rather than tip a request AWS would otherwise accept over the limit. The
|
|
84
|
+
// running total only needs to clear the cap, so stop summing the moment it
|
|
85
|
+
// does instead of byte-counting the rest of a batch we already know is over.
|
|
86
|
+
let requestSize = putEventEntrySize(entries[0], finalData)
|
|
87
|
+
for (let i = 1; requestSize < MAX_PUT_EVENTS_BYTES && i < entries.length; i++) {
|
|
88
|
+
requestSize += putEventEntrySize(entries[i])
|
|
89
|
+
}
|
|
90
|
+
if (requestSize >= MAX_PUT_EVENTS_BYTES) {
|
|
54
91
|
log.info('Payload size too large to pass context')
|
|
55
92
|
return
|
|
56
93
|
}
|
|
57
|
-
|
|
94
|
+
entries[0].Detail = finalData
|
|
58
95
|
}
|
|
59
96
|
}
|
|
60
97
|
}
|