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
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
|
|
5
5
|
const { isError } = require('../util')
|
|
6
|
-
const tracerVersion = require('../../../../package.json').version
|
|
7
6
|
const logger = require('../log')
|
|
8
7
|
const { getValueFromEnvSources } = require('../config/helper')
|
|
9
8
|
const Span = require('../opentracing/span')
|
|
@@ -13,6 +12,17 @@ const {
|
|
|
13
12
|
INPUT_VALUE,
|
|
14
13
|
TRACE_ID,
|
|
15
14
|
} = require('./constants/tags')
|
|
15
|
+
const {
|
|
16
|
+
FEEDBACK_METRIC_TYPES,
|
|
17
|
+
FEEDBACK_TARGET_KEYS,
|
|
18
|
+
buildMetricTags,
|
|
19
|
+
validateAssessment,
|
|
20
|
+
validateLabel,
|
|
21
|
+
validateMetricType,
|
|
22
|
+
validateMetricValue,
|
|
23
|
+
validateReasoning,
|
|
24
|
+
validateTimestamp,
|
|
25
|
+
} = require('./eval-metric')
|
|
16
26
|
const {
|
|
17
27
|
getFunctionArguments,
|
|
18
28
|
validateKind,
|
|
@@ -392,92 +402,35 @@ class LLMObs extends NoopLLMObs {
|
|
|
392
402
|
}
|
|
393
403
|
|
|
394
404
|
const timestampMs = options.timestampMs || Date.now()
|
|
395
|
-
|
|
396
|
-
err = 'invalid_timestamp'
|
|
397
|
-
throw new Error('timestampMs must be a non-negative integer. Evaluation metric data will not be sent')
|
|
398
|
-
}
|
|
405
|
+
validateTimestamp(timestampMs, 'evaluation')
|
|
399
406
|
|
|
400
407
|
const { label, value, tags, reasoning, assessment, metadata } = options
|
|
401
408
|
const metricType = options.metricType?.toLowerCase()
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
err = 'invalid_metric_type'
|
|
408
|
-
throw new Error('metricType must be one of "categorical", "score", "boolean" or "json"')
|
|
409
|
-
}
|
|
410
|
-
if (metricType === 'categorical' && typeof value !== 'string') {
|
|
411
|
-
err = 'invalid_metric_value'
|
|
412
|
-
throw new Error('value must be a string for a categorical metric.')
|
|
413
|
-
}
|
|
414
|
-
if (metricType === 'score' && typeof value !== 'number') {
|
|
415
|
-
err = 'invalid_metric_value'
|
|
416
|
-
throw new Error('value must be a number for a score metric.')
|
|
417
|
-
}
|
|
418
|
-
if (metricType === 'boolean' && typeof value !== 'boolean') {
|
|
419
|
-
err = 'invalid_metric_value'
|
|
420
|
-
throw new Error('value must be a boolean for a boolean metric')
|
|
421
|
-
}
|
|
422
|
-
if (metricType === 'json' && (typeof value !== 'object' || value == null || Array.isArray(value))) {
|
|
423
|
-
err = 'invalid_metric_value'
|
|
424
|
-
throw new Error('value must be a JSON object for a json metric')
|
|
425
|
-
}
|
|
426
|
-
if (assessment != null && assessment !== 'pass' && assessment !== 'fail') {
|
|
427
|
-
err = 'invalid_assessment'
|
|
428
|
-
throw new Error('assessment must be pass or fail')
|
|
429
|
-
}
|
|
430
|
-
if (reasoning != null && typeof reasoning !== 'string') {
|
|
431
|
-
err = 'invalid_reasoning'
|
|
432
|
-
throw new Error('reasoning must be a string')
|
|
433
|
-
}
|
|
409
|
+
const labelValue = validateLabel(label, 'evaluation')
|
|
410
|
+
validateMetricType(metricType, 'evaluation')
|
|
411
|
+
validateMetricValue(metricType, value)
|
|
412
|
+
validateAssessment(assessment)
|
|
413
|
+
validateReasoning(reasoning)
|
|
434
414
|
if (metadata != null && (typeof metadata !== 'object' || Array.isArray(metadata))) {
|
|
435
415
|
err = 'invalid_metadata'
|
|
436
416
|
throw new Error('metadata must be a JSON object')
|
|
437
417
|
}
|
|
438
418
|
|
|
439
|
-
const evaluationTags = {
|
|
440
|
-
'ddtrace.version': tracerVersion,
|
|
441
|
-
ml_app: mlApp,
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (tags) {
|
|
445
|
-
for (const key in tags) {
|
|
446
|
-
const tag = tags[key]
|
|
447
|
-
if (typeof tag === 'string') {
|
|
448
|
-
evaluationTags[key] = tag
|
|
449
|
-
} else if (typeof tag.toString === 'function') {
|
|
450
|
-
evaluationTags[key] = tag.toString()
|
|
451
|
-
} else if (tag == null) {
|
|
452
|
-
evaluationTags[key] = Object.prototype.toString.call(tag)
|
|
453
|
-
} else {
|
|
454
|
-
// should be a rare case
|
|
455
|
-
// every object in JS has a toString, otherwise every primitive has its own toString
|
|
456
|
-
// null and undefined are handled above
|
|
457
|
-
err = 'invalid_tags'
|
|
458
|
-
throw new Error('Failed to parse tags. Tags for evaluation metrics must be strings')
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// When OTel tracing is enabled, add source:otel tag to allow backend to wait for OTel span conversion
|
|
464
|
-
if (this._config.DD_TRACE_OTEL_ENABLED) {
|
|
465
|
-
evaluationTags.source = 'otel'
|
|
466
|
-
}
|
|
467
|
-
|
|
468
419
|
const payload = {
|
|
420
|
+
event_kind: 'evaluation',
|
|
469
421
|
join_on: {
|
|
470
422
|
span: {
|
|
471
423
|
span_id: spanId,
|
|
472
424
|
trace_id: traceId,
|
|
473
425
|
},
|
|
474
426
|
},
|
|
475
|
-
label,
|
|
427
|
+
label: labelValue,
|
|
476
428
|
metric_type: metricType,
|
|
477
429
|
ml_app: mlApp,
|
|
478
430
|
[`${metricType}_value`]: value,
|
|
479
431
|
timestamp_ms: timestampMs,
|
|
480
|
-
|
|
432
|
+
// When OTel tracing is enabled, `source:otel` lets the backend wait for OTel span conversion
|
|
433
|
+
tags: buildMetricTags(tags, mlApp, 'evaluation', this._config.DD_TRACE_OTEL_ENABLED),
|
|
481
434
|
}
|
|
482
435
|
if (reasoning != null) {
|
|
483
436
|
payload.reasoning = reasoning
|
|
@@ -491,11 +444,139 @@ class LLMObs extends NoopLLMObs {
|
|
|
491
444
|
const currentStore = storage.getStore()
|
|
492
445
|
const routing = currentStore?.routingContext
|
|
493
446
|
evalMetricAppendCh.publish({ payload, routing })
|
|
447
|
+
} catch (e) {
|
|
448
|
+
if (e.ddErrorTag) err = e.ddErrorTag
|
|
449
|
+
throw e
|
|
494
450
|
} finally {
|
|
495
451
|
telemetry.recordSubmitEvaluation(options, err)
|
|
496
452
|
}
|
|
497
453
|
}
|
|
498
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Submits end-user feedback for a span, trace, session, or customer-defined entity.
|
|
457
|
+
*
|
|
458
|
+
* Exactly one target must be provided: `span` (as returned by `llmobs.exportSpan()`) or
|
|
459
|
+
* `spanId` to target a span, `traceId` a trace, `sessionId` a session, or `feedbackJoinKey`
|
|
460
|
+
* a customer-defined entity.
|
|
461
|
+
* `label`, `metricType`, `value` and `submitter` are required, and are validated at call time.
|
|
462
|
+
* @param {object} [options] - The feedback options.
|
|
463
|
+
* @param {string} [options.label] - The name of the feedback metric.
|
|
464
|
+
* @param {'categorical' | 'score' | 'boolean' | 'json' | 'text'} [options.metricType] - The value type.
|
|
465
|
+
* @param {string | number | boolean | Record<string, unknown>} [options.value] - The feedback value,
|
|
466
|
+
* matching `metricType`.
|
|
467
|
+
* @param {{ id: string, type?: string }} [options.submitter] - Who submitted the feedback.
|
|
468
|
+
* @param {{ traceId: string, spanId: string }} [options.span] - Span context to attach the feedback to.
|
|
469
|
+
* @param {string} [options.spanId] - ID of the span to attach the feedback to.
|
|
470
|
+
* @param {string} [options.traceId] - ID of the trace to attach the feedback to.
|
|
471
|
+
* @param {string} [options.sessionId] - ID of the session to attach the feedback to.
|
|
472
|
+
* @param {string} [options.feedbackJoinKey] - Customer-defined key to attach the feedback to.
|
|
473
|
+
* @param {Record<string, string>} [options.tags] - Tags to attach to the feedback.
|
|
474
|
+
* @param {string} [options.mlApp] - The ML app name. Defaults to the configured one.
|
|
475
|
+
* @param {number} [options.timestampMs] - When the feedback was generated. Defaults to now.
|
|
476
|
+
* @param {'pass' | 'fail'} [options.assessment] - Assessment of the feedback.
|
|
477
|
+
* @param {string} [options.reasoning] - Explanation of the feedback.
|
|
478
|
+
* @returns {void}
|
|
479
|
+
*/
|
|
480
|
+
submitFeedback (options = {}) {
|
|
481
|
+
if (!this.enabled) return
|
|
482
|
+
|
|
483
|
+
let err = ''
|
|
484
|
+
let targetType = 'other'
|
|
485
|
+
let metricTypeTag = 'other'
|
|
486
|
+
try {
|
|
487
|
+
const { span, spanId, traceId, sessionId, feedbackJoinKey, submitter } = options
|
|
488
|
+
|
|
489
|
+
// Resolved before any validation so telemetry still reports the metric type of a
|
|
490
|
+
// submission that fails on an unrelated field.
|
|
491
|
+
const metricType = options.metricType?.toLowerCase()
|
|
492
|
+
if (FEEDBACK_METRIC_TYPES.includes(metricType)) metricTypeTag = metricType
|
|
493
|
+
|
|
494
|
+
// The intake keys feedback off a single top-level identifier, so more than one target
|
|
495
|
+
// would be ambiguous and none would leave the feedback unattached. `span` also carries a
|
|
496
|
+
// traceId, but passing it is wire-equivalent to passing its `spanId` directly.
|
|
497
|
+
let targetName, targetValue
|
|
498
|
+
let targetCount = 0
|
|
499
|
+
if (span != null) { targetName = 'span'; targetValue = span.spanId; targetCount++ }
|
|
500
|
+
if (spanId != null) { targetName = 'spanId'; targetValue = spanId; targetCount++ }
|
|
501
|
+
if (traceId != null) { targetName = 'traceId'; targetValue = traceId; targetCount++ }
|
|
502
|
+
if (sessionId != null) { targetName = 'sessionId'; targetValue = sessionId; targetCount++ }
|
|
503
|
+
if (feedbackJoinKey != null) { targetName = 'feedbackJoinKey'; targetValue = feedbackJoinKey; targetCount++ }
|
|
504
|
+
|
|
505
|
+
if (targetCount !== 1) {
|
|
506
|
+
err = 'invalid_target_count'
|
|
507
|
+
throw new Error(
|
|
508
|
+
'Exactly one of `span`, `spanId`, `traceId`, `sessionId` or `feedbackJoinKey` ' +
|
|
509
|
+
'must be specified to submit feedback.'
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
targetType = FEEDBACK_TARGET_KEYS[targetName]
|
|
514
|
+
if (typeof targetValue !== 'string' || !targetValue) {
|
|
515
|
+
if (targetName === 'span') {
|
|
516
|
+
err = 'invalid_span'
|
|
517
|
+
throw new TypeError(
|
|
518
|
+
'`span` must be an object containing a non-empty string spanId. ' +
|
|
519
|
+
'`llmobs.exportSpan()` can be used to generate this object from a given span.'
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
err = `invalid_${targetType}`
|
|
523
|
+
throw new TypeError(`\`${targetName}\` must be a non-empty string`)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (typeof submitter?.id !== 'string' || !submitter.id) {
|
|
527
|
+
err = 'invalid_submitter'
|
|
528
|
+
throw new TypeError('submitter must be an object containing a non-empty string id')
|
|
529
|
+
}
|
|
530
|
+
if (submitter.type != null && typeof submitter.type !== 'string') {
|
|
531
|
+
err = 'invalid_submitter'
|
|
532
|
+
throw new TypeError('submitter.type must be a string')
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const mlApp = options.mlApp || this._config.llmobs.mlApp
|
|
536
|
+
if (!mlApp) {
|
|
537
|
+
err = 'missing_ml_app'
|
|
538
|
+
throw new Error('ML App name is required for sending feedback. Feedback data will not be sent.')
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const timestampMs = options.timestampMs || Date.now()
|
|
542
|
+
validateTimestamp(timestampMs, 'feedback')
|
|
543
|
+
|
|
544
|
+
const { label, value, tags, reasoning, assessment } = options
|
|
545
|
+
const labelValue = validateLabel(label, 'feedback')
|
|
546
|
+
validateMetricType(metricType, 'feedback')
|
|
547
|
+
validateMetricValue(metricType, value)
|
|
548
|
+
validateAssessment(assessment)
|
|
549
|
+
validateReasoning(reasoning)
|
|
550
|
+
|
|
551
|
+
const payload = {
|
|
552
|
+
event_kind: 'feedback',
|
|
553
|
+
[targetType]: targetValue,
|
|
554
|
+
label: labelValue,
|
|
555
|
+
metric_type: metricType,
|
|
556
|
+
ml_app: mlApp,
|
|
557
|
+
[`${metricType}_value`]: value,
|
|
558
|
+
timestamp_ms: timestampMs,
|
|
559
|
+
tags: buildMetricTags(tags, mlApp, 'feedback'),
|
|
560
|
+
submitter: submitter.type == null ? { id: submitter.id } : { id: submitter.id, type: submitter.type },
|
|
561
|
+
}
|
|
562
|
+
if (reasoning != null) {
|
|
563
|
+
payload.reasoning = reasoning
|
|
564
|
+
}
|
|
565
|
+
if (assessment != null) {
|
|
566
|
+
payload.assessment = assessment
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const currentStore = storage.getStore()
|
|
570
|
+
const routing = currentStore?.routingContext
|
|
571
|
+
evalMetricAppendCh.publish({ payload, routing })
|
|
572
|
+
} catch (e) {
|
|
573
|
+
if (e.ddErrorTag) err = e.ddErrorTag
|
|
574
|
+
throw e
|
|
575
|
+
} finally {
|
|
576
|
+
telemetry.recordSubmitFeedback(metricTypeTag, targetType, err)
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
499
580
|
annotationContext (options, fn) {
|
|
500
581
|
if (!this.enabled) return fn()
|
|
501
582
|
|
|
@@ -27,6 +27,8 @@ const {
|
|
|
27
27
|
ML_APP,
|
|
28
28
|
TAGS,
|
|
29
29
|
PARENT_ID_KEY,
|
|
30
|
+
PARENT_AGENT_NAME,
|
|
31
|
+
PARENT_AGENT_SPAN_ID,
|
|
30
32
|
SESSION_ID,
|
|
31
33
|
NAME,
|
|
32
34
|
INPUT_PROMPT,
|
|
@@ -157,6 +159,20 @@ class LLMObsSpanProcessor {
|
|
|
157
159
|
this.#addObject(mlObsTags[TOOL_DEFINITIONS], meta.tool_definitions)
|
|
158
160
|
}
|
|
159
161
|
|
|
162
|
+
// Surface the agent attribution resolved at registration, but only on spans that actually
|
|
163
|
+
// have an agent ancestor. The id is always present in that case; the name may be missing when
|
|
164
|
+
// it arrived id-only over distributed propagation (older/unsafe upstream). Emit the name as
|
|
165
|
+
// explicit `null` then, matching the cross-language wire shape (dd-trace-py sends null, not an
|
|
166
|
+
// absent key) so the shared backend and system-tests see the same payload.
|
|
167
|
+
const parentAgentName = mlObsTags[PARENT_AGENT_NAME]
|
|
168
|
+
const parentAgentSpanId = mlObsTags[PARENT_AGENT_SPAN_ID]
|
|
169
|
+
if (parentAgentName != null || parentAgentSpanId != null) {
|
|
170
|
+
meta.agent_attribution = {
|
|
171
|
+
pagent_name: parentAgentName ?? null,
|
|
172
|
+
pagent_span_id: parentAgentSpanId,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
160
176
|
const llmObsSpan = new LLMObservabilitySpan(spanKind)
|
|
161
177
|
|
|
162
178
|
if (spanKind === 'llm' && mlObsTags[INPUT_MESSAGES]) {
|
|
@@ -23,7 +23,11 @@ const {
|
|
|
23
23
|
OUTPUT_MESSAGES,
|
|
24
24
|
TAGS,
|
|
25
25
|
NAME,
|
|
26
|
+
PARENT_AGENT_NAME,
|
|
27
|
+
PARENT_AGENT_SPAN_ID,
|
|
26
28
|
PROPAGATED_PARENT_ID_KEY,
|
|
29
|
+
PROPAGATED_PARENT_AGENT_ID_KEY,
|
|
30
|
+
PROPAGATED_PARENT_AGENT_NAME_KEY,
|
|
27
31
|
ROOT_PARENT_ID,
|
|
28
32
|
CACHE_READ_INPUT_TOKENS_METRIC_KEY,
|
|
29
33
|
CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
|
|
@@ -57,6 +61,7 @@ const {
|
|
|
57
61
|
const { storage } = require('./storage')
|
|
58
62
|
const {
|
|
59
63
|
findGenAIAncestorSpanId,
|
|
64
|
+
resolveAgentAttribution,
|
|
60
65
|
validateCostTags,
|
|
61
66
|
writeBridgeTags,
|
|
62
67
|
validateToolDefinitions,
|
|
@@ -173,6 +178,7 @@ class LLMObsTagger {
|
|
|
173
178
|
this._setTag(span, PARENT_ID_KEY, parentId)
|
|
174
179
|
|
|
175
180
|
this.#tagSamplingDecision(span, parent)
|
|
181
|
+
this.#tagAgentAttribution(span, parent)
|
|
176
182
|
|
|
177
183
|
// apply annotation context
|
|
178
184
|
const annotationContext = storage.getStore()?.annotationContext
|
|
@@ -229,6 +235,34 @@ class LLMObsTagger {
|
|
|
229
235
|
if (samplingDecision != null) this._setTag(span, SAMPLING_DECISION, samplingDecision)
|
|
230
236
|
}
|
|
231
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Store the nearest agent ancestor on the span so it can be surfaced as
|
|
240
|
+
* `meta.agent_attribution` at finish. Resolved once here, at registration, so downstream
|
|
241
|
+
* children inherit it with a single lookup rather than walking the ancestor chain.
|
|
242
|
+
*
|
|
243
|
+
* @param {import('../opentracing/span')} span
|
|
244
|
+
* @param {import('../opentracing/span')} [parent] the LLMObs parent span, if any
|
|
245
|
+
*/
|
|
246
|
+
// TODO: spans whose kind changes after registration (e.g. claude-agent-sdk tools promoted to
|
|
247
|
+
// sub-agents) will not retroactively update already-finished children's attribution. Follow up.
|
|
248
|
+
#tagAgentAttribution (span, parent) {
|
|
249
|
+
let name, spanId
|
|
250
|
+
if (registry.has(parent)) {
|
|
251
|
+
// Local LLMObs parent: attribute to it if it is an agent, else inherit its resolution.
|
|
252
|
+
({ name, spanId } = resolveAgentAttribution(registry.get(parent), parent))
|
|
253
|
+
} else if (span.context()._trace.tags[PROPAGATED_PARENT_ID_KEY]) {
|
|
254
|
+
// Distributed LLMObs parent: inherit the nearest agent propagated from upstream. The
|
|
255
|
+
// name may be absent when the upstream hop ran an older SDK or its name was not
|
|
256
|
+
// wire-safe; the id-only case is expected and the backend resolves the name by span id.
|
|
257
|
+
const traceTags = span.context()._trace.tags
|
|
258
|
+
name = traceTags[PROPAGATED_PARENT_AGENT_NAME_KEY]
|
|
259
|
+
spanId = traceTags[PROPAGATED_PARENT_AGENT_ID_KEY]
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (name != null) this._setTag(span, PARENT_AGENT_NAME, name)
|
|
263
|
+
if (spanId != null) this._setTag(span, PARENT_AGENT_SPAN_ID, spanId)
|
|
264
|
+
}
|
|
265
|
+
|
|
232
266
|
// TODO: similarly for the following `tag` methods,
|
|
233
267
|
// how can we transition from a span weakmap to core API functionality
|
|
234
268
|
tagLLMIO (span, inputData, outputData) {
|
|
@@ -696,6 +730,59 @@ class LLMObsTagger {
|
|
|
696
730
|
return filteredAudioParts
|
|
697
731
|
}
|
|
698
732
|
|
|
733
|
+
// Image counterpart of #filterAudioParts, with the same wire shape and validation.
|
|
734
|
+
#filterImageParts (imageParts) {
|
|
735
|
+
if (!Array.isArray(imageParts)) {
|
|
736
|
+
imageParts = [imageParts]
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const filteredImageParts = []
|
|
740
|
+
for (const imagePart of imageParts) {
|
|
741
|
+
if (imagePart == null || typeof imagePart !== 'object') {
|
|
742
|
+
this.#handleFailure('Image part must be an object.', 'invalid_io_messages')
|
|
743
|
+
continue
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const { mimeType, content, attachmentKey } = imagePart
|
|
747
|
+
|
|
748
|
+
if (typeof mimeType !== 'string' || !mimeType) {
|
|
749
|
+
this.#handleFailure('Image part mimeType must be a non-empty string.', 'invalid_io_messages')
|
|
750
|
+
continue
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (content == null && attachmentKey == null) {
|
|
754
|
+
this.#handleFailure("Image part must have either 'content' or 'attachmentKey'.", 'invalid_io_messages')
|
|
755
|
+
continue
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (content != null && attachmentKey != null) {
|
|
759
|
+
this.#handleFailure(
|
|
760
|
+
"Image part must have only one of 'content' or 'attachmentKey', not both.", 'invalid_io_messages'
|
|
761
|
+
)
|
|
762
|
+
continue
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const imagePartObj = { mime_type: mimeType }
|
|
766
|
+
|
|
767
|
+
if (content == null) {
|
|
768
|
+
if (typeof attachmentKey !== 'string') {
|
|
769
|
+
this.#handleFailure('Image part attachmentKey must be a string.', 'invalid_io_messages')
|
|
770
|
+
continue
|
|
771
|
+
}
|
|
772
|
+
imagePartObj.attachment_key = attachmentKey
|
|
773
|
+
} else {
|
|
774
|
+
if (typeof content !== 'string') {
|
|
775
|
+
this.#handleFailure('Image part content must be a base64-encoded string.', 'invalid_io_messages')
|
|
776
|
+
continue
|
|
777
|
+
}
|
|
778
|
+
imagePartObj.content = content
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
filteredImageParts.push(imagePartObj)
|
|
782
|
+
}
|
|
783
|
+
return filteredImageParts
|
|
784
|
+
}
|
|
785
|
+
|
|
699
786
|
#tagMessages (span, data, key) {
|
|
700
787
|
if (!data) {
|
|
701
788
|
return
|
|
@@ -723,6 +810,7 @@ class LLMObsTagger {
|
|
|
723
810
|
toolResults,
|
|
724
811
|
toolId,
|
|
725
812
|
audioParts,
|
|
813
|
+
imageParts,
|
|
726
814
|
} = message
|
|
727
815
|
const messageObj = {}
|
|
728
816
|
|
|
@@ -764,6 +852,14 @@ class LLMObsTagger {
|
|
|
764
852
|
}
|
|
765
853
|
}
|
|
766
854
|
|
|
855
|
+
if (imageParts != null) {
|
|
856
|
+
const filteredImageParts = this.#filterImageParts(imageParts)
|
|
857
|
+
|
|
858
|
+
if (filteredImageParts.length) {
|
|
859
|
+
messageObj.image_parts = filteredImageParts
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
767
863
|
if (toolId) {
|
|
768
864
|
if (role === 'tool') {
|
|
769
865
|
condition = this.#tagConditionalString(toolId, 'Tool ID', messageObj, 'tool_id') && condition
|
|
@@ -183,6 +183,23 @@ function recordSubmitEvaluation (options, err, value = 1) {
|
|
|
183
183
|
llmobsMetrics.count('evals_submitted', tags).inc(value)
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* @param {string} metricType - The submitted metric type, `'other'` if unrecognized.
|
|
188
|
+
* @param {string} targetType - The payload key the feedback was attached to, `'other'` if unresolved.
|
|
189
|
+
* @param {string} err - The telemetry error tag, empty when the submission succeeded.
|
|
190
|
+
* @param {number} [value]
|
|
191
|
+
* @returns {void}
|
|
192
|
+
*/
|
|
193
|
+
function recordSubmitFeedback (metricType, targetType, err, value = 1) {
|
|
194
|
+
const tags = {
|
|
195
|
+
error: Number(!!err),
|
|
196
|
+
metric_type: metricType,
|
|
197
|
+
target_type: targetType,
|
|
198
|
+
}
|
|
199
|
+
if (err) tags.error_type = err
|
|
200
|
+
llmobsMetrics.count('feedback_submitted', tags).inc(value)
|
|
201
|
+
}
|
|
202
|
+
|
|
186
203
|
function recordLLMObsUserProcessorCalled (error, value = 1) {
|
|
187
204
|
const tags = { error: error ? 1 : 0 }
|
|
188
205
|
llmobsMetrics.count('user_processor_called', tags).inc(value)
|
|
@@ -201,5 +218,6 @@ module.exports = {
|
|
|
201
218
|
recordUserFlush,
|
|
202
219
|
recordExportSpan,
|
|
203
220
|
recordSubmitEvaluation,
|
|
221
|
+
recordSubmitFeedback,
|
|
204
222
|
recordLLMObsUserProcessorCalled,
|
|
205
223
|
}
|
|
@@ -6,6 +6,10 @@ const {
|
|
|
6
6
|
LLMOBS_PARENT_ID_BRIDGE_KEY,
|
|
7
7
|
LLMOBS_TRACE_ID_BRIDGE_KEY,
|
|
8
8
|
SPAN_KINDS,
|
|
9
|
+
SPAN_KIND,
|
|
10
|
+
NAME,
|
|
11
|
+
PARENT_AGENT_NAME,
|
|
12
|
+
PARENT_AGENT_SPAN_ID,
|
|
9
13
|
} = require('./constants/tags')
|
|
10
14
|
|
|
11
15
|
const DECIMAL_TRACE_ID_REGEX = /^\d+$/
|
|
@@ -294,6 +298,76 @@ function getFunctionArguments (fn, args = []) {
|
|
|
294
298
|
}
|
|
295
299
|
}
|
|
296
300
|
|
|
301
|
+
// The `x-datadog-tags` tagset encoder rejects commas (the entry delimiter) and any byte
|
|
302
|
+
// outside 0x20-0x7E, and a raise there drops the ENTIRE header (taking ml_app,
|
|
303
|
+
// llmobs_trace_id, parent_id, sampling with it). An agent name is arbitrary user text, so it
|
|
304
|
+
// must be skipped rather than sanitized when unsafe: only the digit-safe id then propagates and
|
|
305
|
+
// the backend resolves the real name by span id. `=` is legal in tagset values (illegal only in
|
|
306
|
+
// keys), so a name containing `=` is safe and must not be dropped.
|
|
307
|
+
/**
|
|
308
|
+
* @param {string} name
|
|
309
|
+
* @returns {boolean}
|
|
310
|
+
*/
|
|
311
|
+
function agentNameWireSafe (name) {
|
|
312
|
+
// Conservative slice of the 512B shared tagset budget, mirroring dd-trace-py.
|
|
313
|
+
if (Buffer.byteLength(name, 'utf8') > 256) return false
|
|
314
|
+
for (let index = 0; index < name.length; index++) {
|
|
315
|
+
const code = name.charCodeAt(index)
|
|
316
|
+
if (code < 0x20 || code > 0x7E || code === 0x2C /* comma */) return false
|
|
317
|
+
}
|
|
318
|
+
return true
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Resolve the nearest agent that a *child* of `span` should be attributed to.
|
|
323
|
+
*
|
|
324
|
+
* If `span` is itself an agent, the child attributes directly to `span`. Otherwise `span`
|
|
325
|
+
* already resolved its own nearest agent at registration, so the child inherits that with a
|
|
326
|
+
* single lookup rather than walking the ancestor chain. An agent never attributes itself.
|
|
327
|
+
*
|
|
328
|
+
* @param {Record<string, unknown> | undefined} tags - Registry entry for the parent span.
|
|
329
|
+
* @param {import('../opentracing/span')} [span] - The parent span itself (needed for span id / name).
|
|
330
|
+
* @returns {{ name: string | undefined, spanId: string | undefined }}
|
|
331
|
+
*/
|
|
332
|
+
function resolveAgentAttribution (tags, span) {
|
|
333
|
+
if (!tags) return { name: undefined, spanId: undefined }
|
|
334
|
+
if (tags[SPAN_KIND] === 'agent') {
|
|
335
|
+
return { name: tags[NAME] || span._name, spanId: span.context().toSpanId() }
|
|
336
|
+
}
|
|
337
|
+
return { name: tags[PARENT_AGENT_NAME], spanId: tags[PARENT_AGENT_SPAN_ID] }
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Removes all `key=value` entries for the given key from a comma-separated tagset string.
|
|
342
|
+
*
|
|
343
|
+
* @param {string} tags - Existing tagset string (may be empty).
|
|
344
|
+
* @param {string} key
|
|
345
|
+
* @returns {string}
|
|
346
|
+
*/
|
|
347
|
+
function stripTagsetEntry (tags, key) {
|
|
348
|
+
if (!tags.includes(key)) return tags
|
|
349
|
+
return tags.split(',').filter(entry => !entry.startsWith(`${key}=`)).join(',')
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Appends `key=value` to the tagset string with a comma separator, but only when `value` is
|
|
354
|
+
* truthy, passes the optional `safeguard` predicate, and fits within `maxTagSetLength`. Returns
|
|
355
|
+
* the original `tags` unchanged when the value is absent, unsafe, or would overflow the budget.
|
|
356
|
+
*
|
|
357
|
+
* @param {string} tags - Existing tagset string (may be empty).
|
|
358
|
+
* @param {string} key
|
|
359
|
+
* @param {string | undefined} value
|
|
360
|
+
* @param {((v: string) => boolean) | null} [safeguard]
|
|
361
|
+
* @param {number} [maxTagSetLength]
|
|
362
|
+
* @returns {string}
|
|
363
|
+
*/
|
|
364
|
+
function appendOptionalPropagatedTag (tags, key, value, safeguard, maxTagSetLength) {
|
|
365
|
+
if (!value || (safeguard && !safeguard(value))) return tags
|
|
366
|
+
const entry = `${tags ? ',' : ''}${key}=${value}`
|
|
367
|
+
if (maxTagSetLength != null && tags.length + entry.length > maxTagSetLength) return tags
|
|
368
|
+
return `${tags}${entry}`
|
|
369
|
+
}
|
|
370
|
+
|
|
297
371
|
function spanHasError (span) {
|
|
298
372
|
const spanContext = span.context()
|
|
299
373
|
return !!(spanContext.getTag('error') || spanContext.getTag('error.type'))
|
|
@@ -443,6 +517,8 @@ function formatAudioPart (data, mimeType) {
|
|
|
443
517
|
}
|
|
444
518
|
|
|
445
519
|
module.exports = {
|
|
520
|
+
agentNameWireSafe,
|
|
521
|
+
appendOptionalPropagatedTag,
|
|
446
522
|
audioMimeTypeFromFormat,
|
|
447
523
|
encodeUnicode,
|
|
448
524
|
findGenAIAncestorSpanId,
|
|
@@ -450,6 +526,8 @@ module.exports = {
|
|
|
450
526
|
llmObsTraceIdToWire,
|
|
451
527
|
normalizeLlmObsTraceId,
|
|
452
528
|
formatAudioPart,
|
|
529
|
+
resolveAgentAttribution,
|
|
530
|
+
stripTagsetEntry,
|
|
453
531
|
validateCostTags,
|
|
454
532
|
validateKind,
|
|
455
533
|
getFunctionArguments,
|
|
@@ -88,6 +88,9 @@ class BaseLLMObsWriter {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
+
* @param {Record<string, unknown>} event
|
|
92
|
+
* @param {{ apiKey?: string, site?: string }} [routing]
|
|
93
|
+
* @param {number} [byteLength]
|
|
91
94
|
* @returns {boolean} `true` if the event was buffered, `false` if it was dropped
|
|
92
95
|
* (e.g. the per-routing buffer was full). Callers that depend on the event
|
|
93
96
|
* actually being submitted should check this value.
|
|
@@ -7,6 +7,8 @@ const { fetchAgentInfo } = require('../../agent/info')
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @param {import('../../config/config-base')} config
|
|
10
|
+
* @param {(agentless: boolean) => void} setWritersAgentlessValue
|
|
11
|
+
* @returns {void}
|
|
10
12
|
*/
|
|
11
13
|
function setAgentStrategy (config, setWritersAgentlessValue) {
|
|
12
14
|
const agentlessEnabled = config.llmobs.agentlessEnabled
|
|
@@ -2,15 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { channel } = require('dc-polyfill')
|
|
4
4
|
|
|
5
|
-
const Level =
|
|
6
|
-
trace: 10,
|
|
7
|
-
debug: 20,
|
|
8
|
-
info: 30,
|
|
9
|
-
warn: 40,
|
|
10
|
-
error: 50,
|
|
11
|
-
critical: 50,
|
|
12
|
-
off: 100,
|
|
13
|
-
}
|
|
5
|
+
const Level = require('./levels')
|
|
14
6
|
|
|
15
7
|
const traceChannel = channel('datadog:log:trace')
|
|
16
8
|
const debugChannel = channel('datadog:log:debug')
|
|
@@ -63,7 +63,7 @@ class ExposuresWriter extends BaseFFEWriter {
|
|
|
63
63
|
* @param {import('../../config/config-base')} config - Tracer configuration object
|
|
64
64
|
*/
|
|
65
65
|
constructor (config) {
|
|
66
|
-
const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(
|
|
66
|
+
const basePath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
|
|
67
67
|
const endpoint = EXPOSURES_ENDPOINT.replace(/^\/+/, '')
|
|
68
68
|
const fullEndpoint = `${basePath}/${endpoint}`
|
|
69
69
|
|
|
@@ -18,7 +18,7 @@ function setAgentStrategy (config, setWriterEnabledValue) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const endpoints = agentInfo.endpoints
|
|
21
|
-
const normalizedPath = EVP_PROXY_AGENT_BASE_PATH.replace(
|
|
21
|
+
const normalizedPath = EVP_PROXY_AGENT_BASE_PATH.replace(/\/$/, '')
|
|
22
22
|
const hasEndpoint = Array.isArray(endpoints) &&
|
|
23
23
|
endpoints.includes(normalizedPath) || endpoints.includes(normalizedPath + '/')
|
|
24
24
|
|
|
@@ -22,7 +22,7 @@ class BatchLogRecordProcessor {
|
|
|
22
22
|
/**
|
|
23
23
|
* Creates a new BatchLogRecordProcessor instance.
|
|
24
24
|
*
|
|
25
|
-
* @param {
|
|
25
|
+
* @param {import('./otlp_http_log_exporter')} exporter - Log processor for exporting batches to Datadog Agent
|
|
26
26
|
* @param {number} batchTimeout - Timeout in milliseconds for batch processing
|
|
27
27
|
* @param {number} maxExportBatchSize - Maximum number of log records per batch
|
|
28
28
|
*/
|