dd-trace 5.70.0 → 5.72.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/LICENSE-3rdparty.csv +5 -0
- package/index.d.ts +110 -1
- package/initialize.mjs +7 -1
- package/package.json +21 -2
- package/packages/datadog-instrumentations/src/anthropic.js +115 -0
- package/packages/datadog-instrumentations/src/azure-event-hubs.js +37 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
- package/packages/datadog-instrumentations/src/cucumber.js +7 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/jest.js +29 -36
- package/packages/datadog-instrumentations/src/mocha/main.js +8 -9
- package/packages/datadog-instrumentations/src/mocha/utils.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +5 -5
- package/packages/datadog-instrumentations/src/vitest.js +8 -8
- package/packages/datadog-plugin-anthropic/src/index.js +17 -0
- package/packages/datadog-plugin-anthropic/src/tracing.js +30 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +73 -27
- package/packages/datadog-plugin-azure-event-hubs/src/index.js +15 -0
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +82 -0
- package/packages/datadog-plugin-azure-functions/src/index.js +37 -0
- package/packages/datadog-plugin-cucumber/src/index.js +3 -3
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -9
- package/packages/datadog-plugin-jest/src/util.js +10 -2
- package/packages/datadog-plugin-mocha/src/index.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +2 -2
- package/packages/datadog-plugin-vitest/src/index.js +2 -2
- package/packages/datadog-plugin-ws/src/server.js +5 -3
- package/packages/dd-trace/src/appsec/reporter.js +70 -21
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
- package/packages/dd-trace/src/config.js +110 -26
- package/packages/dd-trace/src/config_defaults.js +14 -0
- package/packages/dd-trace/src/git_properties.js +90 -5
- package/packages/dd-trace/src/llmobs/plugins/anthropic.js +282 -0
- package/packages/dd-trace/src/llmobs/tagger.js +35 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -0
- package/packages/dd-trace/src/openfeature/constants/constants.js +51 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +45 -0
- package/packages/dd-trace/src/openfeature/index.js +77 -0
- package/packages/dd-trace/src/openfeature/noop.js +101 -0
- package/packages/dd-trace/src/openfeature/writers/base.js +181 -0
- package/packages/dd-trace/src/openfeature/writers/exposures.js +173 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +43 -0
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +100 -0
- package/packages/dd-trace/src/opentelemetry/logs/index.js +87 -0
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +77 -0
- package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +126 -0
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +173 -0
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +367 -0
- package/packages/dd-trace/src/opentelemetry/protos/common.proto +116 -0
- package/packages/dd-trace/src/opentelemetry/protos/logs.proto +226 -0
- package/packages/dd-trace/src/opentelemetry/protos/logs_service.proto +78 -0
- package/packages/dd-trace/src/opentelemetry/protos/protobuf_loader.js +48 -0
- package/packages/dd-trace/src/opentelemetry/protos/resource.proto +45 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +7 -6
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +6 -5
- package/packages/dd-trace/src/profiling/config.js +21 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -2
- package/packages/dd-trace/src/profiling/profiler.js +44 -22
- package/packages/dd-trace/src/profiling/profilers/events.js +12 -3
- package/packages/dd-trace/src/profiling/profilers/space.js +35 -24
- package/packages/dd-trace/src/profiling/profilers/wall.js +14 -6
- package/packages/dd-trace/src/proxy.js +22 -1
- package/packages/dd-trace/src/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/remote_config/index.js +3 -0
- package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
- package/packages/dd-trace/src/supported-configurations.json +18 -0
- package/packages/dd-trace/src/telemetry/telemetry.js +13 -1
- package/register.js +9 -1
|
@@ -9,7 +9,8 @@ const tagger = require('./tagger')
|
|
|
9
9
|
const set = require('../../datadog-core/src/utils/src/set')
|
|
10
10
|
const { isTrue, isFalse, normalizeProfilingEnabledValue } = require('./util')
|
|
11
11
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('./plugins/util/tags')
|
|
12
|
-
const { getGitMetadataFromGitProperties, removeUserSensitiveInfo } =
|
|
12
|
+
const { getGitMetadataFromGitProperties, removeUserSensitiveInfo, getRemoteOriginURL, resolveGitHeadSHA } =
|
|
13
|
+
require('./git_properties')
|
|
13
14
|
const { updateConfig } = require('./telemetry')
|
|
14
15
|
const telemetryMetrics = require('./telemetry/metrics')
|
|
15
16
|
const { isInServerlessEnvironment, getIsGCPFunction, getIsAzureFunction } = require('./serverless')
|
|
@@ -17,6 +18,7 @@ const { ORIGIN_KEY } = require('./constants')
|
|
|
17
18
|
const { appendRules } = require('./payload-tagging/config')
|
|
18
19
|
const { getEnvironmentVariable, getEnvironmentVariables } = require('./config-helper')
|
|
19
20
|
const defaults = require('./config_defaults')
|
|
21
|
+
const path = require('path')
|
|
20
22
|
|
|
21
23
|
const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
22
24
|
|
|
@@ -67,6 +69,8 @@ const VALID_PROPAGATION_BEHAVIOR_EXTRACT = new Set(['continue', 'restart', 'igno
|
|
|
67
69
|
|
|
68
70
|
const VALID_LOG_LEVELS = new Set(['debug', 'info', 'warn', 'error'])
|
|
69
71
|
|
|
72
|
+
const DEFAULT_OTLP_PORT = 4318
|
|
73
|
+
|
|
70
74
|
function getFromOtelSamplerMap (otelTracesSampler, otelTracesSamplerArg) {
|
|
71
75
|
const OTEL_TRACES_SAMPLER_MAPPING = {
|
|
72
76
|
always_on: '1.0',
|
|
@@ -380,30 +384,7 @@ class Config {
|
|
|
380
384
|
}
|
|
381
385
|
|
|
382
386
|
if (this.gitMetadataEnabled) {
|
|
383
|
-
this.
|
|
384
|
-
getEnvironmentVariable('DD_GIT_REPOSITORY_URL') ??
|
|
385
|
-
this.tags[GIT_REPOSITORY_URL]
|
|
386
|
-
)
|
|
387
|
-
this.commitSHA = getEnvironmentVariable('DD_GIT_COMMIT_SHA') ??
|
|
388
|
-
this.tags[GIT_COMMIT_SHA]
|
|
389
|
-
if (!this.repositoryUrl || !this.commitSHA) {
|
|
390
|
-
const DD_GIT_PROPERTIES_FILE = getEnvironmentVariable('DD_GIT_PROPERTIES_FILE') ??
|
|
391
|
-
`${process.cwd()}/git.properties`
|
|
392
|
-
let gitPropertiesString
|
|
393
|
-
try {
|
|
394
|
-
gitPropertiesString = fs.readFileSync(DD_GIT_PROPERTIES_FILE, 'utf8')
|
|
395
|
-
} catch (e) {
|
|
396
|
-
// Only log error if the user has set a git.properties path
|
|
397
|
-
if (getEnvironmentVariable('DD_GIT_PROPERTIES_FILE')) {
|
|
398
|
-
log.error('Error reading DD_GIT_PROPERTIES_FILE: %s', DD_GIT_PROPERTIES_FILE, e)
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
if (gitPropertiesString) {
|
|
402
|
-
const { commitSHA, repositoryUrl } = getGitMetadataFromGitProperties(gitPropertiesString)
|
|
403
|
-
this.commitSHA = this.commitSHA || commitSHA
|
|
404
|
-
this.repositoryUrl = this.repositoryUrl || repositoryUrl
|
|
405
|
-
}
|
|
406
|
-
}
|
|
387
|
+
this._loadGitMetadata()
|
|
407
388
|
}
|
|
408
389
|
}
|
|
409
390
|
|
|
@@ -554,6 +535,7 @@ class Config {
|
|
|
554
535
|
DD_INSTRUMENTATION_TELEMETRY_ENABLED,
|
|
555
536
|
DD_INSTRUMENTATION_CONFIG_ID,
|
|
556
537
|
DD_LOGS_INJECTION,
|
|
538
|
+
DD_LOGS_OTEL_ENABLED,
|
|
557
539
|
DD_LANGCHAIN_SPAN_CHAR_LIMIT,
|
|
558
540
|
DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE,
|
|
559
541
|
DD_LLMOBS_AGENTLESS_ENABLED,
|
|
@@ -635,7 +617,18 @@ class Config {
|
|
|
635
617
|
OTEL_RESOURCE_ATTRIBUTES,
|
|
636
618
|
OTEL_SERVICE_NAME,
|
|
637
619
|
OTEL_TRACES_SAMPLER,
|
|
638
|
-
OTEL_TRACES_SAMPLER_ARG
|
|
620
|
+
OTEL_TRACES_SAMPLER_ARG,
|
|
621
|
+
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED,
|
|
622
|
+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
|
|
623
|
+
OTEL_EXPORTER_OTLP_LOGS_HEADERS,
|
|
624
|
+
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL,
|
|
625
|
+
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
|
|
626
|
+
OTEL_EXPORTER_OTLP_PROTOCOL,
|
|
627
|
+
OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
628
|
+
OTEL_EXPORTER_OTLP_HEADERS,
|
|
629
|
+
OTEL_EXPORTER_OTLP_TIMEOUT,
|
|
630
|
+
OTEL_BSP_SCHEDULE_DELAY,
|
|
631
|
+
OTEL_BSP_MAX_EXPORT_BATCH_SIZE
|
|
639
632
|
} = getEnvironmentVariables()
|
|
640
633
|
|
|
641
634
|
const tags = {}
|
|
@@ -649,6 +642,23 @@ class Config {
|
|
|
649
642
|
tagger.add(tags, DD_TRACE_TAGS)
|
|
650
643
|
tagger.add(tags, DD_TRACE_GLOBAL_TAGS)
|
|
651
644
|
|
|
645
|
+
this._setBoolean(env, 'otelLogsEnabled', isTrue(DD_LOGS_OTEL_ENABLED))
|
|
646
|
+
// Set OpenTelemetry logs configuration with specific _LOGS_ vars taking precedence over generic _EXPORTERS_ vars
|
|
647
|
+
if (OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
648
|
+
// Only set if there's a custom URL, otherwise let calc phase handle the default
|
|
649
|
+
this._setString(env, 'otelUrl', OTEL_EXPORTER_OTLP_ENDPOINT)
|
|
650
|
+
}
|
|
651
|
+
if (OTEL_EXPORTER_OTLP_ENDPOINT || OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
|
|
652
|
+
this._setString(env, 'otelLogsUrl', OTEL_EXPORTER_OTLP_LOGS_ENDPOINT || env.otelUrl)
|
|
653
|
+
}
|
|
654
|
+
this._setString(env, 'otelHeaders', OTEL_EXPORTER_OTLP_HEADERS)
|
|
655
|
+
this._setString(env, 'otelLogsHeaders', OTEL_EXPORTER_OTLP_LOGS_HEADERS || env.otelHeaders)
|
|
656
|
+
this._setString(env, 'otelProtocol', OTEL_EXPORTER_OTLP_PROTOCOL)
|
|
657
|
+
this._setString(env, 'otelLogsProtocol', OTEL_EXPORTER_OTLP_LOGS_PROTOCOL || env.otelProtocol)
|
|
658
|
+
env.otelTimeout = maybeInt(OTEL_EXPORTER_OTLP_TIMEOUT)
|
|
659
|
+
env.otelLogsTimeout = maybeInt(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT) || env.otelTimeout
|
|
660
|
+
env.otelLogsBatchTimeout = maybeInt(OTEL_BSP_SCHEDULE_DELAY)
|
|
661
|
+
env.otelLogsMaxExportBatchSize = maybeInt(OTEL_BSP_MAX_EXPORT_BATCH_SIZE)
|
|
652
662
|
this._setBoolean(
|
|
653
663
|
env,
|
|
654
664
|
'apmTracingEnabled',
|
|
@@ -668,6 +678,7 @@ class Config {
|
|
|
668
678
|
this._envUnprocessed['appsec.blockedTemplateJson'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON
|
|
669
679
|
this._setBoolean(env, 'appsec.enabled', DD_APPSEC_ENABLED)
|
|
670
680
|
this._setString(env, 'appsec.eventTracking.mode', DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE)
|
|
681
|
+
// TODO appsec.extendedHeadersCollection are deprecated, to delete in a major
|
|
671
682
|
this._setBoolean(env, 'appsec.extendedHeadersCollection.enabled', DD_APPSEC_COLLECT_ALL_HEADERS)
|
|
672
683
|
this._setBoolean(
|
|
673
684
|
env,
|
|
@@ -679,6 +690,7 @@ class Config {
|
|
|
679
690
|
this._setString(env, 'appsec.obfuscatorKeyRegex', DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP)
|
|
680
691
|
this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
|
|
681
692
|
this._setBoolean(env, 'appsec.rasp.enabled', DD_APPSEC_RASP_ENABLED)
|
|
693
|
+
// TODO Deprecated, to delete in a major
|
|
682
694
|
this._setBoolean(env, 'appsec.rasp.bodyCollection', DD_APPSEC_RASP_COLLECT_REQUEST_BODY)
|
|
683
695
|
env['appsec.rateLimit'] = maybeInt(DD_APPSEC_TRACE_RATE_LIMIT)
|
|
684
696
|
this._envUnprocessed['appsec.rateLimit'] = DD_APPSEC_TRACE_RATE_LIMIT
|
|
@@ -719,6 +731,7 @@ class Config {
|
|
|
719
731
|
env['dynamicInstrumentation.uploadIntervalSeconds'] = maybeFloat(DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS)
|
|
720
732
|
this._envUnprocessed['dynamicInstrumentation.uploadInterval'] = DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS
|
|
721
733
|
this._setString(env, 'env', DD_ENV || tags.env)
|
|
734
|
+
this._setBoolean(env, 'experimental.flaggingProvider.enabled', DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED)
|
|
722
735
|
this._setBoolean(env, 'traceEnabled', DD_TRACE_ENABLED)
|
|
723
736
|
this._setBoolean(env, 'experimental.aiguard.enabled', DD_AI_GUARD_ENABLED)
|
|
724
737
|
this._setString(env, 'experimental.aiguard.endpoint', DD_AI_GUARD_ENDPOINT)
|
|
@@ -968,6 +981,7 @@ class Config {
|
|
|
968
981
|
this._optsUnprocessed['experimental.aiguard.timeout'] = options.experimental?.aiguard?.timeout
|
|
969
982
|
this._setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData)
|
|
970
983
|
this._setString(opts, 'experimental.exporter', options.experimental?.exporter)
|
|
984
|
+
this._setBoolean(opts, 'experimental.flaggingProvider.enabled', options.experimental?.flaggingProvider?.enabled)
|
|
971
985
|
opts.flushInterval = maybeInt(options.flushInterval)
|
|
972
986
|
this._optsUnprocessed.flushInterval = options.flushInterval
|
|
973
987
|
opts.flushMinSpans = maybeInt(options.flushMinSpans)
|
|
@@ -1154,7 +1168,20 @@ class Config {
|
|
|
1154
1168
|
calc.testManagementAttemptToFixRetries = maybeInt(DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES) ?? 20
|
|
1155
1169
|
this._setBoolean(calc, 'isImpactedTestsEnabled', !isFalse(DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED))
|
|
1156
1170
|
}
|
|
1171
|
+
|
|
1172
|
+
// Disable log injection when OTEL logs are enabled
|
|
1173
|
+
// OTEL logs and DD log injection are mutually exclusive
|
|
1174
|
+
if (this._env.otelLogsEnabled) {
|
|
1175
|
+
this._setBoolean(calc, 'logInjection', false)
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1157
1178
|
calc['dogstatsd.hostname'] = this._getHostname()
|
|
1179
|
+
|
|
1180
|
+
// Compute OTLP logs URL to send payloads to the active Datadog Agent
|
|
1181
|
+
const agentHostname = this._getHostname()
|
|
1182
|
+
calc.otelLogsUrl = `http://${agentHostname}:${DEFAULT_OTLP_PORT}`
|
|
1183
|
+
calc.otelUrl = `http://${agentHostname}:${DEFAULT_OTLP_PORT}`
|
|
1184
|
+
|
|
1158
1185
|
this._setBoolean(calc, 'isGitUploadEnabled',
|
|
1159
1186
|
calc.isIntelligentTestRunnerEnabled && !isFalse(this._isCiVisibilityGitUploadEnabled()))
|
|
1160
1187
|
this._setBoolean(calc, 'spanComputePeerService', this._getSpanComputePeerService())
|
|
@@ -1364,6 +1391,63 @@ class Config {
|
|
|
1364
1391
|
}
|
|
1365
1392
|
}
|
|
1366
1393
|
}
|
|
1394
|
+
|
|
1395
|
+
_loadGitMetadata () {
|
|
1396
|
+
// try to read Git metadata from the environment variables
|
|
1397
|
+
this.repositoryUrl = removeUserSensitiveInfo(
|
|
1398
|
+
getEnvironmentVariable('DD_GIT_REPOSITORY_URL') ??
|
|
1399
|
+
this.tags[GIT_REPOSITORY_URL]
|
|
1400
|
+
)
|
|
1401
|
+
this.commitSHA = getEnvironmentVariable('DD_GIT_COMMIT_SHA') ??
|
|
1402
|
+
this.tags[GIT_COMMIT_SHA]
|
|
1403
|
+
|
|
1404
|
+
// otherwise, try to read Git metadata from the git.properties file
|
|
1405
|
+
if (!this.repositoryUrl || !this.commitSHA) {
|
|
1406
|
+
const DD_GIT_PROPERTIES_FILE = getEnvironmentVariable('DD_GIT_PROPERTIES_FILE') ??
|
|
1407
|
+
`${process.cwd()}/git.properties`
|
|
1408
|
+
let gitPropertiesString
|
|
1409
|
+
try {
|
|
1410
|
+
gitPropertiesString = fs.readFileSync(DD_GIT_PROPERTIES_FILE, 'utf8')
|
|
1411
|
+
} catch (e) {
|
|
1412
|
+
// Only log error if the user has set a git.properties path
|
|
1413
|
+
if (getEnvironmentVariable('DD_GIT_PROPERTIES_FILE')) {
|
|
1414
|
+
log.error('Error reading DD_GIT_PROPERTIES_FILE: %s', DD_GIT_PROPERTIES_FILE, e)
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (gitPropertiesString) {
|
|
1418
|
+
const { commitSHA, repositoryUrl } = getGitMetadataFromGitProperties(gitPropertiesString)
|
|
1419
|
+
this.commitSHA = this.commitSHA || commitSHA
|
|
1420
|
+
this.repositoryUrl = this.repositoryUrl || repositoryUrl
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
// otherwise, try to read Git metadata from the .git/ folder
|
|
1424
|
+
if (!this.repositoryUrl || !this.commitSHA) {
|
|
1425
|
+
const DD_GIT_FOLDER_PATH = getEnvironmentVariable('DD_GIT_FOLDER_PATH') ??
|
|
1426
|
+
path.join(process.cwd(), '.git')
|
|
1427
|
+
if (!this.repositoryUrl) {
|
|
1428
|
+
// try to read git config (repository URL)
|
|
1429
|
+
const gitConfigPath = path.join(DD_GIT_FOLDER_PATH, 'config')
|
|
1430
|
+
try {
|
|
1431
|
+
const gitConfigContent = fs.readFileSync(gitConfigPath, 'utf8')
|
|
1432
|
+
if (gitConfigContent) {
|
|
1433
|
+
this.repositoryUrl = getRemoteOriginURL(gitConfigContent)
|
|
1434
|
+
}
|
|
1435
|
+
} catch (e) {
|
|
1436
|
+
// Only log error if the user has set a .git/ path
|
|
1437
|
+
if (getEnvironmentVariable('DD_GIT_FOLDER_PATH')) {
|
|
1438
|
+
log.error('Error reading git config: %s', gitConfigPath, e)
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
if (!this.commitSHA) {
|
|
1443
|
+
// try to read git HEAD (commit SHA)
|
|
1444
|
+
const gitHeadSha = resolveGitHeadSHA(DD_GIT_FOLDER_PATH)
|
|
1445
|
+
if (gitHeadSha) {
|
|
1446
|
+
this.commitSHA = gitHeadSha
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1367
1451
|
}
|
|
1368
1452
|
|
|
1369
1453
|
function handleOtel (tagString) {
|
|
@@ -36,12 +36,14 @@ module.exports = {
|
|
|
36
36
|
'appsec.blockedTemplateJson': undefined,
|
|
37
37
|
'appsec.enabled': undefined,
|
|
38
38
|
'appsec.eventTracking.mode': 'identification',
|
|
39
|
+
// TODO appsec.extendedHeadersCollection is deprecated, to delete in a major
|
|
39
40
|
'appsec.extendedHeadersCollection.enabled': false,
|
|
40
41
|
'appsec.extendedHeadersCollection.redaction': true,
|
|
41
42
|
'appsec.extendedHeadersCollection.maxHeaders': 50,
|
|
42
43
|
'appsec.obfuscatorKeyRegex': defaultWafObfuscatorKeyRegex,
|
|
43
44
|
'appsec.obfuscatorValueRegex': defaultWafObfuscatorValueRegex,
|
|
44
45
|
'appsec.rasp.enabled': true,
|
|
46
|
+
// TODO Deprecated, to delete in a major
|
|
45
47
|
'appsec.rasp.bodyCollection': false,
|
|
46
48
|
'appsec.rateLimit': 100,
|
|
47
49
|
'appsec.rules': undefined,
|
|
@@ -75,6 +77,7 @@ module.exports = {
|
|
|
75
77
|
'experimental.aiguard.timeout': 10_000, // ms
|
|
76
78
|
'experimental.enableGetRumData': false,
|
|
77
79
|
'experimental.exporter': undefined,
|
|
80
|
+
'experimental.flaggingProvider.enabled': false,
|
|
78
81
|
flushInterval: 2000,
|
|
79
82
|
flushMinSpans: 1000,
|
|
80
83
|
gitMetadataEnabled: true,
|
|
@@ -124,6 +127,17 @@ module.exports = {
|
|
|
124
127
|
isTestManagementEnabled: false,
|
|
125
128
|
isImpactedTestsEnabled: false,
|
|
126
129
|
logInjection: true,
|
|
130
|
+
otelLogsEnabled: false,
|
|
131
|
+
otelUrl: undefined,
|
|
132
|
+
otelLogsUrl: undefined, // Will be computed using agent host
|
|
133
|
+
otelHeaders: undefined,
|
|
134
|
+
otelLogsHeaders: '',
|
|
135
|
+
otelProtocol: 'http/protobuf',
|
|
136
|
+
otelLogsProtocol: 'http/protobuf',
|
|
137
|
+
otelLogsTimeout: 10_000,
|
|
138
|
+
otelTimeout: 10_000,
|
|
139
|
+
otelLogsBatchTimeout: 5000,
|
|
140
|
+
otelLogsMaxExportBatchSize: 512,
|
|
127
141
|
lookup: undefined,
|
|
128
142
|
inferredProxyServicesEnabled: false,
|
|
129
143
|
memcachedCommandEnabled: false,
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
const gitPropertiesCommitSHARegex = /git\.commit\.sha=([a-f\d]{40})/
|
|
7
|
+
const gitPropertiesRepositoryUrlRegex = /git\.repository_url=([\w\d:@/.-]+)/
|
|
8
|
+
const repositoryUrlRegex = /^([\w\d:@/.-]+)$/
|
|
9
|
+
const remoteOriginRegex = /^\[remote\s+"origin"\]/i
|
|
10
|
+
const gitHeadRefRegex = /ref:\s+(refs\/[A-Za-z0-9._/-]+)/
|
|
11
|
+
const commitSHARegex = /^[0-9a-f]{40}$/
|
|
5
12
|
|
|
6
13
|
function removeUserSensitiveInfo (repositoryUrl) {
|
|
7
14
|
try {
|
|
@@ -21,8 +28,8 @@ function getGitMetadataFromGitProperties (gitPropertiesString) {
|
|
|
21
28
|
if (!gitPropertiesString) {
|
|
22
29
|
return {}
|
|
23
30
|
}
|
|
24
|
-
const commitSHAMatch = gitPropertiesString.match(
|
|
25
|
-
const repositoryUrlMatch = gitPropertiesString.match(
|
|
31
|
+
const commitSHAMatch = gitPropertiesString.match(gitPropertiesCommitSHARegex)
|
|
32
|
+
const repositoryUrlMatch = gitPropertiesString.match(gitPropertiesRepositoryUrlRegex)
|
|
26
33
|
|
|
27
34
|
const repositoryUrl = repositoryUrlMatch ? repositoryUrlMatch[1] : undefined
|
|
28
35
|
|
|
@@ -32,4 +39,82 @@ function getGitMetadataFromGitProperties (gitPropertiesString) {
|
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
function getRemoteOriginURL (gitConfigContent) {
|
|
43
|
+
if (!gitConfigContent) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
const lines = gitConfigContent.split('\n')
|
|
47
|
+
let index = 0
|
|
48
|
+
|
|
49
|
+
// find the remote origin section
|
|
50
|
+
for (; index < lines.length; index++) {
|
|
51
|
+
const line = lines[index]
|
|
52
|
+
if (line[0] !== '[') continue // fast path
|
|
53
|
+
if (remoteOriginRegex.test(line)) break
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// find the url key/value in the [remote "origin"] section
|
|
57
|
+
index++
|
|
58
|
+
for (; index < lines.length; index++) {
|
|
59
|
+
const line = lines[index]
|
|
60
|
+
if (line[0] === '[') return // abort, section didn't contain a url
|
|
61
|
+
const splitAt = line.indexOf('=')
|
|
62
|
+
if (splitAt === -1) continue
|
|
63
|
+
const key = line.slice(0, splitAt).trim().toLowerCase()
|
|
64
|
+
if (key !== 'url') continue
|
|
65
|
+
const repositoryUrlValue = line.slice(splitAt + 1).trim()
|
|
66
|
+
const repositoryUrlMatch = repositoryUrlValue.match(repositoryUrlRegex)
|
|
67
|
+
if (!repositoryUrlMatch) continue
|
|
68
|
+
return removeUserSensitiveInfo(repositoryUrlMatch[0])
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getGitHeadRef (gitHeadContent) {
|
|
73
|
+
if (!gitHeadContent) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Extract the ref after 'ref: '
|
|
78
|
+
const gitRefMatch = gitHeadContent.match(gitHeadRefRegex)
|
|
79
|
+
return gitRefMatch?.[1]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveGitHeadSHA (DD_GIT_FOLDER_PATH) {
|
|
83
|
+
const gitHeadPath = path.join(DD_GIT_FOLDER_PATH, 'HEAD')
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const gitHeadContent = fs.readFileSync(gitHeadPath, 'utf8')
|
|
87
|
+
if (!gitHeadContent) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const headContent = gitHeadContent.trim()
|
|
92
|
+
|
|
93
|
+
// Handle detached head case
|
|
94
|
+
if (commitSHARegex.test(headContent)) {
|
|
95
|
+
return headContent
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Handle ref case - extract the ref and read the SHA from the ref file
|
|
99
|
+
const gitHeadRef = getGitHeadRef(headContent)
|
|
100
|
+
if (!gitHeadRef) {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
const gitHeadRefPath = path.join(DD_GIT_FOLDER_PATH, gitHeadRef)
|
|
104
|
+
const gitHeadRefContent = fs.readFileSync(gitHeadRefPath, 'utf8')
|
|
105
|
+
if (gitHeadRefContent) {
|
|
106
|
+
const headRefContent = gitHeadRefContent.trim()
|
|
107
|
+
if (commitSHARegex.test(headRefContent)) {
|
|
108
|
+
return headRefContent
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch {}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
getGitMetadataFromGitProperties,
|
|
116
|
+
removeUserSensitiveInfo,
|
|
117
|
+
getGitHeadRef,
|
|
118
|
+
getRemoteOriginURL,
|
|
119
|
+
resolveGitHeadSHA,
|
|
120
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LLMObsPlugin = require('./base')
|
|
4
|
+
|
|
5
|
+
const ALLOWED_METADATA_KEYS = new Set([
|
|
6
|
+
'max_tokens',
|
|
7
|
+
'stop_sequences',
|
|
8
|
+
'temperature',
|
|
9
|
+
'top_k',
|
|
10
|
+
'top_p',
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
class AnthropicLLMObsPlugin extends LLMObsPlugin {
|
|
14
|
+
static integration = 'anthropic' // used for llmobs telemetry
|
|
15
|
+
static id = 'anthropic'
|
|
16
|
+
static prefix = 'tracing:apm:anthropic:request'
|
|
17
|
+
|
|
18
|
+
constructor () {
|
|
19
|
+
super(...arguments)
|
|
20
|
+
|
|
21
|
+
this.addSub('apm:anthropic:request:chunk', ({ ctx, chunk, done }) => {
|
|
22
|
+
ctx.chunks ??= []
|
|
23
|
+
const chunks = ctx.chunks
|
|
24
|
+
if (chunk) chunks.push(chunk)
|
|
25
|
+
|
|
26
|
+
if (!done) return
|
|
27
|
+
|
|
28
|
+
const response = { content: [] }
|
|
29
|
+
|
|
30
|
+
for (const chunk of chunks) {
|
|
31
|
+
switch (chunk.type) {
|
|
32
|
+
case 'message_start': {
|
|
33
|
+
const { message } = chunk
|
|
34
|
+
if (!message) continue
|
|
35
|
+
|
|
36
|
+
const { role, usage } = message
|
|
37
|
+
if (role) response.role = role
|
|
38
|
+
if (usage) response.usage = usage
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
case 'content_block_start': {
|
|
42
|
+
const contentBlock = chunk.content_block
|
|
43
|
+
if (!contentBlock) continue
|
|
44
|
+
|
|
45
|
+
const { type } = contentBlock
|
|
46
|
+
if (type === 'text') {
|
|
47
|
+
response.content.push({ type, text: contentBlock.text })
|
|
48
|
+
} else if (type === 'tool_use') {
|
|
49
|
+
response.content.push({ type, name: contentBlock.name, input: '', id: contentBlock.id })
|
|
50
|
+
}
|
|
51
|
+
break
|
|
52
|
+
}
|
|
53
|
+
case 'content_block_delta': {
|
|
54
|
+
const { delta } = chunk
|
|
55
|
+
if (!delta) continue
|
|
56
|
+
|
|
57
|
+
const { text } = delta
|
|
58
|
+
if (text) response.content[response.content.length - 1].text += text
|
|
59
|
+
|
|
60
|
+
const partialJson = delta.partial_json
|
|
61
|
+
if (partialJson && delta.type === 'input_json_delta') {
|
|
62
|
+
response.content[response.content.length - 1].input += partialJson
|
|
63
|
+
}
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
case 'content_block_stop': {
|
|
67
|
+
const type = response.content[response.content.length - 1].type
|
|
68
|
+
if (type === 'tool_use') {
|
|
69
|
+
const input = response.content[response.content.length - 1].input ?? '{}'
|
|
70
|
+
response.content[response.content.length - 1].input = JSON.parse(input)
|
|
71
|
+
}
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
case 'message_delta': {
|
|
75
|
+
const { delta } = chunk
|
|
76
|
+
|
|
77
|
+
const finishReason = delta?.stop_reason
|
|
78
|
+
if (finishReason) response.finish_reason = finishReason
|
|
79
|
+
|
|
80
|
+
const { usage } = chunk
|
|
81
|
+
if (usage) {
|
|
82
|
+
const responseUsage = response.usage ?? (response.usage = { input_tokens: 0, output_tokens: 0 })
|
|
83
|
+
responseUsage.output_tokens = usage.output_tokens
|
|
84
|
+
|
|
85
|
+
const cacheCreationTokens = usage.cache_creation_input_tokens
|
|
86
|
+
const cacheReadTokens = usage.cache_read_input_tokens
|
|
87
|
+
if (cacheCreationTokens) responseUsage.cache_creation_input_tokens = cacheCreationTokens
|
|
88
|
+
if (cacheReadTokens) responseUsage.cache_read_input_tokens = cacheReadTokens
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
case 'error': {
|
|
94
|
+
const { error } = chunk
|
|
95
|
+
if (!error) continue
|
|
96
|
+
|
|
97
|
+
response.error = {}
|
|
98
|
+
if (error.type) response.error.type = error.type
|
|
99
|
+
if (error.message) response.error.message = error.message
|
|
100
|
+
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ctx.result = response
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
111
|
+
const { options } = ctx
|
|
112
|
+
const { model } = options
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
kind: 'llm',
|
|
116
|
+
modelName: model,
|
|
117
|
+
modelProvider: 'anthropic'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
setLLMObsTags (ctx) {
|
|
122
|
+
const span = ctx.currentStore?.span
|
|
123
|
+
if (!span) return
|
|
124
|
+
|
|
125
|
+
const { options, result } = ctx
|
|
126
|
+
|
|
127
|
+
this.#tagAnthropicInputMessages(span, options)
|
|
128
|
+
this.#tagAnthropicOutputMessages(span, result)
|
|
129
|
+
this.#tagAnthropicMetadata(span, options)
|
|
130
|
+
this.#tagAnthropicUsage(span, result)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#tagAnthropicInputMessages (span, options) {
|
|
134
|
+
const { system, messages } = options
|
|
135
|
+
const inputMessages = []
|
|
136
|
+
|
|
137
|
+
if (system) {
|
|
138
|
+
messages.unshift({ content: system, role: 'system' })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const message of messages) {
|
|
142
|
+
const { content, role } = message
|
|
143
|
+
|
|
144
|
+
if (typeof content === 'string') {
|
|
145
|
+
inputMessages.push({ content, role })
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const block of content) {
|
|
150
|
+
if (block.type === 'text') {
|
|
151
|
+
inputMessages.push({ content: block.text, role })
|
|
152
|
+
} else if (block.type === 'image') {
|
|
153
|
+
inputMessages.push({ content: '([IMAGE DETECTED])', role })
|
|
154
|
+
} else if (block.type === 'tool_use') {
|
|
155
|
+
const { text, name, id, type } = block
|
|
156
|
+
let input = block.input
|
|
157
|
+
if (typeof input === 'string') {
|
|
158
|
+
input = JSON.parse(input)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const toolCall = {
|
|
162
|
+
name,
|
|
163
|
+
arguments: input,
|
|
164
|
+
toolId: id,
|
|
165
|
+
type
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
inputMessages.push({ content: text ?? '', role, toolCalls: [toolCall] })
|
|
169
|
+
} else if (block.type === 'tool_result') {
|
|
170
|
+
const { content } = block
|
|
171
|
+
const formattedContent = this.#formatAnthropicToolResultContent(content)
|
|
172
|
+
const toolResult = {
|
|
173
|
+
result: formattedContent,
|
|
174
|
+
toolId: block.tool_use_id,
|
|
175
|
+
type: 'tool_result'
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
inputMessages.push({ content: '', role, toolResults: [toolResult] })
|
|
179
|
+
} else {
|
|
180
|
+
inputMessages.push({ content: JSON.stringify(block), role })
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this._tagger.tagLLMIO(span, inputMessages)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#tagAnthropicOutputMessages (span, result) {
|
|
189
|
+
if (!result) return
|
|
190
|
+
|
|
191
|
+
const { content, role } = result
|
|
192
|
+
|
|
193
|
+
if (typeof content === 'string') {
|
|
194
|
+
this._tagger.tagLLMIO(span, null, [{ content, role }])
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const outputMessages = []
|
|
199
|
+
for (const block of content) {
|
|
200
|
+
const { text } = block
|
|
201
|
+
if (typeof text === 'string') {
|
|
202
|
+
outputMessages.push({ content: text, role })
|
|
203
|
+
} else if (block.type === 'tool_use') {
|
|
204
|
+
let input = block.input
|
|
205
|
+
if (typeof input === 'string') {
|
|
206
|
+
input = JSON.parse(input)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const toolCall = {
|
|
210
|
+
name: block.name,
|
|
211
|
+
arguments: input,
|
|
212
|
+
toolId: block.id,
|
|
213
|
+
type: block.type
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
outputMessages.push({ content: text ?? '', role, toolCalls: [toolCall] })
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this._tagger.tagLLMIO(span, null, outputMessages)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#tagAnthropicMetadata (span, options) {
|
|
224
|
+
const metadata = {}
|
|
225
|
+
for (const [key, value] of Object.entries(options)) {
|
|
226
|
+
if (ALLOWED_METADATA_KEYS.has(key)) {
|
|
227
|
+
metadata[key] = value
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this._tagger.tagMetadata(span, metadata)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#tagAnthropicUsage (span, result) {
|
|
235
|
+
if (!result) return
|
|
236
|
+
|
|
237
|
+
const { usage } = result
|
|
238
|
+
if (!usage) return
|
|
239
|
+
|
|
240
|
+
const inputTokens = usage.input_tokens
|
|
241
|
+
const outputTokens = usage.output_tokens
|
|
242
|
+
const cacheWriteTokens = usage.cache_creation_input_tokens
|
|
243
|
+
const cacheReadTokens = usage.cache_read_input_tokens
|
|
244
|
+
|
|
245
|
+
const metrics = {}
|
|
246
|
+
|
|
247
|
+
metrics.inputTokens =
|
|
248
|
+
(inputTokens ?? 0) +
|
|
249
|
+
(cacheWriteTokens ?? 0) +
|
|
250
|
+
(cacheReadTokens ?? 0)
|
|
251
|
+
|
|
252
|
+
if (outputTokens) metrics.outputTokens = outputTokens
|
|
253
|
+
const totalTokens = metrics.inputTokens + (outputTokens ?? 0)
|
|
254
|
+
if (totalTokens) metrics.totalTokens = totalTokens
|
|
255
|
+
|
|
256
|
+
if (cacheWriteTokens != null) metrics.cacheWriteTokens = cacheWriteTokens
|
|
257
|
+
if (cacheReadTokens != null) metrics.cacheReadTokens = cacheReadTokens
|
|
258
|
+
|
|
259
|
+
this._tagger.tagMetrics(span, metrics)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// maybe can make into a util file
|
|
263
|
+
#formatAnthropicToolResultContent (content) {
|
|
264
|
+
if (typeof content === 'string') {
|
|
265
|
+
return content
|
|
266
|
+
} else if (Array.isArray(content)) {
|
|
267
|
+
const formattedContent = []
|
|
268
|
+
for (const toolResultBlock of content) {
|
|
269
|
+
if (toolResultBlock.text) {
|
|
270
|
+
formattedContent.push(toolResultBlock.text)
|
|
271
|
+
} else if (toolResultBlock.type === 'image') {
|
|
272
|
+
formattedContent.push('([IMAGE DETECTED])')
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return formattedContent.join(',')
|
|
277
|
+
}
|
|
278
|
+
return JSON.stringify(content)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = AnthropicLLMObsPlugin
|