dd-trace 5.71.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.
Files changed (63) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/index.d.ts +93 -1
  3. package/package.json +21 -2
  4. package/packages/datadog-instrumentations/src/azure-event-hubs.js +37 -0
  5. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  6. package/packages/datadog-instrumentations/src/cucumber.js +7 -7
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  8. package/packages/datadog-instrumentations/src/jest.js +29 -36
  9. package/packages/datadog-instrumentations/src/mocha/main.js +8 -9
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +1 -1
  11. package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
  12. package/packages/datadog-instrumentations/src/pg.js +1 -1
  13. package/packages/datadog-instrumentations/src/playwright.js +5 -5
  14. package/packages/datadog-instrumentations/src/vitest.js +8 -8
  15. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +73 -27
  16. package/packages/datadog-plugin-azure-event-hubs/src/index.js +15 -0
  17. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +82 -0
  18. package/packages/datadog-plugin-azure-functions/src/index.js +37 -0
  19. package/packages/datadog-plugin-cucumber/src/index.js +3 -3
  20. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -9
  21. package/packages/datadog-plugin-jest/src/util.js +10 -2
  22. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  23. package/packages/datadog-plugin-playwright/src/index.js +2 -2
  24. package/packages/datadog-plugin-vitest/src/index.js +2 -2
  25. package/packages/datadog-plugin-ws/src/server.js +5 -3
  26. package/packages/dd-trace/src/config.js +108 -26
  27. package/packages/dd-trace/src/config_defaults.js +12 -0
  28. package/packages/dd-trace/src/git_properties.js +90 -5
  29. package/packages/dd-trace/src/noop/proxy.js +3 -0
  30. package/packages/dd-trace/src/openfeature/constants/constants.js +51 -0
  31. package/packages/dd-trace/src/openfeature/flagging_provider.js +45 -0
  32. package/packages/dd-trace/src/openfeature/index.js +77 -0
  33. package/packages/dd-trace/src/openfeature/noop.js +101 -0
  34. package/packages/dd-trace/src/openfeature/writers/base.js +181 -0
  35. package/packages/dd-trace/src/openfeature/writers/exposures.js +173 -0
  36. package/packages/dd-trace/src/openfeature/writers/util.js +43 -0
  37. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +100 -0
  38. package/packages/dd-trace/src/opentelemetry/logs/index.js +87 -0
  39. package/packages/dd-trace/src/opentelemetry/logs/logger.js +77 -0
  40. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +126 -0
  41. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +173 -0
  42. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +367 -0
  43. package/packages/dd-trace/src/opentelemetry/protos/common.proto +116 -0
  44. package/packages/dd-trace/src/opentelemetry/protos/logs.proto +226 -0
  45. package/packages/dd-trace/src/opentelemetry/protos/logs_service.proto +78 -0
  46. package/packages/dd-trace/src/opentelemetry/protos/protobuf_loader.js +48 -0
  47. package/packages/dd-trace/src/opentelemetry/protos/resource.proto +45 -0
  48. package/packages/dd-trace/src/plugins/ci_plugin.js +7 -6
  49. package/packages/dd-trace/src/plugins/index.js +1 -0
  50. package/packages/dd-trace/src/plugins/util/test.js +6 -5
  51. package/packages/dd-trace/src/profiling/config.js +21 -1
  52. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -2
  53. package/packages/dd-trace/src/profiling/profiler.js +44 -22
  54. package/packages/dd-trace/src/profiling/profilers/events.js +12 -3
  55. package/packages/dd-trace/src/profiling/profilers/space.js +35 -24
  56. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -6
  57. package/packages/dd-trace/src/proxy.js +22 -1
  58. package/packages/dd-trace/src/remote_config/capabilities.js +1 -0
  59. package/packages/dd-trace/src/remote_config/index.js +1 -0
  60. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +4 -0
  61. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  62. package/packages/dd-trace/src/supported-configurations.json +17 -0
  63. package/packages/dd-trace/src/telemetry/telemetry.js +13 -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 } = require('./git_properties')
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.repositoryUrl = removeUserSensitiveInfo(
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',
@@ -721,6 +731,7 @@ class Config {
721
731
  env['dynamicInstrumentation.uploadIntervalSeconds'] = maybeFloat(DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS)
722
732
  this._envUnprocessed['dynamicInstrumentation.uploadInterval'] = DD_DYNAMIC_INSTRUMENTATION_UPLOAD_INTERVAL_SECONDS
723
733
  this._setString(env, 'env', DD_ENV || tags.env)
734
+ this._setBoolean(env, 'experimental.flaggingProvider.enabled', DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED)
724
735
  this._setBoolean(env, 'traceEnabled', DD_TRACE_ENABLED)
725
736
  this._setBoolean(env, 'experimental.aiguard.enabled', DD_AI_GUARD_ENABLED)
726
737
  this._setString(env, 'experimental.aiguard.endpoint', DD_AI_GUARD_ENDPOINT)
@@ -970,6 +981,7 @@ class Config {
970
981
  this._optsUnprocessed['experimental.aiguard.timeout'] = options.experimental?.aiguard?.timeout
971
982
  this._setBoolean(opts, 'experimental.enableGetRumData', options.experimental?.enableGetRumData)
972
983
  this._setString(opts, 'experimental.exporter', options.experimental?.exporter)
984
+ this._setBoolean(opts, 'experimental.flaggingProvider.enabled', options.experimental?.flaggingProvider?.enabled)
973
985
  opts.flushInterval = maybeInt(options.flushInterval)
974
986
  this._optsUnprocessed.flushInterval = options.flushInterval
975
987
  opts.flushMinSpans = maybeInt(options.flushMinSpans)
@@ -1156,7 +1168,20 @@ class Config {
1156
1168
  calc.testManagementAttemptToFixRetries = maybeInt(DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES) ?? 20
1157
1169
  this._setBoolean(calc, 'isImpactedTestsEnabled', !isFalse(DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED))
1158
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
+
1159
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
+
1160
1185
  this._setBoolean(calc, 'isGitUploadEnabled',
1161
1186
  calc.isIntelligentTestRunnerEnabled && !isFalse(this._isCiVisibilityGitUploadEnabled()))
1162
1187
  this._setBoolean(calc, 'spanComputePeerService', this._getSpanComputePeerService())
@@ -1366,6 +1391,63 @@ class Config {
1366
1391
  }
1367
1392
  }
1368
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
+ }
1369
1451
  }
1370
1452
 
1371
1453
  function handleOtel (tagString) {
@@ -77,6 +77,7 @@ module.exports = {
77
77
  'experimental.aiguard.timeout': 10_000, // ms
78
78
  'experimental.enableGetRumData': false,
79
79
  'experimental.exporter': undefined,
80
+ 'experimental.flaggingProvider.enabled': false,
80
81
  flushInterval: 2000,
81
82
  flushMinSpans: 1000,
82
83
  gitMetadataEnabled: true,
@@ -126,6 +127,17 @@ module.exports = {
126
127
  isTestManagementEnabled: false,
127
128
  isImpactedTestsEnabled: false,
128
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,
129
141
  lookup: undefined,
130
142
  inferredProxyServicesEnabled: false,
131
143
  memcachedCommandEnabled: false,
@@ -1,7 +1,14 @@
1
1
  'use strict'
2
2
 
3
- const commitSHARegex = /git\.commit\.sha=([a-f\d]{40})/
4
- const repositoryUrlRegex = /git\.repository_url=([\w\d:@/.-]+)/
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(commitSHARegex)
25
- const repositoryUrlMatch = gitPropertiesString.match(repositoryUrlRegex)
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
- module.exports = { getGitMetadataFromGitProperties, removeUserSensitiveInfo }
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
+ }
@@ -4,12 +4,14 @@ const NoopTracer = require('./tracer')
4
4
  const NoopAppsecSdk = require('../appsec/sdk/noop')
5
5
  const NoopDogStatsDClient = require('./dogstatsd')
6
6
  const NoopLLMObsSDK = require('../llmobs/noop')
7
+ const NoopFlaggingProvider = require('../openfeature/noop')
7
8
  const NoopAIGuardSDK = require('../aiguard/noop')
8
9
 
9
10
  const noop = new NoopTracer()
10
11
  const noopAppsec = new NoopAppsecSdk()
11
12
  const noopDogStatsDClient = new NoopDogStatsDClient()
12
13
  const noopLLMObs = new NoopLLMObsSDK(noop)
14
+ const noopOpenFeatureProvider = new NoopFlaggingProvider()
13
15
  const noopAIGuard = new NoopAIGuardSDK()
14
16
 
15
17
  /** @type {import('../../src/index')} Proxy */
@@ -19,6 +21,7 @@ class NoopProxy {
19
21
  this.appsec = noopAppsec
20
22
  this.dogstatsd = noopDogStatsDClient
21
23
  this.llmobs = noopLLMObs
24
+ this.openfeature = noopOpenFeatureProvider
22
25
  this.aiguard = noopAIGuard
23
26
  this.setBaggageItem = () => {}
24
27
  this.getBaggageItem = () => {}
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ /**
5
+ * @constant
6
+ * @type {string} Base path for EVP proxy agent endpoint
7
+ */
8
+ EVP_PROXY_AGENT_BASE_PATH: '/evp_proxy/v2/',
9
+
10
+ /**
11
+ * @constant
12
+ * @type {string} HTTP header name for EVP subdomain routing
13
+ */
14
+ EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
15
+
16
+ /**
17
+ * @constant
18
+ * @type {string} EVP subdomain value for event platform intake
19
+ */
20
+ EVP_SUBDOMAIN_VALUE: 'event-platform-intake',
21
+
22
+ /**
23
+ * @constant
24
+ * @type {string} API endpoint for exposure events EVP track
25
+ */
26
+ EXPOSURES_ENDPOINT: '/api/v2/exposures',
27
+
28
+ /**
29
+ * @constant
30
+ * @type {number} Maximum payload size for EVP intake (5MB, actual limit is 5.1MB)
31
+ */
32
+ EVP_PAYLOAD_SIZE_LIMIT: 5 << 20,
33
+
34
+ /**
35
+ * @constant
36
+ * @type {number} Maximum individual event size (999KB, actual limit is 1MB)
37
+ */
38
+ EVP_EVENT_SIZE_LIMIT: (1 << 20) - 1024,
39
+
40
+ /**
41
+ * @constant
42
+ * @type {string} Channel name for exposure event submission
43
+ */
44
+ EXPOSURE_CHANNEL: 'ffe:exposure:submit',
45
+
46
+ /**
47
+ * @constant
48
+ * @type {string} Reason code for noop provider evaluations
49
+ */
50
+ NOOP_REASON: 'STATIC'
51
+ }
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const { DatadogNodeServerProvider } = require('@datadog/openfeature-node-server')
4
+ const { channel } = require('dc-polyfill')
5
+ const log = require('../log')
6
+ const { EXPOSURE_CHANNEL } = require('./constants/constants')
7
+
8
+ /**
9
+ * OpenFeature provider that integrates with Datadog's feature flagging system.
10
+ * Extends DatadogNodeServerProvider to add tracer integration and configuration management.
11
+ */
12
+ class FlaggingProvider extends DatadogNodeServerProvider {
13
+ /**
14
+ * @param {import('../tracer')} tracer - Datadog tracer instance
15
+ * @param {import('../config')} config - Tracer configuration object
16
+ */
17
+ constructor (tracer, config) {
18
+ // Call parent constructor with required options
19
+ super({
20
+ exposureChannel: channel(EXPOSURE_CHANNEL)
21
+ })
22
+
23
+ this._tracer = tracer
24
+ this._config = config
25
+
26
+ log.debug(this.constructor.name + ' created')
27
+ }
28
+
29
+ /**
30
+ * Internal method to update flag configuration from Remote Config.
31
+ * This method is called automatically when Remote Config delivers UFC updates.
32
+ *
33
+ * @internal
34
+ * @param {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1} ufc
35
+ * - Universal Flag Configuration object
36
+ */
37
+ _setConfiguration (ufc) {
38
+ if (typeof this.setConfiguration === 'function') {
39
+ this.setConfiguration(ufc)
40
+ }
41
+ log.debug(this.constructor.name + ' provider configuration updated')
42
+ }
43
+ }
44
+
45
+ module.exports = FlaggingProvider
@@ -0,0 +1,77 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+ const ExposuresWriter = require('./writers/exposures')
5
+ const { setAgentStrategy } = require('./writers/util')
6
+ const { channel } = require('dc-polyfill')
7
+
8
+ const exposureSubmitCh = channel('ffe:exposure:submit')
9
+ const flushCh = channel('ffe:writers:flush')
10
+
11
+ let exposuresWriter = null
12
+
13
+ /**
14
+ * @private
15
+ * @param {Object|Array<Object>} exposureEvents - Exposure events channel subscriber
16
+ * @returns {void}
17
+ */
18
+ function _handleExposureSubmit (exposureEvents) {
19
+ if (!exposuresWriter) return
20
+ exposuresWriter.append(exposureEvents)
21
+ }
22
+
23
+ /**
24
+ * Channel subscriber for manually flushing the exposures writer
25
+ * @private
26
+ * @returns {void}
27
+ */
28
+ function _handleFlush () {
29
+ exposuresWriter?.flush()
30
+ }
31
+
32
+ /**
33
+ * Enables the OpenFeature module and sets up FF&E writer and channel subscribers
34
+ * @param {import('../config')} config - Tracer configuration object
35
+ * @returns {void}
36
+ */
37
+ function enable (config) {
38
+ if (exposuresWriter) {
39
+ log.warn(exposuresWriter.constructor.name + ' already enabled')
40
+ return
41
+ }
42
+
43
+ exposuresWriter = new ExposuresWriter(config)
44
+ exposureSubmitCh.subscribe(_handleExposureSubmit)
45
+ flushCh.subscribe(_handleFlush)
46
+
47
+ setAgentStrategy(config, hasAgent => {
48
+ exposuresWriter?.setEnabled(hasAgent)
49
+ })
50
+
51
+ log.debug('OpenFeature module enabled')
52
+ }
53
+
54
+ /**
55
+ * Disables the OpenFeature module and cleans up resources
56
+ * @returns {void}
57
+ */
58
+ function disable () {
59
+ if (!exposuresWriter) return
60
+
61
+ if (exposureSubmitCh.hasSubscribers) {
62
+ exposureSubmitCh.unsubscribe(_handleExposureSubmit)
63
+ }
64
+ if (flushCh.hasSubscribers) {
65
+ flushCh.unsubscribe(_handleFlush)
66
+ }
67
+
68
+ exposuresWriter.destroy?.()
69
+ exposuresWriter = null
70
+
71
+ log.debug('OpenFeature module disabled')
72
+ }
73
+
74
+ module.exports = {
75
+ enable,
76
+ disable
77
+ }
@@ -0,0 +1,101 @@
1
+ 'use strict'
2
+
3
+ const { NOOP_REASON } = require('./constants/constants')
4
+
5
+ /**
6
+ * No-op implementation of OpenFeature provider that always returns default values.
7
+ * Used when the OpenFeature provider is not initialized or disabled.
8
+ * https://openfeature.dev/docs/reference/concepts/provider/
9
+ */
10
+ class NoopFlaggingProvider {
11
+ /**
12
+ * @param {Object} [noopTracer] - Optional noop tracer instance
13
+ */
14
+ constructor (noopTracer) {
15
+ this._tracer = noopTracer
16
+ this._config = {}
17
+ this.metadata = { name: 'NoopFlaggingProvider' }
18
+ this.status = 'NOT_READY'
19
+ this.runsOn = 'server'
20
+ }
21
+
22
+ /**
23
+ * @param {string} flagKey - Flag key
24
+ * @param {boolean} defaultValue - Default value to return
25
+ * @param {Object} context - Evaluation context
26
+ * @param {Object} logger - Logger instance
27
+ * @returns {Promise<{value: boolean, reason: string}>} Resolution details
28
+ */
29
+ resolveBooleanEvaluation (flagKey, defaultValue, context, logger) {
30
+ return Promise.resolve({
31
+ value: defaultValue,
32
+ reason: NOOP_REASON
33
+ })
34
+ }
35
+
36
+ /**
37
+ * @param {string} flagKey - Flag key
38
+ * @param {string} defaultValue - Default value to return
39
+ * @param {Object} context - Evaluation context
40
+ * @param {Object} logger - Logger instance
41
+ * @returns {Promise<{value: string, reason: string}>} Resolution details
42
+ */
43
+ resolveStringEvaluation (flagKey, defaultValue, context, logger) {
44
+ return Promise.resolve({
45
+ value: defaultValue,
46
+ reason: NOOP_REASON
47
+ })
48
+ }
49
+
50
+ /**
51
+ * @param {string} flagKey - Flag key
52
+ * @param {number} defaultValue - Default value to return
53
+ * @param {Object} context - Evaluation context
54
+ * @param {Object} logger - Logger instance
55
+ * @returns {Promise<{value: number, reason: string}>} Resolution details
56
+ */
57
+ resolveNumberEvaluation (flagKey, defaultValue, context, logger) {
58
+ return Promise.resolve({
59
+ value: defaultValue,
60
+ reason: NOOP_REASON,
61
+ })
62
+ }
63
+
64
+ /**
65
+ * @param {string} flagKey - Flag key
66
+ * @param {Object} defaultValue - Default value to return
67
+ * @param {Object} context - Evaluation context
68
+ * @param {Object} logger - Logger instance
69
+ * @returns {Promise<{value: Object, reason: string}>} Resolution details
70
+ */
71
+ resolveObjectEvaluation (flagKey, defaultValue, context, logger) {
72
+ return Promise.resolve({
73
+ value: defaultValue,
74
+ reason: NOOP_REASON
75
+ })
76
+ }
77
+
78
+ /**
79
+ * @returns {Object} Current configuration
80
+ */
81
+ getConfiguration () {
82
+ return this._config
83
+ }
84
+
85
+ /**
86
+ * @param {Object} config - Configuration to set
87
+ */
88
+ setConfiguration (config) {
89
+ this._config = config
90
+ }
91
+
92
+ /**
93
+ * @internal
94
+ * @param {Object} ufc - Universal Flag Configuration object
95
+ */
96
+ _setConfiguration (ufc) {
97
+ this.setConfiguration(ufc)
98
+ }
99
+ }
100
+
101
+ module.exports = NoopFlaggingProvider