dd-trace 5.72.0 → 5.74.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 (53) hide show
  1. package/LICENSE-3rdparty.csv +3 -0
  2. package/index.d.ts +49 -0
  3. package/package.json +11 -6
  4. package/packages/datadog-core/src/utils/src/set.js +5 -1
  5. package/packages/datadog-esbuild/index.js +112 -36
  6. package/packages/datadog-esbuild/src/utils.js +198 -0
  7. package/packages/datadog-instrumentations/src/azure-service-bus.js +49 -22
  8. package/packages/datadog-instrumentations/src/cookie-parser.js +2 -0
  9. package/packages/datadog-instrumentations/src/express-session.js +1 -0
  10. package/packages/datadog-instrumentations/src/express.js +82 -0
  11. package/packages/datadog-instrumentations/src/helpers/router-helper.js +238 -0
  12. package/packages/datadog-instrumentations/src/jest.js +60 -14
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +3 -4
  14. package/packages/datadog-instrumentations/src/playwright.js +110 -56
  15. package/packages/datadog-instrumentations/src/router.js +63 -6
  16. package/packages/datadog-instrumentations/src/ws.js +3 -3
  17. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -1
  18. package/packages/datadog-plugin-azure-functions/src/index.js +24 -14
  19. package/packages/datadog-plugin-azure-service-bus/src/index.js +1 -1
  20. package/packages/datadog-plugin-azure-service-bus/src/producer.js +60 -12
  21. package/packages/datadog-plugin-express/src/code_origin.js +2 -0
  22. package/packages/datadog-plugin-jest/src/index.js +53 -18
  23. package/packages/datadog-plugin-ws/src/close.js +2 -2
  24. package/packages/datadog-plugin-ws/src/producer.js +1 -1
  25. package/packages/datadog-plugin-ws/src/receiver.js +1 -1
  26. package/packages/dd-trace/src/appsec/index.js +9 -1
  27. package/packages/dd-trace/src/appsec/reporter.js +2 -3
  28. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -0
  29. package/packages/dd-trace/src/config-helper.js +3 -1
  30. package/packages/dd-trace/src/config.js +437 -446
  31. package/packages/dd-trace/src/config_defaults.js +5 -12
  32. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +8 -3
  33. package/packages/dd-trace/src/llmobs/plugins/base.js +11 -12
  34. package/packages/dd-trace/src/llmobs/sdk.js +20 -4
  35. package/packages/dd-trace/src/llmobs/tagger.js +12 -0
  36. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +7 -127
  37. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +19 -134
  38. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +720 -0
  39. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +78 -0
  40. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +177 -0
  41. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +163 -0
  42. package/packages/dd-trace/src/opentelemetry/{protos → otlp}/protobuf_loader.js +24 -6
  43. package/packages/dd-trace/src/plugins/util/ci.js +3 -2
  44. package/packages/dd-trace/src/plugins/util/stacktrace.js +16 -1
  45. package/packages/dd-trace/src/supported-configurations.json +2 -0
  46. package/packages/dd-trace/src/telemetry/endpoints.js +27 -1
  47. package/packages/dd-trace/src/telemetry/index.js +16 -13
  48. package/scripts/preinstall.js +3 -1
  49. package/version.js +2 -1
  50. /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/common.proto +0 -0
  51. /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/logs.proto +0 -0
  52. /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/logs_service.proto +0 -0
  53. /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/resource.proto +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  const pkg = require('./pkg')
4
4
  const { GRPC_CLIENT_ERROR_STATUSES, GRPC_SERVER_ERROR_STATUSES } = require('./constants')
5
- const { getEnvironmentVariables } = require('./config-helper')
5
+ const { getEnvironmentVariable: getEnv } = require('./config-helper')
6
6
 
7
7
  // eslint-disable-next-line @stylistic/max-len
8
8
  const qsRegex = String.raw`(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\s|%20)*(?:=|%3D)[^&]+|(?:"|%22)(?:\s|%20)*(?::|%3A)(?:\s|%20)*(?:"|%22)(?:%2[^2]|%[^2]|[^"%])+(?:"|%22))|bearer(?:\s|%20)+[a-z0-9\._\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\w=-]|%3D)+\.ey[I-L](?:[\w=-]|%3D)+(?:\.(?:[\w.+\/=-]|%3D|%2F|%2B)+)?|[\-]{5}BEGIN(?:[a-z\s]|%20)+PRIVATE(?:\s|%20)KEY[\-]{5}[^\-]+[\-]{5}END(?:[a-z\s]|%20)+PRIVATE(?:\s|%20)KEY|ssh-rsa(?:\s|%20)*(?:[a-z0-9\/\.+]|%2F|%5C|%2B){100,}`
@@ -11,17 +11,10 @@ const defaultWafObfuscatorKeyRegex = String.raw`(?i)pass|pw(?:or)?d|secret|(?:ap
11
11
  // eslint-disable-next-line @stylistic/max-len
12
12
  const defaultWafObfuscatorValueRegex = String.raw`(?i)(?:p(?:ass)?w(?:or)?d|pass(?:[_-]?phrase)?|secret(?:[_-]?key)?|(?:(?:api|private|public|access)[_-]?)key(?:[_-]?id)?|(?:(?:auth|access|id|refresh)[_-]?)?token|consumer[_-]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?|jsessionid|phpsessid|asp\.net(?:[_-]|-)sessionid|sid|jwt)(?:\s*=([^;&]+)|"\s*:\s*("[^"]+"|\d+))|bearer\s+([a-z0-9\._\-]+)|token\s*:\s*([a-z0-9]{13})|gh[opsu]_([0-9a-zA-Z]{36})|ey[I-L][\w=-]+\.(ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?)|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}([^\-]+)[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*([a-z0-9\/\.+]{100,})`
13
13
 
14
- const {
15
- AWS_LAMBDA_FUNCTION_NAME,
16
- FUNCTION_NAME,
17
- K_SERVICE,
18
- WEBSITE_SITE_NAME
19
- } = getEnvironmentVariables()
20
-
21
- const service = AWS_LAMBDA_FUNCTION_NAME ||
22
- FUNCTION_NAME || // Google Cloud Function Name set by deprecated runtimes
23
- K_SERVICE || // Google Cloud Function Name set by newer runtimes
24
- WEBSITE_SITE_NAME || // set by Azure Functions
14
+ const service = getEnv('AWS_LAMBDA_FUNCTION_NAME') ||
15
+ getEnv('FUNCTION_NAME') || // Google Cloud Function Name set by deprecated runtimes
16
+ getEnv('K_SERVICE') || // Google Cloud Function Name set by newer runtimes
17
+ getEnv('WEBSITE_SITE_NAME') || // set by Azure Functions
25
18
  pkg.name ||
26
19
  'node'
27
20
 
@@ -45,19 +45,24 @@ function getOperation (span) {
45
45
 
46
46
  /**
47
47
  * Get the LLM token usage from the span tags
48
+ * Supports both AI SDK v4 (promptTokens/completionTokens) and v5 (inputTokens/outputTokens)
48
49
  * @template T extends {inputTokens: number, outputTokens: number, totalTokens: number}
49
50
  * @param {T} tags
50
51
  * @returns {Pick<T, 'inputTokens' | 'outputTokens' | 'totalTokens'>}
51
52
  */
52
53
  function getUsage (tags) {
53
54
  const usage = {}
54
- const inputTokens = tags['ai.usage.promptTokens']
55
- const outputTokens = tags['ai.usage.completionTokens']
55
+
56
+ // AI SDK v5 uses inputTokens/outputTokens, v4 uses promptTokens/completionTokens
57
+ // Check v5 properties first, fall back to v4
58
+ const inputTokens = tags['ai.usage.inputTokens'] ?? tags['ai.usage.promptTokens']
59
+ const outputTokens = tags['ai.usage.outputTokens'] ?? tags['ai.usage.completionTokens']
56
60
 
57
61
  if (inputTokens != null) usage.inputTokens = inputTokens
58
62
  if (outputTokens != null) usage.outputTokens = outputTokens
59
63
 
60
- const totalTokens = inputTokens + outputTokens
64
+ // v5 provides totalTokens directly, v4 requires computation
65
+ const totalTokens = tags['ai.usage.totalTokens'] ?? (inputTokens + outputTokens)
61
66
  if (!Number.isNaN(totalTokens)) usage.totalTokens = totalTokens
62
67
 
63
68
  return usage
@@ -28,7 +28,7 @@ class LLMObsPlugin extends TracingPlugin {
28
28
  const enabled = this._tracerConfig.llmobs.enabled
29
29
  if (!enabled) return
30
30
 
31
- const parent = this.getLLMObsParent(ctx)
31
+ const parentStore = llmobsStorage.getStore()
32
32
  const apmStore = ctx.currentStore
33
33
  const span = apmStore?.span
34
34
 
@@ -40,10 +40,14 @@ class LLMObsPlugin extends TracingPlugin {
40
40
  telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: this.constructor.integration })
41
41
 
42
42
  ctx.llmobs = {} // initialize context-based namespace
43
- llmobsStorage.enterWith({ span })
44
- ctx.llmobs.parent = parent
45
-
46
- this._tagger.registerLLMObsSpan(span, { parent, integration: this.constructor.integration, ...registerOptions })
43
+ llmobsStorage.enterWith({ ...parentStore, span })
44
+ ctx.llmobs.parent = parentStore
45
+
46
+ this._tagger.registerLLMObsSpan(span, {
47
+ parent: parentStore?.span,
48
+ integration: this.constructor.integration,
49
+ ...registerOptions
50
+ })
47
51
  }
48
52
  }
49
53
 
@@ -56,8 +60,8 @@ class LLMObsPlugin extends TracingPlugin {
56
60
  const span = apmStore?.span
57
61
  if (!LLMObsTagger.tagMap.has(span)) return
58
62
 
59
- const parent = ctx.llmobs.parent
60
- llmobsStorage.enterWith({ span: parent })
63
+ const parentStore = ctx.llmobs.parent
64
+ llmobsStorage.enterWith(parentStore)
61
65
  }
62
66
 
63
67
  asyncEnd (ctx) {
@@ -87,11 +91,6 @@ class LLMObsPlugin extends TracingPlugin {
87
91
  }
88
92
  super.configure(config)
89
93
  }
90
-
91
- getLLMObsParent () {
92
- const store = llmobsStorage.getStore()
93
- return store?.span
94
- }
95
94
  }
96
95
 
97
96
  module.exports = LLMObsPlugin
@@ -422,6 +422,22 @@ class LLMObs extends NoopLLMObs {
422
422
  }
423
423
  }
424
424
 
425
+ annotationContext (options, fn) {
426
+ if (!this.enabled) return fn()
427
+
428
+ const currentStore = storage.getStore()
429
+
430
+ const store = {
431
+ ...currentStore,
432
+ annotationContext: {
433
+ ...currentStore?.annotationContext,
434
+ ...options
435
+ }
436
+ }
437
+
438
+ return storage.run(store, fn)
439
+ }
440
+
425
441
  flush () {
426
442
  if (!this.enabled) return
427
443
 
@@ -447,20 +463,20 @@ class LLMObs extends NoopLLMObs {
447
463
  }
448
464
 
449
465
  _activate (span, options, fn) {
450
- const parent = this._active()
451
- if (this.enabled) storage.enterWith({ span })
466
+ const parentStore = storage.getStore()
467
+ if (this.enabled) storage.enterWith({ ...parentStore, span })
452
468
 
453
469
  if (options) {
454
470
  this._tagger.registerLLMObsSpan(span, {
455
471
  ...options,
456
- parent
472
+ parent: parentStore?.span
457
473
  })
458
474
  }
459
475
 
460
476
  try {
461
477
  return fn()
462
478
  } finally {
463
- if (this.enabled) storage.enterWith({ span: parent })
479
+ if (this.enabled) storage.enterWith(parentStore)
464
480
  }
465
481
  }
466
482
 
@@ -29,6 +29,7 @@ const {
29
29
  DECORATOR,
30
30
  PROPAGATED_ML_APP_KEY
31
31
  } = require('./constants/tags')
32
+ const { storage } = require('./storage')
32
33
 
33
34
  // global registry of LLMObs spans
34
35
  // maps LLMObs spans to their annotations
@@ -97,6 +98,17 @@ class LLMObsTagger {
97
98
  span.context()._trace.tags[PROPAGATED_PARENT_ID_KEY] ??
98
99
  ROOT_PARENT_ID
99
100
  this._setTag(span, PARENT_ID_KEY, parentId)
101
+
102
+ // apply annotation context
103
+ const annotationContext = storage.getStore()?.annotationContext
104
+
105
+ // apply annotation context tags
106
+ const tags = annotationContext?.tags
107
+ if (tags) this.tagSpanTags(span, tags)
108
+
109
+ // apply annotation context name
110
+ const annotationContextName = annotationContext?.name
111
+ if (annotationContextName) this._setTag(span, NAME, annotationContextName)
100
112
  }
101
113
 
102
114
  // TODO: similarly for the following `tag` methods,
@@ -1,18 +1,12 @@
1
1
  'use strict'
2
2
 
3
- const http = require('http')
4
- const { URL } = require('url')
5
- const log = require('../../log')
3
+ const OtlpHttpExporterBase = require('../otlp/otlp_http_exporter_base')
6
4
  const OtlpTransformer = require('./otlp_transformer')
7
- const telemetryMetrics = require('../../telemetry/metrics')
8
5
 
9
6
  /**
10
7
  * @typedef {import('@opentelemetry/resources').Resource} Resource
11
8
  * @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
12
- *
13
- */
14
-
15
- const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
9
+ */
16
10
 
17
11
  /**
18
12
  * OtlpHttpLogExporter exports log records via OTLP over HTTP.
@@ -21,12 +15,9 @@ const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
21
15
  * https://opentelemetry.io/docs/specs/otlp/#otlphttp
22
16
  *
23
17
  * @class OtlpHttpLogExporter
18
+ * @extends OtlpHttpExporterBase
24
19
  */
25
- class OtlpHttpLogExporter {
26
- #telemetryTags
27
-
28
- DEFAULT_LOGS_PATH = '/v1/logs'
29
-
20
+ class OtlpHttpLogExporter extends OtlpHttpExporterBase {
30
21
  /**
31
22
  * Creates a new OtlpHttpLogExporter instance.
32
23
  *
@@ -37,28 +28,8 @@ class OtlpHttpLogExporter {
37
28
  * @param {Resource} resource - Resource attributes
38
29
  */
39
30
  constructor (url, headers, timeout, protocol, resource) {
40
- const parsedUrl = new URL(url)
41
-
42
- this.protocol = protocol
31
+ super(url, headers, timeout, protocol, '/v1/logs', 'logs')
43
32
  this.transformer = new OtlpTransformer(resource, protocol)
44
- // If no path is provided, use default path
45
- const path = parsedUrl.pathname === '/' ? this.DEFAULT_LOGS_PATH : parsedUrl.pathname
46
- const isJson = protocol === 'http/json'
47
- this.options = {
48
- hostname: parsedUrl.hostname,
49
- port: parsedUrl.port,
50
- path: path + parsedUrl.search,
51
- method: 'POST',
52
- timeout,
53
- headers: {
54
- 'Content-Type': isJson ? 'application/json' : 'application/x-protobuf',
55
- ...this.#parseAdditionalHeaders(headers)
56
- }
57
- }
58
- this.#telemetryTags = [
59
- 'protocol:http',
60
- `encoding:${isJson ? 'json' : 'protobuf'}`
61
- ]
62
33
  }
63
34
 
64
35
  /**
@@ -74,99 +45,8 @@ class OtlpHttpLogExporter {
74
45
  }
75
46
 
76
47
  const payload = this.transformer.transformLogRecords(logRecords)
77
- this.#sendPayload(payload, resultCallback)
78
- tracerMetrics.count('otel.log_records', this.#telemetryTags).inc(logRecords.length)
79
- }
80
-
81
- /**
82
- * Sends the payload via HTTP request.
83
- * @param {Buffer|string} payload - The payload to send
84
- * @param {Function} resultCallback - Callback for the result
85
- * @private
86
- */
87
- #sendPayload (payload, resultCallback) {
88
- const options = {
89
- ...this.options,
90
- headers: {
91
- ...this.options.headers,
92
- 'Content-Length': payload.length
93
- }
94
- }
95
-
96
- const req = http.request(options, (res) => {
97
- let data = ''
98
-
99
- res.on('data', (chunk) => {
100
- data += chunk
101
- })
102
-
103
- res.on('end', () => {
104
- if (res.statusCode >= 200 && res.statusCode < 300) {
105
- resultCallback({ code: 0 })
106
- } else {
107
- const error = new Error(`HTTP ${res.statusCode}: ${data}`)
108
- resultCallback({ code: 1, error })
109
- }
110
- })
111
- })
112
-
113
- req.on('error', (error) => {
114
- log.error('Error sending OTLP logs:', error)
115
- resultCallback({ code: 1, error })
116
- })
117
-
118
- req.on('timeout', () => {
119
- req.destroy()
120
- const error = new Error('Request timeout')
121
- resultCallback({ code: 1, error })
122
- })
123
-
124
- req.write(payload)
125
- req.end()
126
- }
127
-
128
- /**
129
- * Parses additional HTTP headers from a comma-separated string.
130
- * @param {string} headersString - Comma-separated key=value pairs
131
- * @returns {Record<string, string>} Parsed headers object
132
- * @private
133
- */
134
- #parseAdditionalHeaders (headersString) {
135
- const headers = {}
136
- let key = ''
137
- let value = ''
138
- let readingKey = true
139
-
140
- for (const char of headersString) {
141
- if (readingKey) {
142
- if (char === '=') {
143
- readingKey = false
144
- key = key.trim()
145
- } else {
146
- key += char
147
- }
148
- } else if (char === ',') {
149
- value = value.trim()
150
- if (key && value) {
151
- headers[key] = value
152
- }
153
- key = ''
154
- value = ''
155
- readingKey = true
156
- } else {
157
- value += char
158
- }
159
- }
160
-
161
- // Add the last pair if present
162
- if (!readingKey) {
163
- value = value.trim()
164
- if (value) {
165
- headers[key] = value
166
- }
167
- }
168
-
169
- return headers
48
+ this._sendPayload(payload, resultCallback)
49
+ this._recordTelemetry('otel.log_records', logRecords.length)
170
50
  }
171
51
  }
172
52
 
@@ -1,14 +1,12 @@
1
1
  'use strict'
2
2
 
3
+ const OtlpTransformerBase = require('../otlp/otlp_transformer_base')
3
4
  const { SeverityNumber } = require('@opentelemetry/api-logs')
4
- const { getProtobufTypes } = require('../protos/protobuf_loader')
5
+ const { getProtobufTypes } = require('../otlp/protobuf_loader')
5
6
  const { trace } = require('@opentelemetry/api')
6
- const log = require('../../log')
7
7
 
8
8
  /**
9
- * @typedef {import('@opentelemetry/api').Attributes} Attributes
10
9
  * @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
11
- * @typedef {import('@opentelemetry/resources').Resource} Resource
12
10
  */
13
11
 
14
12
  // Global severity mapping constant - no need to regenerate
@@ -46,24 +44,17 @@ const SEVERITY_MAP = {
46
44
  * https://opentelemetry.io/docs/specs/otlp/#log-data-model
47
45
  *
48
46
  * @class OtlpTransformer
47
+ * @extends OtlpTransformerBase
49
48
  */
50
- class OtlpTransformer {
51
- #resourceAttributes
52
-
49
+ class OtlpTransformer extends OtlpTransformerBase {
53
50
  /**
54
51
  * Creates a new OtlpTransformer instance.
55
52
  *
56
- * @param {Attributes} resourceAttributes - Resource attributes
53
+ * @param {import('@opentelemetry/api').Attributes} resourceAttributes - Resource attributes
57
54
  * @param {string} protocol - OTLP protocol (http/protobuf or http/json)
58
55
  */
59
56
  constructor (resourceAttributes, protocol) {
60
- this.#resourceAttributes = this.#transformAttributes(resourceAttributes)
61
- if (protocol === 'grpc') {
62
- log.warn('OTLP gRPC protocol is not supported for logs. ' +
63
- 'Defaulting to http/protobuf. gRPC protobuf support may be added in a future release.')
64
- protocol = 'http/protobuf'
65
- }
66
- this.protocol = protocol
57
+ super(resourceAttributes, protocol, 'logs')
67
58
  }
68
59
 
69
60
  /**
@@ -72,37 +63,12 @@ class OtlpTransformer {
72
63
  * @returns {Buffer} Transformed log records in the appropriate format
73
64
  */
74
65
  transformLogRecords (logRecords) {
75
- // Use the configured protocol to determine serialization format
76
66
  if (this.protocol === 'http/json') {
77
67
  return this.#transformToJson(logRecords)
78
68
  }
79
- // Default to protobuf for http/protobuf or any other protocol
80
69
  return this.#transformToProtobuf(logRecords)
81
70
  }
82
71
 
83
- /**
84
- * Groups log records by instrumentation library (name and version).
85
- * @param {LogRecord[]} logRecords - Array of log records to group
86
- * @returns {Map<string, LogRecord[]>} Map of instrumentation library key to log records
87
- * @private
88
- */
89
- #groupByInstrumentationScope (logRecords) {
90
- const grouped = new Map()
91
-
92
- for (const record of logRecords) {
93
- const instrumentationScope = record.instrumentationScope || { name: '', version: '0.0.0', schemaUrl: '' }
94
- const key = `${instrumentationScope.name}@${instrumentationScope.version}@${instrumentationScope.schemaUrl}`
95
-
96
- const group = grouped.get(key)
97
- if (group === undefined) {
98
- grouped.set(key, [record])
99
- } else {
100
- group.push(record)
101
- }
102
- }
103
- return grouped
104
- }
105
-
106
72
  /**
107
73
  * Transforms log records to protobuf format.
108
74
  * @param {LogRecord[]} logRecords - Array of enriched log records to transform
@@ -111,19 +77,15 @@ class OtlpTransformer {
111
77
  */
112
78
  #transformToProtobuf (logRecords) {
113
79
  const { protoLogsService } = getProtobufTypes()
114
- // Create the OTLP LogsData structure
80
+
115
81
  const logsData = {
116
82
  resourceLogs: [{
117
- resource: this.#transformResource(),
83
+ resource: this._transformResource(),
118
84
  scopeLogs: this.#transformScope(logRecords),
119
85
  }]
120
86
  }
121
87
 
122
- // Serialize to protobuf
123
- const message = protoLogsService.create(logsData)
124
- const buffer = protoLogsService.encode(message).finish()
125
-
126
- return buffer
88
+ return this._serializeToProtobuf(protoLogsService, logsData)
127
89
  }
128
90
 
129
91
  /**
@@ -135,11 +97,11 @@ class OtlpTransformer {
135
97
  #transformToJson (logRecords) {
136
98
  const logsData = {
137
99
  resourceLogs: [{
138
- resource: this.#transformResource(),
100
+ resource: this._transformResource(),
139
101
  scopeLogs: this.#transformScope(logRecords)
140
102
  }]
141
103
  }
142
- return Buffer.from(JSON.stringify(logsData))
104
+ return this._serializeToJson(logsData)
143
105
  }
144
106
 
145
107
  /**
@@ -149,10 +111,7 @@ class OtlpTransformer {
149
111
  * @private
150
112
  */
151
113
  #transformScope (logRecords) {
152
- // Group log records by instrumentation library
153
- const groupedRecords = this.#groupByInstrumentationScope(logRecords)
154
-
155
- // Create scope logs for each instrumentation library
114
+ const groupedRecords = this._groupByInstrumentationScope(logRecords)
156
115
  const scopeLogs = []
157
116
 
158
117
  for (const records of groupedRecords.values()) {
@@ -161,7 +120,6 @@ class OtlpTransformer {
161
120
  scope: {
162
121
  name: records[0]?.instrumentationScope?.name || 'dd-trace-js',
163
122
  version: records[0]?.instrumentationScope?.version || '',
164
- // TODO: Support setting attributes on instrumentation scope
165
123
  attributes: [],
166
124
  droppedAttributesCount: 0
167
125
  },
@@ -173,18 +131,6 @@ class OtlpTransformer {
173
131
  return scopeLogs
174
132
  }
175
133
 
176
- /**
177
- * Transforms resource attributes to OTLP resource format.
178
- * @returns {Resource} OTLP resource object
179
- * @private
180
- */
181
- #transformResource () {
182
- return {
183
- attributes: this.#resourceAttributes,
184
- droppedAttributesCount: 0
185
- }
186
- }
187
-
188
134
  /**
189
135
  * Transforms a single log record to OTLP format.
190
136
  * @param {LogRecord} logRecord - Log record to transform
@@ -192,14 +138,10 @@ class OtlpTransformer {
192
138
  * @private
193
139
  */
194
140
  #transformLogRecord (logRecord) {
195
- const timestamp = logRecord.timestamp
196
-
197
- // Extract span context from the log record's context
198
141
  const spanContext = this.#extractSpanContext(logRecord.context)
199
142
 
200
- // Only timeUnixNano and body are required
201
143
  const result = {
202
- timeUnixNano: timestamp,
144
+ timeUnixNano: logRecord.timestamp,
203
145
  body: this.#transformBody(logRecord.body)
204
146
  }
205
147
 
@@ -217,14 +159,14 @@ class OtlpTransformer {
217
159
  }
218
160
 
219
161
  if (logRecord.attributes) {
220
- result.attributes = this.#transformAttributes(logRecord.attributes)
162
+ result.attributes = this._transformAttributes(logRecord.attributes)
221
163
  }
222
164
 
223
165
  if (spanContext?.traceFlags !== undefined) {
224
166
  result.flags = spanContext.traceFlags
225
167
  }
226
168
 
227
- // Only include traceId and spanId if they are valid (not empty, undefined, or all zeros)
169
+ // Only include traceId and spanId if they are valid
228
170
  if (spanContext?.traceId && spanContext.traceId !== '00000000000000000000000000000000') {
229
171
  result.traceId = this.#hexToBytes(spanContext.traceId)
230
172
  }
@@ -285,82 +227,25 @@ class OtlpTransformer {
285
227
  */
286
228
  #transformBody (body) {
287
229
  if (typeof body === 'string') {
288
- return {
289
- stringValue: body
290
- }
230
+ return { stringValue: body }
291
231
  } else if (typeof body === 'number') {
292
232
  if (Number.isInteger(body)) {
293
233
  return { intValue: body }
294
234
  }
295
235
  return { doubleValue: body }
296
236
  } else if (typeof body === 'boolean') {
297
- return {
298
- boolValue: body
299
- }
237
+ return { boolValue: body }
300
238
  } else if (body && typeof body === 'object') {
301
239
  return {
302
240
  kvlistValue: {
303
241
  values: Object.entries(body).map(([key, value]) => ({
304
242
  key,
305
- value: this.#transformAnyValue(value)
306
- }))
307
- }
308
- }
309
- }
310
- return {
311
- stringValue: String(body)
312
- }
313
- }
314
-
315
- /**
316
- * Transforms attributes to OTLP KeyValue format.
317
- * @param {Object} attributes - Attributes to transform
318
- * @returns {Object[]} Array of OTLP KeyValue objects
319
- * @private
320
- */
321
- #transformAttributes (attributes) {
322
- if (!attributes) {
323
- return {}
324
- }
325
- return Object.entries(attributes).map(([key, value]) => ({
326
- key,
327
- value: this.#transformAnyValue(value)
328
- }))
329
- }
330
-
331
- /**
332
- * Transforms any value to OTLP AnyValue format.
333
- * @param {any} value - Value to transform
334
- * @returns {Object} OTLP AnyValue object
335
- * @private
336
- */
337
- #transformAnyValue (value) {
338
- if (typeof value === 'string') {
339
- return { stringValue: value }
340
- } else if (typeof value === 'number') {
341
- if (Number.isInteger(value)) {
342
- return { intValue: value }
343
- }
344
- return { doubleValue: value }
345
- } else if (typeof value === 'boolean') {
346
- return { boolValue: value }
347
- } else if (Array.isArray(value)) {
348
- return {
349
- arrayValue: {
350
- values: value.map(v => this.#transformAnyValue(v))
351
- }
352
- }
353
- } else if (value && typeof value === 'object') {
354
- return {
355
- kvlistValue: {
356
- values: Object.entries(value).map(([k, v]) => ({
357
- key: k,
358
- value: this.#transformAnyValue(v)
243
+ value: this._transformAnyValue(value)
359
244
  }))
360
245
  }
361
246
  }
362
247
  }
363
- return { stringValue: String(value) }
248
+ return { stringValue: String(body) }
364
249
  }
365
250
  }
366
251