dd-trace 6.8.0 → 6.10.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/diagnose.js +27 -25
- package/ci/init.js +6 -6
- package/ci/runbook.md +114 -20
- package/ci/test-optimization-validation/approval-artifacts.js +36 -3
- package/ci/test-optimization-validation/approval.js +78 -22
- package/ci/test-optimization-validation/blocker-category.js +24 -0
- package/ci/test-optimization-validation/ci-discovery.js +23 -17
- package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
- package/ci/test-optimization-validation/ci-remediation.js +1 -1
- package/ci/test-optimization-validation/cli.js +280 -52
- package/ci/test-optimization-validation/command-blocker.js +159 -20
- package/ci/test-optimization-validation/command-output-policy.js +9 -3
- package/ci/test-optimization-validation/command-runner.js +9 -7
- package/ci/test-optimization-validation/environment.js +4 -2
- package/ci/test-optimization-validation/executable.js +15 -13
- package/ci/test-optimization-validation/execution-lock.js +70 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
- package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
- package/ci/test-optimization-validation/generated-files.js +38 -28
- package/ci/test-optimization-validation/generated-test-contract.js +8 -4
- package/ci/test-optimization-validation/generated-verifier.js +24 -17
- package/ci/test-optimization-validation/literal-glob.js +52 -0
- package/ci/test-optimization-validation/manifest-loader.js +3 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
- package/ci/test-optimization-validation/manifest-schema.js +80 -17
- package/ci/test-optimization-validation/offline-fixtures.js +13 -4
- package/ci/test-optimization-validation/package-check.js +94 -0
- package/ci/test-optimization-validation/plan-writer.js +183 -10
- package/ci/test-optimization-validation/preflight-runner.js +110 -31
- package/ci/test-optimization-validation/project-build-artifact.js +31 -0
- package/ci/test-optimization-validation/redaction.js +18 -27
- package/ci/test-optimization-validation/report-writer.js +182 -48
- package/ci/test-optimization-validation/result-semantics.js +20 -3
- package/ci/test-optimization-validation/runner-command.js +40 -20
- package/ci/test-optimization-validation/runner-contract.js +79 -43
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
- package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
- package/ci/test-optimization-validation/static-diagnosis.js +10 -3
- package/ci/test-optimization-validation/test-output.js +2 -1
- package/ci/test-optimization-validation/validation-blocker.js +21 -0
- package/ci/vitest-no-worker-init-setup.mjs +140 -42
- package/index.d.ts +140 -1
- package/init.js +1 -17
- package/initialize.mjs +11 -0
- package/loader-hook.mjs +6 -4
- package/package.json +8 -5
- package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
- package/packages/datadog-esbuild/src/utils.js +16 -1
- package/packages/datadog-instrumentations/src/ai.js +61 -34
- package/packages/datadog-instrumentations/src/bluebird.js +6 -6
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
- package/packages/datadog-instrumentations/src/cucumber.js +124 -47
- package/packages/datadog-instrumentations/src/dns.js +21 -2
- package/packages/datadog-instrumentations/src/electron.js +1 -0
- package/packages/datadog-instrumentations/src/fastify.js +3 -1
- package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +199 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +62 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +980 -234
- package/packages/datadog-instrumentations/src/mocha/main.js +108 -10
- package/packages/datadog-instrumentations/src/mocha/utils.js +66 -50
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
- package/packages/datadog-instrumentations/src/mysql.js +46 -16
- package/packages/datadog-instrumentations/src/mysql2.js +119 -18
- package/packages/datadog-instrumentations/src/next.js +155 -23
- package/packages/datadog-instrumentations/src/openai.js +14 -0
- package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
- package/packages/datadog-instrumentations/src/pg.js +209 -11
- package/packages/datadog-instrumentations/src/playwright.js +173 -79
- package/packages/datadog-instrumentations/src/promise-js.js +4 -4
- package/packages/datadog-instrumentations/src/promise.js +3 -3
- package/packages/datadog-instrumentations/src/router.js +195 -19
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +85 -13
- package/packages/datadog-instrumentations/src/vitest-main.js +528 -72
- package/packages/datadog-instrumentations/src/vitest-util.js +106 -12
- package/packages/datadog-instrumentations/src/vitest-worker.js +382 -40
- package/packages/datadog-instrumentations/src/webdriverio.js +212 -22
- package/packages/datadog-instrumentations/src/when.js +3 -3
- package/packages/datadog-instrumentations/src/ws.js +5 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
- package/packages/datadog-plugin-cucumber/src/index.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -47
- package/packages/datadog-plugin-cypress/src/support.js +7 -4
- package/packages/datadog-plugin-electron/src/net.js +10 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +6 -22
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/producer.js +5 -3
- package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
- package/packages/datadog-plugin-mocha/src/index.js +832 -4
- package/packages/datadog-plugin-mysql/src/index.js +37 -0
- package/packages/datadog-plugin-next/src/index.js +51 -21
- package/packages/datadog-plugin-openai/src/tracing.js +4 -0
- package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
- package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
- package/packages/datadog-plugin-pg/src/index.js +66 -1
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +103 -27
- package/packages/datadog-plugin-ws/src/util.js +2 -1
- package/packages/datadog-shimmer/src/shimmer.js +6 -2
- package/packages/dd-trace/src/aiguard/sdk.js +3 -0
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
- package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
- package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +228 -0
- package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
- package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
- package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/index.js +48 -476
- package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
- package/packages/dd-trace/src/carrier.js +356 -0
- package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
- package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/generated-config-types.d.ts +5 -0
- package/packages/dd-trace/src/config/helper.js +2 -0
- package/packages/dd-trace/src/config/index.js +16 -5
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +18 -0
- package/packages/dd-trace/src/datastreams/context.js +6 -1
- package/packages/dd-trace/src/datastreams/manager.js +5 -1
- package/packages/dd-trace/src/datastreams/pathway.js +23 -22
- package/packages/dd-trace/src/datastreams/processor.js +3 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
- package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
- package/packages/dd-trace/src/guardrails/log.js +1 -10
- package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
- package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
- package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
- package/packages/dd-trace/src/llmobs/index.js +34 -2
- package/packages/dd-trace/src/llmobs/noop.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +149 -68
- package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
- package/packages/dd-trace/src/llmobs/tagger.js +96 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/util.js +78 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
- package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
- package/packages/dd-trace/src/log/channels.js +1 -9
- package/packages/dd-trace/src/log/levels.js +12 -0
- package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span.js +4 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +6 -0
- package/packages/dd-trace/src/plugins/util/git.js +6 -4
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
- package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +3 -62
- package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
- package/packages/dd-trace/src/plugins/util/test.js +397 -74
- package/packages/dd-trace/src/plugins/util/url.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +76 -3
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
- package/packages/dd-trace/src/proxy.js +1 -0
- package/packages/dd-trace/src/ritm.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
- package/packages/dd-trace/src/sampling_rule.js +4 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
- package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/vendor/dist/pprof-format/index.js +1 -1
- package/version.js +10 -8
- /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
|
@@ -21,6 +21,38 @@ class MySQLPlugin extends DatabasePlugin {
|
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
this.addBind(`apm:${this.component}:connection:finish`, ctx => ctx.currentStore)
|
|
24
|
+
|
|
25
|
+
// Explicit getConnection calls get an acquire span. Pool queries report their internal acquire
|
|
26
|
+
// on the query span instead, so each acquire is counted once.
|
|
27
|
+
this.addSub(`apm:${this.component}:pool:acquire:start`, ctx => {
|
|
28
|
+
const operation = `${this.component}.pool.acquire`
|
|
29
|
+
|
|
30
|
+
this.startSpan(operation, {
|
|
31
|
+
service: this.serviceName({ pluginConfig: this.config, dbConfig: ctx.conf, system: this.system }),
|
|
32
|
+
resource: operation,
|
|
33
|
+
startTime: ctx.startTime,
|
|
34
|
+
type: 'sql',
|
|
35
|
+
kind: 'client',
|
|
36
|
+
meta: {
|
|
37
|
+
'db.type': this.system,
|
|
38
|
+
'db.user': ctx.conf.user,
|
|
39
|
+
'db.name': ctx.conf.database,
|
|
40
|
+
'out.host': ctx.conf.host,
|
|
41
|
+
[CLIENT_PORT_KEY]: ctx.conf.port,
|
|
42
|
+
},
|
|
43
|
+
}, ctx)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
this.addSub(`apm:${this.component}:pool:acquire:finish`, ctx => {
|
|
47
|
+
const span = ctx.currentStore?.span
|
|
48
|
+
if (span === undefined) return
|
|
49
|
+
|
|
50
|
+
if (ctx.error) {
|
|
51
|
+
this.addError(ctx.error, span)
|
|
52
|
+
}
|
|
53
|
+
span.setTag(`${this.component}.pool.wait_time`, ctx.poolWaitTime)
|
|
54
|
+
this.finish(ctx)
|
|
55
|
+
})
|
|
24
56
|
}
|
|
25
57
|
|
|
26
58
|
bindStart (ctx) {
|
|
@@ -38,6 +70,11 @@ class MySQLPlugin extends DatabasePlugin {
|
|
|
38
70
|
[CLIENT_PORT_KEY]: ctx.conf.port,
|
|
39
71
|
},
|
|
40
72
|
}, ctx)
|
|
73
|
+
|
|
74
|
+
if (ctx.poolWaitTime !== undefined) {
|
|
75
|
+
span.setTag(`${this.component}.pool.wait_time`, ctx.poolWaitTime)
|
|
76
|
+
}
|
|
77
|
+
|
|
41
78
|
ctx.sql = this.injectDbmQuery(span, ctx.sql, service.name)
|
|
42
79
|
|
|
43
80
|
return ctx.currentStore
|
|
@@ -5,8 +5,11 @@ const { storage } = require('../../datadog-core')
|
|
|
5
5
|
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
6
6
|
const { COMPONENT, SVC_SRC_KEY } = require('../../dd-trace/src/constants')
|
|
7
7
|
const web = require('../../dd-trace/src/plugins/util/web')
|
|
8
|
+
const { HTTP_ROUTE, RESOURCE_NAME } = require('../../../ext/tags')
|
|
8
9
|
|
|
9
10
|
const errorPages = new Set(['/404', '/500', '/_error', '/_not-found', '/_not-found/page'])
|
|
11
|
+
const reusedNextRequestStores = new WeakSet()
|
|
12
|
+
const nextParentRoutes = new WeakMap()
|
|
10
13
|
|
|
11
14
|
class NextPlugin extends ServerPlugin {
|
|
12
15
|
static id = 'next'
|
|
@@ -18,7 +21,14 @@ class NextPlugin extends ServerPlugin {
|
|
|
18
21
|
|
|
19
22
|
bindStart ({ req, res }) {
|
|
20
23
|
const store = storage('legacy').getStore()
|
|
21
|
-
const
|
|
24
|
+
const parentSpan = store?.span
|
|
25
|
+
if (parentSpan?._integrationName === this.constructor.id) {
|
|
26
|
+
const reusedStore = { ...store, span: parentSpan, req }
|
|
27
|
+
reusedNextRequestStores.add(reusedStore)
|
|
28
|
+
return reusedStore
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const childOf = parentSpan || web.extractIncomingServerContext(this.tracer, req.headers)
|
|
22
32
|
const { name: schemaServiceName, source: schemaServiceSource } = this.serviceName()
|
|
23
33
|
const serviceName = this.config.service || schemaServiceName
|
|
24
34
|
let serviceSource = this.config.service ? 'opt.plugin' : schemaServiceSource
|
|
@@ -42,7 +52,10 @@ class NextPlugin extends ServerPlugin {
|
|
|
42
52
|
|
|
43
53
|
analyticsSampler.sample(span, this.config.measured, true)
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
const isHttpParent = parentSpan?._integrationName === 'http'
|
|
56
|
+
const httpParentSpan = isHttpParent ? parentSpan : undefined
|
|
57
|
+
const httpParentReq = isHttpParent ? web.getRequest(parentSpan) : undefined
|
|
58
|
+
return { ...store, span, req, httpParentSpan, httpParentReq }
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
error ({ span, error }) {
|
|
@@ -60,6 +73,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
60
73
|
const store = storage('legacy').getStore()
|
|
61
74
|
|
|
62
75
|
if (!store) return
|
|
76
|
+
if (reusedNextRequestStores.has(store)) return
|
|
63
77
|
|
|
64
78
|
const span = store.span
|
|
65
79
|
const error = span.context().getTag('error')
|
|
@@ -88,12 +102,12 @@ class NextPlugin extends ServerPlugin {
|
|
|
88
102
|
span.finish()
|
|
89
103
|
}
|
|
90
104
|
|
|
91
|
-
pageLoad ({ page, isAppPath = false, isStatic = false }) {
|
|
105
|
+
pageLoad ({ page, isAppPath = false, isStatic = false, isFilesystemPath = isAppPath }) {
|
|
92
106
|
const store = storage('legacy').getStore()
|
|
93
107
|
|
|
94
108
|
if (!store) return
|
|
95
109
|
|
|
96
|
-
const { span, req } = store
|
|
110
|
+
const { span, req, httpParentReq, httpParentSpan } = store
|
|
97
111
|
|
|
98
112
|
// safeguard against missing req in complicated timeout scenarios
|
|
99
113
|
if (!req) return
|
|
@@ -106,10 +120,11 @@ class NextPlugin extends ServerPlugin {
|
|
|
106
120
|
return
|
|
107
121
|
}
|
|
108
122
|
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
// Compiled runtimes may publish a normalized pathname rather than a filesystem path.
|
|
124
|
+
if (isFilesystemPath && !isErrorPage) {
|
|
125
|
+
if (isAppPath) page = normalizeAppPath(page)
|
|
126
|
+
page = normalizeIndexPage(page)
|
|
127
|
+
}
|
|
113
128
|
|
|
114
129
|
// handle static resource
|
|
115
130
|
if (isStatic) {
|
|
@@ -123,29 +138,44 @@ class NextPlugin extends ServerPlugin {
|
|
|
123
138
|
'resource.name': `${req.method} ${page}`.trim(),
|
|
124
139
|
'next.page': page,
|
|
125
140
|
})
|
|
126
|
-
|
|
141
|
+
const routeRequest = httpParentReq || req
|
|
142
|
+
web.setRouteOrEndpointTag(routeRequest)
|
|
143
|
+
setHttpParentRoute(httpParentSpan, req.method, page, isStatic)
|
|
144
|
+
web.setRoute(routeRequest, page)
|
|
127
145
|
}
|
|
128
146
|
|
|
129
147
|
configure (config) {
|
|
130
|
-
return super.configure(normalizeConfig(config))
|
|
148
|
+
return super.configure(web.normalizeConfig(config))
|
|
131
149
|
}
|
|
132
150
|
}
|
|
133
151
|
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return { ...config, hooks, validateStatus }
|
|
152
|
+
function normalizeIndexPage (page) {
|
|
153
|
+
if (typeof page !== 'string') return page
|
|
154
|
+
if (page === '/index') return '/'
|
|
155
|
+
if (page.endsWith('/index')) return page.slice(0, -'/index'.length) || '/'
|
|
156
|
+
return page
|
|
141
157
|
}
|
|
142
158
|
|
|
143
|
-
|
|
159
|
+
function normalizeAppPath (page) {
|
|
160
|
+
if (typeof page !== 'string') return page
|
|
144
161
|
|
|
145
|
-
|
|
146
|
-
|
|
162
|
+
for (const suffix of ['/page', '/route']) {
|
|
163
|
+
if (page === suffix) return '/'
|
|
164
|
+
if (page.endsWith(suffix)) return page.slice(0, -suffix.length) || '/'
|
|
165
|
+
}
|
|
147
166
|
|
|
148
|
-
return
|
|
167
|
+
return page
|
|
149
168
|
}
|
|
150
169
|
|
|
170
|
+
function setHttpParentRoute (span, method, page, isStatic) {
|
|
171
|
+
if (!span) return
|
|
172
|
+
const currentRoute = span.context().getTag(HTTP_ROUTE)
|
|
173
|
+
|
|
174
|
+
// Only refine a route that Next itself set; static resources are fallbacks.
|
|
175
|
+
if (currentRoute && (nextParentRoutes.get(span) !== currentRoute || isStatic)) return
|
|
176
|
+
|
|
177
|
+
span.setTag(HTTP_ROUTE, page)
|
|
178
|
+
span.setTag(RESOURCE_NAME, `${method} ${page}`.trim())
|
|
179
|
+
nextParentRoutes.set(span, page)
|
|
180
|
+
}
|
|
151
181
|
module.exports = NextPlugin
|
|
@@ -627,6 +627,10 @@ function lookupOperationEndpoint (operationId, methodName, url) {
|
|
|
627
627
|
* This function essentially normalizes the OpenAI method interface. Many methods accept
|
|
628
628
|
* a single object argument. The remaining ones take individual arguments. This function
|
|
629
629
|
* turns the individual arguments into an object to make extracting properties consistent.
|
|
630
|
+
*
|
|
631
|
+
* @param {string} methodName SDK method name, e.g. `createChatCompletion` or `chat.completions.create`
|
|
632
|
+
* @param {unknown[]} args arguments the SDK method was called with
|
|
633
|
+
* @returns {Record<string, unknown>}
|
|
630
634
|
*/
|
|
631
635
|
function normalizeRequestPayload (methodName, args) {
|
|
632
636
|
switch (methodName) {
|
|
@@ -15,6 +15,9 @@ const { AGENTS_ERROR_TYPE, applyError, deriveSpanName } = require('./util')
|
|
|
15
15
|
|
|
16
16
|
const COMPONENT = 'openai-agents'
|
|
17
17
|
const DEFAULT_MODEL_PROVIDER = 'openai'
|
|
18
|
+
// Bounds the agents-core parent-chain walk so a cyclic chain from a future
|
|
19
|
+
// agents-core version can't spin on the span-start hot path.
|
|
20
|
+
const MAX_ANCESTOR_WALK = 32
|
|
18
21
|
const MODEL_BASE_URL_STORE_KEY = Symbol('openai-agents.model-base-url')
|
|
19
22
|
const legacyStorage = storage('legacy')
|
|
20
23
|
|
|
@@ -37,9 +40,20 @@ const KIND_TO_SPAN_KIND = {
|
|
|
37
40
|
* metadata?: Record<string, unknown>,
|
|
38
41
|
* groupId?: string,
|
|
39
42
|
* llmobsParentStore?: object,
|
|
43
|
+
* activeSpanCount: number,
|
|
44
|
+
* completionRequested: boolean,
|
|
40
45
|
* }} LLMObsTraceInfo
|
|
41
46
|
*/
|
|
42
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {{
|
|
50
|
+
* parentId: string | null,
|
|
51
|
+
* traceId?: string,
|
|
52
|
+
* activeChildCount: number,
|
|
53
|
+
* ended: boolean,
|
|
54
|
+
* }} UntracedSpanInfo
|
|
55
|
+
*/
|
|
56
|
+
|
|
43
57
|
/**
|
|
44
58
|
* Owns tracer/tagger refs, maps agents-core span ids → dd-trace spans, and
|
|
45
59
|
* reconstructs workflow-level input/output from the first and last response
|
|
@@ -59,6 +73,12 @@ class OpenAIAgentsIntegration {
|
|
|
59
73
|
#tagger
|
|
60
74
|
/** @type {Map<string, import('../../dd-trace/src/opentracing/span')>} */
|
|
61
75
|
#oaiToDdSpan = new Map()
|
|
76
|
+
/**
|
|
77
|
+
* agents-core spans we deliberately don't trace (`task` / `turn`). Ended
|
|
78
|
+
* nodes stay walkable only while an observed child callback is still active.
|
|
79
|
+
* @type {Map<string, UntracedSpanInfo>}
|
|
80
|
+
*/
|
|
81
|
+
#untracedSpans = new Map()
|
|
62
82
|
/** @type {Map<string, LLMObsTraceInfo>} */
|
|
63
83
|
#traceInfo = new Map()
|
|
64
84
|
|
|
@@ -83,11 +103,58 @@ class OpenAIAgentsIntegration {
|
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
/**
|
|
86
|
-
*
|
|
106
|
+
* Resolve the dd-trace span for an agents-core spanId. When the id belongs to
|
|
107
|
+
* a span type we don't trace — agents-core >=0.14 runs model calls inside a
|
|
108
|
+
* `turn` span — the nearest traced ancestor's span is returned so callers
|
|
109
|
+
* still activate the enclosing agent span.
|
|
110
|
+
*
|
|
111
|
+
* @param {string | undefined | null} spanId agents-core spanId
|
|
87
112
|
* @returns {import('../../dd-trace/src/opentracing/span') | undefined}
|
|
88
113
|
*/
|
|
89
114
|
getDDSpan (spanId) {
|
|
90
|
-
return this.#oaiToDdSpan.get(spanId)
|
|
115
|
+
return this.#oaiToDdSpan.get(this.#nearestTracedAncestorId(spanId))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Remember an untraced agents-core span's parent so the chain stays walkable.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} oaiSpan
|
|
122
|
+
*/
|
|
123
|
+
recordUntracedSpan (oaiSpan) {
|
|
124
|
+
const { spanId } = oaiSpan
|
|
125
|
+
if (!spanId || this.#untracedSpans.has(spanId)) return
|
|
126
|
+
|
|
127
|
+
const parentId = oaiSpan.parentId ?? null
|
|
128
|
+
this.#untracedSpans.set(spanId, {
|
|
129
|
+
parentId,
|
|
130
|
+
traceId: oaiSpan.traceId,
|
|
131
|
+
activeChildCount: 0,
|
|
132
|
+
ended: false,
|
|
133
|
+
})
|
|
134
|
+
this.#retainUntracedParent(parentId)
|
|
135
|
+
this.#spanStarted(oaiSpan.traceId)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Mark an untraced span callback complete. Its ancestry remains available
|
|
140
|
+
* until every observed child callback has completed.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} oaiSpan
|
|
143
|
+
*/
|
|
144
|
+
endUntracedSpan (oaiSpan) {
|
|
145
|
+
const info = this.#untracedSpans.get(oaiSpan.spanId)
|
|
146
|
+
if (!info || info.ended) return
|
|
147
|
+
|
|
148
|
+
// agents-core does not end its trace when the run throws. In 0.14 the
|
|
149
|
+
// failure can live on the root task before any agent span exists, making
|
|
150
|
+
// this structural callback the only opportunity to finish the workflow.
|
|
151
|
+
if (oaiSpan.error && this.#isTopLevelSpan(oaiSpan)) {
|
|
152
|
+
this.#requestWorkflowCompletion(oaiSpan.traceId, oaiSpan)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
info.ended = true
|
|
156
|
+
this.#pruneUntracedSpan(oaiSpan.spanId, info)
|
|
157
|
+
this.#spanEnded(info.traceId)
|
|
91
158
|
}
|
|
92
159
|
|
|
93
160
|
/**
|
|
@@ -114,6 +181,7 @@ class OpenAIAgentsIntegration {
|
|
|
114
181
|
ddSpan.finish()
|
|
115
182
|
}
|
|
116
183
|
this.#oaiToDdSpan.clear()
|
|
184
|
+
this.#untracedSpans.clear()
|
|
117
185
|
this.#traceInfo.clear()
|
|
118
186
|
}
|
|
119
187
|
|
|
@@ -138,6 +206,8 @@ class OpenAIAgentsIntegration {
|
|
|
138
206
|
groupId: oaiTrace.groupId || undefined,
|
|
139
207
|
metadata: oaiTrace.metadata,
|
|
140
208
|
llmobsParentStore,
|
|
209
|
+
activeSpanCount: 0,
|
|
210
|
+
completionRequested: false,
|
|
141
211
|
})
|
|
142
212
|
|
|
143
213
|
this.#tagger.registerLLMObsSpan(ddSpan, {
|
|
@@ -153,38 +223,55 @@ class OpenAIAgentsIntegration {
|
|
|
153
223
|
}
|
|
154
224
|
|
|
155
225
|
endTrace (oaiTrace) {
|
|
156
|
-
this.#
|
|
226
|
+
this.#requestWorkflowCompletion(oaiTrace.traceId)
|
|
157
227
|
}
|
|
158
228
|
|
|
159
229
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* `rootAgentSpan` is provided, its `error` field is reflected onto the
|
|
164
|
-
* workflow span before finishing.
|
|
230
|
+
* Request workflow completion after all observed span callbacks finish.
|
|
231
|
+
* Error tags are applied immediately so the agents-core span does not need
|
|
232
|
+
* to be retained while completion is pending.
|
|
165
233
|
*
|
|
166
234
|
* @param {string | undefined} traceId
|
|
167
|
-
* @param {object} [
|
|
235
|
+
* @param {object} [rootSpan] - top-level oai-span that ended in error.
|
|
168
236
|
*/
|
|
169
|
-
#
|
|
237
|
+
#requestWorkflowCompletion (traceId, rootSpan) {
|
|
170
238
|
if (!traceId) return
|
|
171
239
|
const ddSpan = this.#oaiToDdSpan.get(traceId)
|
|
172
240
|
if (!ddSpan) return
|
|
173
241
|
const info = this.#traceInfo.get(traceId)
|
|
174
242
|
|
|
175
|
-
if (
|
|
243
|
+
if (rootSpan?.error) {
|
|
176
244
|
ddSpan.setTag('error', true)
|
|
177
245
|
ddSpan.setTag('error.type', AGENTS_ERROR_TYPE)
|
|
178
|
-
if (
|
|
179
|
-
ddSpan.setTag('error.message',
|
|
246
|
+
if (rootSpan.error.message) {
|
|
247
|
+
ddSpan.setTag('error.message', rootSpan.error.message)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (info) {
|
|
252
|
+
if (!info.completionRequested) {
|
|
253
|
+
info.completionRequested = true
|
|
254
|
+
if (LLMObsTagger.tagMap.has(ddSpan)) {
|
|
255
|
+
llmobsStorage.enterWith(info.llmobsParentStore)
|
|
256
|
+
}
|
|
180
257
|
}
|
|
258
|
+
if (info.activeSpanCount > 0) return
|
|
181
259
|
}
|
|
182
260
|
|
|
261
|
+
this.#completeWorkflowSpan(traceId)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Finish the workflow dd-trace span and clear its bookkeeping.
|
|
266
|
+
*
|
|
267
|
+
* @param {string} traceId
|
|
268
|
+
*/
|
|
269
|
+
#completeWorkflowSpan (traceId) {
|
|
270
|
+
const ddSpan = this.#oaiToDdSpan.get(traceId)
|
|
271
|
+
if (!ddSpan) return
|
|
272
|
+
|
|
183
273
|
if (this.#isLLMObsEnabled()) this.#setTraceAttributes(ddSpan, traceId)
|
|
184
274
|
ddSpan.finish()
|
|
185
|
-
if (info && LLMObsTagger.tagMap.has(ddSpan)) {
|
|
186
|
-
llmobsStorage.enterWith(info.llmobsParentStore)
|
|
187
|
-
}
|
|
188
275
|
this.#oaiToDdSpan.delete(traceId)
|
|
189
276
|
this.#traceInfo.delete(traceId)
|
|
190
277
|
}
|
|
@@ -204,6 +291,8 @@ class OpenAIAgentsIntegration {
|
|
|
204
291
|
})
|
|
205
292
|
|
|
206
293
|
this.#oaiToDdSpan.set(spanId, ddSpan)
|
|
294
|
+
this.#retainUntracedParent(oaiSpan.parentId)
|
|
295
|
+
this.#spanStarted(oaiSpan.traceId)
|
|
207
296
|
|
|
208
297
|
if (this.#isLLMObsEnabled()) {
|
|
209
298
|
const llmobsOptions = {
|
|
@@ -230,49 +319,57 @@ class OpenAIAgentsIntegration {
|
|
|
230
319
|
const ddSpan = this.#oaiToDdSpan.get(spanId)
|
|
231
320
|
if (!ddSpan) return
|
|
232
321
|
|
|
233
|
-
|
|
322
|
+
try {
|
|
323
|
+
applyError(ddSpan, oaiSpan)
|
|
234
324
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
325
|
+
if (oaiSpan.spanData?.type === 'handoff') {
|
|
326
|
+
const spanName = deriveSpanName(oaiSpan)
|
|
327
|
+
ddSpan.setOperationName(spanName)
|
|
328
|
+
if (this.#isLLMObsEnabled()) this.#tagger.setName(ddSpan, spanName)
|
|
329
|
+
}
|
|
240
330
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
331
|
+
if (this.#isLLMObsEnabled()) {
|
|
332
|
+
const spanData = oaiSpan.spanData
|
|
333
|
+
switch (spanData?.type) {
|
|
334
|
+
case 'response':
|
|
335
|
+
this.#setResponseAttributes(ddSpan, oaiSpan)
|
|
336
|
+
this.#updateTraceInfoOutput(oaiSpan)
|
|
337
|
+
break
|
|
338
|
+
case 'generation':
|
|
339
|
+
this.#setGenerationAttributes(ddSpan, oaiSpan)
|
|
340
|
+
this.#updateTraceInfoOutput(oaiSpan)
|
|
341
|
+
break
|
|
342
|
+
case 'function':
|
|
343
|
+
this.#tagger.tagTextIO(ddSpan, spanData.input ?? '', spanData.output ?? '')
|
|
344
|
+
break
|
|
345
|
+
case 'handoff':
|
|
346
|
+
this.#tagger.tagTextIO(ddSpan, spanData.from_agent ?? '', spanData.to_agent ?? '')
|
|
347
|
+
break
|
|
348
|
+
case 'agent':
|
|
349
|
+
this.#setAgentAttributes(ddSpan, oaiSpan)
|
|
350
|
+
break
|
|
351
|
+
case 'custom':
|
|
352
|
+
if (spanData.data && typeof spanData.data === 'object') {
|
|
353
|
+
this.#tagger.tagMetadata(ddSpan, spanData.data)
|
|
354
|
+
}
|
|
355
|
+
break
|
|
356
|
+
}
|
|
266
357
|
}
|
|
267
|
-
}
|
|
268
358
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
359
|
+
// agents-core's withTrace skips Trace.end() when its callback throws, so
|
|
360
|
+
// an errored top-level span is our last chance to finalize the workflow.
|
|
361
|
+
if (oaiSpan.error && this.#isTopLevelSpan(oaiSpan)) {
|
|
362
|
+
this.#requestWorkflowCompletion(oaiSpan.traceId, oaiSpan)
|
|
363
|
+
}
|
|
364
|
+
} finally {
|
|
365
|
+
try {
|
|
366
|
+
ddSpan.finish()
|
|
367
|
+
} finally {
|
|
368
|
+
this.#oaiToDdSpan.delete(spanId)
|
|
369
|
+
this.#releaseUntracedParent(oaiSpan.parentId)
|
|
370
|
+
this.#spanEnded(oaiSpan.traceId)
|
|
371
|
+
}
|
|
274
372
|
}
|
|
275
|
-
this.#oaiToDdSpan.delete(spanId)
|
|
276
373
|
}
|
|
277
374
|
|
|
278
375
|
// ── Per-type attribute setters ──────────────────────────────────────────────
|
|
@@ -360,7 +457,7 @@ class OpenAIAgentsIntegration {
|
|
|
360
457
|
#llmSpanParentAgentName (oaiSpan) {
|
|
361
458
|
const traceInfo = this.#traceInfo.get(oaiSpan.traceId)
|
|
362
459
|
if (!traceInfo?.currentTopLevelAgentSpanId) return
|
|
363
|
-
if (oaiSpan.parentId
|
|
460
|
+
if (!this.#hasUntracedPathToAncestor(oaiSpan.parentId, traceInfo.currentTopLevelAgentSpanId)) return
|
|
364
461
|
return traceInfo.currentTopLevelAgentName
|
|
365
462
|
}
|
|
366
463
|
|
|
@@ -412,13 +509,12 @@ class OpenAIAgentsIntegration {
|
|
|
412
509
|
const info = this.#traceInfo.get(oaiSpan.traceId)
|
|
413
510
|
if (!info) return
|
|
414
511
|
|
|
415
|
-
const parentId = oaiSpan.parentId
|
|
416
512
|
const type = oaiSpan.spanData?.type
|
|
417
513
|
|
|
418
514
|
// Identify the first top-level agent span under the root trace and
|
|
419
515
|
// stash its display name so `${agentName} (LLM)` doesn't have to read
|
|
420
516
|
// the dd-trace span context's private fields later.
|
|
421
|
-
if (type === 'agent' &&
|
|
517
|
+
if (type === 'agent' && this.#isTopLevelSpan(oaiSpan)) {
|
|
422
518
|
info.currentTopLevelAgentSpanId = oaiSpan.spanId
|
|
423
519
|
info.currentTopLevelAgentName = spanName
|
|
424
520
|
}
|
|
@@ -427,9 +523,9 @@ class OpenAIAgentsIntegration {
|
|
|
427
523
|
// as the workflow-level input source.
|
|
428
524
|
if (
|
|
429
525
|
(type === 'response' || type === 'generation') &&
|
|
430
|
-
|
|
526
|
+
info.currentTopLevelAgentSpanId &&
|
|
431
527
|
!info.inputOaiSpan &&
|
|
432
|
-
parentId
|
|
528
|
+
this.#hasUntracedPathToAncestor(oaiSpan.parentId, info.currentTopLevelAgentSpanId)
|
|
433
529
|
) {
|
|
434
530
|
info.inputOaiSpan = oaiSpan
|
|
435
531
|
}
|
|
@@ -440,9 +536,8 @@ class OpenAIAgentsIntegration {
|
|
|
440
536
|
if (!info) return
|
|
441
537
|
|
|
442
538
|
if (
|
|
443
|
-
oaiSpan.parentId &&
|
|
444
539
|
info.currentTopLevelAgentSpanId &&
|
|
445
|
-
oaiSpan.parentId
|
|
540
|
+
this.#hasUntracedPathToAncestor(oaiSpan.parentId, info.currentTopLevelAgentSpanId)
|
|
446
541
|
) {
|
|
447
542
|
info.outputOaiSpan = oaiSpan
|
|
448
543
|
}
|
|
@@ -451,18 +546,132 @@ class OpenAIAgentsIntegration {
|
|
|
451
546
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
452
547
|
|
|
453
548
|
#resolveParent (oaiSpan) {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
549
|
+
return this.getDDSpan(oaiSpan.parentId) ?? this.#oaiToDdSpan.get(oaiSpan.traceId)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Walk the agents-core parent chain from `spanId`, skipping spans we don't
|
|
554
|
+
* trace, and return the first id that maps to a dd-trace span. Returns
|
|
555
|
+
* `undefined` when the walk reaches the trace root (or a span we never saw),
|
|
556
|
+
* which callers read as "no traced ancestor".
|
|
557
|
+
*
|
|
558
|
+
* @param {string | undefined | null} spanId agents-core spanId to start from
|
|
559
|
+
* @returns {string | undefined}
|
|
560
|
+
*/
|
|
561
|
+
#nearestTracedAncestorId (spanId) {
|
|
562
|
+
let currentId = spanId
|
|
563
|
+
for (let depth = 0; currentId != null && depth < MAX_ANCESTOR_WALK; depth++) {
|
|
564
|
+
if (this.#oaiToDdSpan.has(currentId)) return currentId
|
|
565
|
+
currentId = this.#untracedSpans.get(currentId)?.parentId
|
|
459
566
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* True when `spanId` reaches `ancestorId` through only untraced structural
|
|
571
|
+
* spans. Unlike #nearestTracedAncestorId, this still recognizes an ancestor
|
|
572
|
+
* whose dd-trace span has already finished and been removed from the live map.
|
|
573
|
+
*
|
|
574
|
+
* @param {string | undefined | null} spanId agents-core spanId to start from
|
|
575
|
+
* @param {string} ancestorId agents-core ancestor spanId
|
|
576
|
+
* @returns {boolean}
|
|
577
|
+
*/
|
|
578
|
+
#hasUntracedPathToAncestor (spanId, ancestorId) {
|
|
579
|
+
let currentId = spanId
|
|
580
|
+
for (let depth = 0; currentId != null && depth < MAX_ANCESTOR_WALK; depth++) {
|
|
581
|
+
if (currentId === ancestorId) return true
|
|
582
|
+
currentId = this.#untracedSpans.get(currentId)?.parentId
|
|
583
|
+
}
|
|
584
|
+
return false
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* True when nothing but untraced spans sits between this span and the trace
|
|
589
|
+
* root. Stricter than `#nearestTracedAncestorId(…) === undefined`, which also
|
|
590
|
+
* answers "undefined" for a parent we simply never saw — that must not be
|
|
591
|
+
* mistaken for top-level, or an error would finalize the workflow span early.
|
|
592
|
+
*
|
|
593
|
+
* @param {object} oaiSpan
|
|
594
|
+
* @returns {boolean}
|
|
595
|
+
*/
|
|
596
|
+
#isTopLevelSpan (oaiSpan) {
|
|
597
|
+
let currentId = oaiSpan.parentId
|
|
598
|
+
for (let depth = 0; depth < MAX_ANCESTOR_WALK; depth++) {
|
|
599
|
+
if (currentId == null) return true
|
|
600
|
+
const info = this.#untracedSpans.get(currentId)
|
|
601
|
+
if (!info) return false
|
|
602
|
+
currentId = info.parentId
|
|
603
|
+
}
|
|
604
|
+
return false
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Keep a structural parent alive until this child callback completes.
|
|
609
|
+
*
|
|
610
|
+
* @param {string | undefined | null} parentId
|
|
611
|
+
*/
|
|
612
|
+
#retainUntracedParent (parentId) {
|
|
613
|
+
const info = this.#untracedSpans.get(parentId)
|
|
614
|
+
if (info) info.activeChildCount++
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Release a structural parent and iteratively prune any ended, childless
|
|
619
|
+
* ancestors. Each iteration deletes one node, so even a cyclic parent chain
|
|
620
|
+
* terminates when it reaches the first node it already removed.
|
|
621
|
+
*
|
|
622
|
+
* @param {string | undefined | null} parentId
|
|
623
|
+
*/
|
|
624
|
+
#releaseUntracedParent (parentId) {
|
|
625
|
+
let currentId = parentId
|
|
626
|
+
while (currentId != null) {
|
|
627
|
+
const info = this.#untracedSpans.get(currentId)
|
|
628
|
+
if (!info || info.activeChildCount === 0) return
|
|
629
|
+
|
|
630
|
+
info.activeChildCount--
|
|
631
|
+
if (!info.ended || info.activeChildCount > 0) return
|
|
632
|
+
|
|
633
|
+
this.#untracedSpans.delete(currentId)
|
|
634
|
+
currentId = info.parentId
|
|
463
635
|
}
|
|
464
636
|
}
|
|
465
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Remove an ended structural span once no observed child still needs it.
|
|
640
|
+
*
|
|
641
|
+
* @param {string} spanId
|
|
642
|
+
* @param {UntracedSpanInfo} info
|
|
643
|
+
*/
|
|
644
|
+
#pruneUntracedSpan (spanId, info) {
|
|
645
|
+
if (info.activeChildCount > 0) return
|
|
646
|
+
this.#untracedSpans.delete(spanId)
|
|
647
|
+
this.#releaseUntracedParent(info.parentId)
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Count a span callback whose end may arrive after trace completion.
|
|
652
|
+
*
|
|
653
|
+
* @param {string | undefined} traceId
|
|
654
|
+
*/
|
|
655
|
+
#spanStarted (traceId) {
|
|
656
|
+
const info = this.#traceInfo.get(traceId)
|
|
657
|
+
if (info) info.activeSpanCount++
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Release one observed span callback and complete the trace when it becomes
|
|
662
|
+
* quiescent. Structural ancestry prunes independently with each subtree.
|
|
663
|
+
*
|
|
664
|
+
* @param {string | undefined} traceId
|
|
665
|
+
*/
|
|
666
|
+
#spanEnded (traceId) {
|
|
667
|
+
const info = this.#traceInfo.get(traceId)
|
|
668
|
+
if (!info) return
|
|
669
|
+
if (info.activeSpanCount > 0) info.activeSpanCount--
|
|
670
|
+
if (info.activeSpanCount > 0) return
|
|
671
|
+
|
|
672
|
+
if (info.completionRequested) this.#completeWorkflowSpan(traceId)
|
|
673
|
+
}
|
|
674
|
+
|
|
466
675
|
/**
|
|
467
676
|
* Read the mutable tracer configuration so remote/runtime enablement is
|
|
468
677
|
* reflected without reconstructing the plugin.
|