dd-trace 5.71.0 → 5.73.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 (84) hide show
  1. package/LICENSE-3rdparty.csv +7 -0
  2. package/index.d.ts +114 -1
  3. package/package.json +25 -4
  4. package/packages/datadog-esbuild/index.js +8 -0
  5. package/packages/datadog-instrumentations/src/azure-event-hubs.js +37 -0
  6. package/packages/datadog-instrumentations/src/azure-functions.js +3 -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/cucumber.js +7 -7
  10. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  11. package/packages/datadog-instrumentations/src/jest.js +85 -47
  12. package/packages/datadog-instrumentations/src/mocha/main.js +8 -9
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +4 -5
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
  15. package/packages/datadog-instrumentations/src/pg.js +1 -1
  16. package/packages/datadog-instrumentations/src/playwright.js +5 -5
  17. package/packages/datadog-instrumentations/src/vitest.js +8 -8
  18. package/packages/datadog-plugin-amqplib/src/consumer.js +1 -1
  19. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +73 -27
  20. package/packages/datadog-plugin-azure-event-hubs/src/index.js +15 -0
  21. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +82 -0
  22. package/packages/datadog-plugin-azure-functions/src/index.js +50 -3
  23. package/packages/datadog-plugin-azure-service-bus/src/index.js +1 -1
  24. package/packages/datadog-plugin-azure-service-bus/src/producer.js +60 -12
  25. package/packages/datadog-plugin-cucumber/src/index.js +3 -3
  26. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +9 -9
  27. package/packages/datadog-plugin-jest/src/index.js +53 -18
  28. package/packages/datadog-plugin-jest/src/util.js +10 -2
  29. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  30. package/packages/datadog-plugin-playwright/src/index.js +2 -2
  31. package/packages/datadog-plugin-vitest/src/index.js +2 -2
  32. package/packages/datadog-plugin-ws/src/close.js +1 -1
  33. package/packages/datadog-plugin-ws/src/producer.js +1 -1
  34. package/packages/datadog-plugin-ws/src/receiver.js +1 -1
  35. package/packages/datadog-plugin-ws/src/server.js +5 -3
  36. package/packages/dd-trace/src/appsec/index.js +9 -1
  37. package/packages/dd-trace/src/appsec/reporter.js +2 -3
  38. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -0
  39. package/packages/dd-trace/src/config.js +108 -26
  40. package/packages/dd-trace/src/config_defaults.js +12 -0
  41. package/packages/dd-trace/src/git_properties.js +90 -5
  42. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +8 -3
  43. package/packages/dd-trace/src/llmobs/plugins/base.js +11 -12
  44. package/packages/dd-trace/src/llmobs/sdk.js +20 -4
  45. package/packages/dd-trace/src/llmobs/tagger.js +12 -0
  46. package/packages/dd-trace/src/noop/proxy.js +3 -0
  47. package/packages/dd-trace/src/openfeature/constants/constants.js +51 -0
  48. package/packages/dd-trace/src/openfeature/flagging_provider.js +45 -0
  49. package/packages/dd-trace/src/openfeature/index.js +77 -0
  50. package/packages/dd-trace/src/openfeature/noop.js +101 -0
  51. package/packages/dd-trace/src/openfeature/writers/base.js +181 -0
  52. package/packages/dd-trace/src/openfeature/writers/exposures.js +173 -0
  53. package/packages/dd-trace/src/openfeature/writers/util.js +43 -0
  54. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +100 -0
  55. package/packages/dd-trace/src/opentelemetry/logs/index.js +87 -0
  56. package/packages/dd-trace/src/opentelemetry/logs/logger.js +77 -0
  57. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +126 -0
  58. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +53 -0
  59. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +252 -0
  60. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +116 -0
  61. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +226 -0
  62. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +78 -0
  63. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +720 -0
  64. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +78 -0
  65. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +177 -0
  66. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +163 -0
  67. package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +66 -0
  68. package/packages/dd-trace/src/opentelemetry/otlp/resource.proto +45 -0
  69. package/packages/dd-trace/src/plugins/ci_plugin.js +7 -6
  70. package/packages/dd-trace/src/plugins/index.js +1 -0
  71. package/packages/dd-trace/src/plugins/util/test.js +6 -5
  72. package/packages/dd-trace/src/profiling/config.js +21 -1
  73. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -2
  74. package/packages/dd-trace/src/profiling/profiler.js +44 -22
  75. package/packages/dd-trace/src/profiling/profilers/events.js +12 -3
  76. package/packages/dd-trace/src/profiling/profilers/space.js +35 -24
  77. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -6
  78. package/packages/dd-trace/src/proxy.js +22 -1
  79. package/packages/dd-trace/src/remote_config/capabilities.js +1 -0
  80. package/packages/dd-trace/src/remote_config/index.js +1 -0
  81. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +4 -0
  82. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  83. package/packages/dd-trace/src/supported-configurations.json +18 -0
  84. package/packages/dd-trace/src/telemetry/telemetry.js +13 -1
@@ -0,0 +1,87 @@
1
+ 'use strict'
2
+
3
+ const os = require('os')
4
+
5
+ /**
6
+ * @typedef {import('../../config')} Config
7
+ */
8
+
9
+ /**
10
+ * @fileoverview OpenTelemetry Logs Implementation for dd-trace-js
11
+ *
12
+ * This package provides a custom OpenTelemetry Logs implementation that integrates
13
+ * with the Datadog tracing library. It includes all necessary components for
14
+ * emitting, processing, and exporting log records via OTLP (OpenTelemetry Protocol).
15
+ *
16
+ * Key Components:
17
+ * - LoggerProvider: Main entry point for creating loggers
18
+ * - Logger: Provides methods to emit log records
19
+ * - BatchLogRecordProcessor: Processes log records in batches for efficient export
20
+ * - OtlpHttpLogExporter: Exports log records via OTLP over HTTP
21
+ * - OtlpTransformer: Transforms log records to OTLP format
22
+ *
23
+ * This is a custom implementation to avoid pulling in the full OpenTelemetry SDK,
24
+ * based on OTLP Protocol v1.7.0. It supports both protobuf and JSON serialization
25
+ * formats and integrates with Datadog's configuration system.
26
+ *
27
+ * @package
28
+ */
29
+
30
+ const LoggerProvider = require('./logger_provider')
31
+ const BatchLogRecordProcessor = require('./batch_log_processor')
32
+ const OtlpHttpLogExporter = require('./otlp_http_log_exporter')
33
+
34
+ /**
35
+ * Initializes OpenTelemetry Logs support
36
+ * @param {Config} config - Tracer configuration instance
37
+ */
38
+ function initializeOpenTelemetryLogs (config) {
39
+ // Build resource attributes
40
+ const resourceAttributes = {
41
+ 'service.name': config.service,
42
+ 'service.version': config.version,
43
+ 'deployment.environment': config.env
44
+ }
45
+
46
+ // Add all tracer tags (includes DD_TAGS, OTEL_RESOURCE_ATTRIBUTES, DD_TRACE_TAGS, etc.)
47
+ // Exclude Datadog-style keys that duplicate OpenTelemetry standard keys
48
+ if (config.tags) {
49
+ const filteredTags = { ...config.tags }
50
+ delete filteredTags.service
51
+ delete filteredTags.version
52
+ delete filteredTags.env
53
+ Object.assign(resourceAttributes, filteredTags)
54
+ }
55
+
56
+ // Add host.name if reportHostname is enabled
57
+ if (config.reportHostname) {
58
+ resourceAttributes['host.name'] = os.hostname()
59
+ }
60
+
61
+ // Create OTLP exporter using resolved config values
62
+ const exporter = new OtlpHttpLogExporter(
63
+ config.otelLogsUrl,
64
+ config.otelLogsHeaders,
65
+ config.otelLogsTimeout,
66
+ config.otelLogsProtocol,
67
+ resourceAttributes
68
+ )
69
+
70
+ // Create batch processor for exporting logs to Datadog Agent
71
+ const processor = new BatchLogRecordProcessor(
72
+ exporter,
73
+ config.otelLogsBatchTimeout,
74
+ config.otelLogsMaxExportBatchSize
75
+ )
76
+
77
+ // Create logger provider with processor for Datadog Agent export
78
+ const loggerProvider = new LoggerProvider({ processor })
79
+
80
+ // Register the logger provider globally with OpenTelemetry API
81
+ loggerProvider.register()
82
+ }
83
+
84
+ module.exports = {
85
+ LoggerProvider,
86
+ initializeOpenTelemetryLogs
87
+ }
@@ -0,0 +1,77 @@
1
+ 'use strict'
2
+
3
+ const { sanitizeAttributes } = require('@opentelemetry/core')
4
+ const { context } = require('@opentelemetry/api')
5
+ const packageVersion = require('../../../../../package.json').version
6
+ /**
7
+ * @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
8
+ * @typedef {import('@opentelemetry/api').SpanContext} SpanContext
9
+ * @typedef {import('@opentelemetry/api').Attributes} Attributes
10
+ * @typedef {import('@opentelemetry/resources').Resource} Resource
11
+ * @typedef {import('@opentelemetry/core').InstrumentationScope} InstrumentationScope
12
+ */
13
+
14
+ /**
15
+ * Logger provides methods to emit log records.
16
+ *
17
+ * This implementation follows the OpenTelemetry JavaScript API Logger:
18
+ * https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api-logs.Logger.html
19
+ *
20
+ * @class Logger
21
+ */
22
+ class Logger {
23
+ #instrumentationScope
24
+
25
+ /**
26
+ * Creates a new Logger instance.
27
+ *
28
+ * @param {LoggerProvider} loggerProvider - Parent logger provider
29
+ * @param {InstrumentationScope} [instrumentationScope] - Instrumentation scope information (newer API)
30
+ * @param {Object} [instrumentationLibrary] - Instrumentation library information (legacy API) [DEPRECATED in v1.3.0]
31
+ * @param {InstrumentationScope} [instrumentationScope.name] - Library name (defaults to 'dd-trace-js')
32
+ * @param {InstrumentationScope} [instrumentationScope.version] - Library version (defaults to tracer version)
33
+ * @param {string} [instrumentationLibrary.name] - Library name (legacy, defaults to 'dd-trace-js')
34
+ * @param {string} [instrumentationLibrary.version] - Library version (legacy, defaults to tracer version)
35
+ */
36
+ constructor (loggerProvider, instrumentationScope, instrumentationLibrary) {
37
+ this.loggerProvider = loggerProvider
38
+
39
+ // Support both newer instrumentationScope and legacy instrumentationLibrary
40
+ const scope = instrumentationScope || instrumentationLibrary
41
+ this.#instrumentationScope = {
42
+ name: scope?.name || 'dd-trace-js',
43
+ version: scope?.version || packageVersion,
44
+ schemaUrl: scope?.schemaUrl || '',
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Emits a log record.
50
+ *
51
+ * @param {LogRecord} logRecord - The log record to emit
52
+ * @returns {void}
53
+ */
54
+ emit (logRecord) {
55
+ if (this.loggerProvider.isShutdown || !this.loggerProvider.processor) {
56
+ return
57
+ }
58
+
59
+ if (logRecord.attributes) {
60
+ logRecord.attributes = sanitizeAttributes(logRecord.attributes)
61
+ }
62
+
63
+ // Note: timestamp is in nanoseconds (as defined by OpenTelemetry LogRecord API)
64
+ if (!logRecord.timestamp) {
65
+ logRecord.timestamp = Number(process.hrtime.bigint())
66
+ }
67
+
68
+ if (!logRecord.context) {
69
+ // Store span context in the log record context for trace correlation
70
+ logRecord.context = context.active()
71
+ }
72
+
73
+ this.loggerProvider.processor.onEmit(logRecord, this.#instrumentationScope)
74
+ }
75
+ }
76
+
77
+ module.exports = Logger
@@ -0,0 +1,126 @@
1
+ 'use strict'
2
+ const { logs } = require('@opentelemetry/api-logs')
3
+ const { context } = require('@opentelemetry/api')
4
+ const Logger = require('./logger')
5
+ const log = require('../../log')
6
+ const ContextManager = require('../context_manager')
7
+
8
+ /**
9
+ * @typedef {import('@opentelemetry/api-logs').Logger} Logger
10
+ * @typedef {import('./batch_log_processor')} BatchLogRecordProcessor
11
+ */
12
+
13
+ /**
14
+ * LoggerProvider is the main entry point for creating loggers with a single processor for Datadog Agent export.
15
+ *
16
+ * This implementation follows the OpenTelemetry JavaScript API LoggerProvider interface:
17
+ * https://github.com/open-telemetry/opentelemetry-js/blob/a7a36499f70f25201949aeabb84c5fd4ca80e860/experimental/packages/api-logs/src/types/LoggerProvider.ts
18
+ *
19
+ * @class LoggerProvider
20
+ * @implements {import('@opentelemetry/api-logs').LoggerProvider}
21
+ */
22
+ class LoggerProvider {
23
+ #loggers
24
+ #contextManager
25
+
26
+ /**
27
+ * Creates a new LoggerProvider instance with a single processor for Datadog Agent export.
28
+ *
29
+ * @param {Object} [options] - LoggerProvider options
30
+ * @param {BatchLogRecordProcessor} [options.processor] - Single LogRecordProcessor instance for
31
+ * exporting logs to Datadog Agent
32
+ */
33
+ constructor (options = {}) {
34
+ this.processor = options.processor
35
+ this.#loggers = new Map()
36
+ this.#contextManager = new ContextManager()
37
+ this.isShutdown = false
38
+ }
39
+
40
+ /**
41
+ * Gets or creates a logger instance.
42
+ *
43
+ * @param {string|Object} nameOrOptions - Logger name or options object
44
+ * @param {string} [version] - Logger version (when nameOrOptions is a string)
45
+ * @param {Object} [options] - Additional options (when nameOrOptions is a string)
46
+ * @returns {Logger} Logger instance
47
+ */
48
+ getLogger (nameOrOptions, version, options = {}) {
49
+ if (this.isShutdown) {
50
+ return this.#createNoOpLogger()
51
+ }
52
+
53
+ let name, loggerOptions
54
+ if (typeof nameOrOptions === 'string') {
55
+ name = nameOrOptions
56
+ loggerOptions = { version, ...options }
57
+ } else {
58
+ name = nameOrOptions.name
59
+ loggerOptions = nameOrOptions
60
+ }
61
+
62
+ const loggerVersion = loggerOptions.version || ''
63
+ const loggerSchemaUrl = loggerOptions?.schemaUrl || ''
64
+ const key = `${name}@${loggerVersion}`
65
+
66
+ if (!this.#loggers.has(key)) {
67
+ this.#loggers.set(key, new Logger(this, { name, version: loggerVersion, schemaUrl: loggerSchemaUrl }))
68
+ }
69
+ return this.#loggers.get(key)
70
+ }
71
+
72
+ /**
73
+ * Registers this logger provider as the global provider.
74
+ */
75
+ register () {
76
+ if (this.isShutdown) {
77
+ log.warn('Cannot register after shutdown')
78
+ return
79
+ }
80
+ // Set context manager, this is required to correlate logs to spans
81
+ context.setGlobalContextManager(this.#contextManager)
82
+ logs.setGlobalLoggerProvider(this)
83
+ }
84
+
85
+ /**
86
+ * Forces a flush of all pending log records.
87
+ * @returns {undefined} Promise that resolves when flush is n ssue cncomplete
88
+ */
89
+ forceFlush () {
90
+ if (!this.isShutdown) {
91
+ return this.processor?.forceFlush()
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Shuts down the logger provider and all associated processors.
97
+ * @returns {undefined} Promise that resolves when shutdown is complete
98
+ */
99
+ shutdown () {
100
+ if (!this.isShutdown) {
101
+ this.isShutdown = true
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Creates a no-op logger for use when the provider is shutdown.
107
+ * @returns {Logger} A no-op logger instance
108
+ * @private
109
+ */
110
+ #createNoOpLogger () {
111
+ return {
112
+ instrumentationScope: {
113
+ name: 'dd-trace-js',
114
+ version: ''
115
+ },
116
+ emit: () => {},
117
+ debug: () => {},
118
+ info: () => {},
119
+ warn: () => {},
120
+ error: () => {},
121
+ fatal: () => {}
122
+ }
123
+ }
124
+ }
125
+
126
+ module.exports = LoggerProvider
@@ -0,0 +1,53 @@
1
+ 'use strict'
2
+
3
+ const OtlpHttpExporterBase = require('../otlp/otlp_http_exporter_base')
4
+ const OtlpTransformer = require('./otlp_transformer')
5
+
6
+ /**
7
+ * @typedef {import('@opentelemetry/resources').Resource} Resource
8
+ * @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
9
+ */
10
+
11
+ /**
12
+ * OtlpHttpLogExporter exports log records via OTLP over HTTP.
13
+ *
14
+ * This implementation follows the OTLP HTTP specification:
15
+ * https://opentelemetry.io/docs/specs/otlp/#otlphttp
16
+ *
17
+ * @class OtlpHttpLogExporter
18
+ * @extends OtlpHttpExporterBase
19
+ */
20
+ class OtlpHttpLogExporter extends OtlpHttpExporterBase {
21
+ /**
22
+ * Creates a new OtlpHttpLogExporter instance.
23
+ *
24
+ * @param {string} url - OTLP endpoint URL
25
+ * @param {string} headers - Additional HTTP headers as comma-separated key=value string
26
+ * @param {number} timeout - Request timeout in milliseconds
27
+ * @param {string} protocol - OTLP protocol (http/protobuf or http/json)
28
+ * @param {Resource} resource - Resource attributes
29
+ */
30
+ constructor (url, headers, timeout, protocol, resource) {
31
+ super(url, headers, timeout, protocol, '/v1/logs', 'logs')
32
+ this.transformer = new OtlpTransformer(resource, protocol)
33
+ }
34
+
35
+ /**
36
+ * Exports log records via OTLP over HTTP.
37
+ *
38
+ * @param {LogRecord[]} logRecords - Array of enriched log records to export
39
+ * @param {Function} resultCallback - Callback function for export result
40
+ */
41
+ export (logRecords, resultCallback) {
42
+ if (logRecords.length === 0) {
43
+ resultCallback({ code: 0 })
44
+ return
45
+ }
46
+
47
+ const payload = this.transformer.transformLogRecords(logRecords)
48
+ this._sendPayload(payload, resultCallback)
49
+ this._recordTelemetry('otel.log_records', logRecords.length)
50
+ }
51
+ }
52
+
53
+ module.exports = OtlpHttpLogExporter
@@ -0,0 +1,252 @@
1
+ 'use strict'
2
+
3
+ const OtlpTransformerBase = require('../otlp/otlp_transformer_base')
4
+ const { SeverityNumber } = require('@opentelemetry/api-logs')
5
+ const { getProtobufTypes } = require('../otlp/protobuf_loader')
6
+ const { trace } = require('@opentelemetry/api')
7
+
8
+ /**
9
+ * @typedef {import('@opentelemetry/api-logs').LogRecord} LogRecord
10
+ */
11
+
12
+ // Global severity mapping constant - no need to regenerate
13
+ const SEVERITY_MAP = {
14
+ [SeverityNumber.TRACE]: 'SEVERITY_NUMBER_TRACE',
15
+ [SeverityNumber.TRACE2]: 'SEVERITY_NUMBER_TRACE2',
16
+ [SeverityNumber.TRACE3]: 'SEVERITY_NUMBER_TRACE3',
17
+ [SeverityNumber.TRACE4]: 'SEVERITY_NUMBER_TRACE4',
18
+ [SeverityNumber.DEBUG]: 'SEVERITY_NUMBER_DEBUG',
19
+ [SeverityNumber.DEBUG2]: 'SEVERITY_NUMBER_DEBUG2',
20
+ [SeverityNumber.DEBUG3]: 'SEVERITY_NUMBER_DEBUG3',
21
+ [SeverityNumber.DEBUG4]: 'SEVERITY_NUMBER_DEBUG4',
22
+ [SeverityNumber.INFO]: 'SEVERITY_NUMBER_INFO',
23
+ [SeverityNumber.INFO2]: 'SEVERITY_NUMBER_INFO2',
24
+ [SeverityNumber.INFO3]: 'SEVERITY_NUMBER_INFO3',
25
+ [SeverityNumber.INFO4]: 'SEVERITY_NUMBER_INFO4',
26
+ [SeverityNumber.WARN]: 'SEVERITY_NUMBER_WARN',
27
+ [SeverityNumber.WARN2]: 'SEVERITY_NUMBER_WARN2',
28
+ [SeverityNumber.WARN3]: 'SEVERITY_NUMBER_WARN3',
29
+ [SeverityNumber.WARN4]: 'SEVERITY_NUMBER_WARN4',
30
+ [SeverityNumber.ERROR]: 'SEVERITY_NUMBER_ERROR',
31
+ [SeverityNumber.ERROR2]: 'SEVERITY_NUMBER_ERROR2',
32
+ [SeverityNumber.ERROR3]: 'SEVERITY_NUMBER_ERROR3',
33
+ [SeverityNumber.ERROR4]: 'SEVERITY_NUMBER_ERROR4',
34
+ [SeverityNumber.FATAL]: 'SEVERITY_NUMBER_FATAL',
35
+ [SeverityNumber.FATAL2]: 'SEVERITY_NUMBER_FATAL2',
36
+ [SeverityNumber.FATAL3]: 'SEVERITY_NUMBER_FATAL3',
37
+ [SeverityNumber.FATAL4]: 'SEVERITY_NUMBER_FATAL4'
38
+ }
39
+
40
+ /**
41
+ * OtlpTransformer transforms log records to OTLP format.
42
+ *
43
+ * This implementation follows the OTLP Logs Data Model specification:
44
+ * https://opentelemetry.io/docs/specs/otlp/#log-data-model
45
+ *
46
+ * @class OtlpTransformer
47
+ * @extends OtlpTransformerBase
48
+ */
49
+ class OtlpTransformer extends OtlpTransformerBase {
50
+ /**
51
+ * Creates a new OtlpTransformer instance.
52
+ *
53
+ * @param {import('@opentelemetry/api').Attributes} resourceAttributes - Resource attributes
54
+ * @param {string} protocol - OTLP protocol (http/protobuf or http/json)
55
+ */
56
+ constructor (resourceAttributes, protocol) {
57
+ super(resourceAttributes, protocol, 'logs')
58
+ }
59
+
60
+ /**
61
+ * Transforms log records to OTLP format based on the configured protocol.
62
+ * @param {LogRecord[]} logRecords - Array of enriched log records to transform
63
+ * @returns {Buffer} Transformed log records in the appropriate format
64
+ */
65
+ transformLogRecords (logRecords) {
66
+ if (this.protocol === 'http/json') {
67
+ return this.#transformToJson(logRecords)
68
+ }
69
+ return this.#transformToProtobuf(logRecords)
70
+ }
71
+
72
+ /**
73
+ * Transforms log records to protobuf format.
74
+ * @param {LogRecord[]} logRecords - Array of enriched log records to transform
75
+ * @returns {Buffer} Protobuf-encoded log records
76
+ * @private
77
+ */
78
+ #transformToProtobuf (logRecords) {
79
+ const { protoLogsService } = getProtobufTypes()
80
+
81
+ const logsData = {
82
+ resourceLogs: [{
83
+ resource: this._transformResource(),
84
+ scopeLogs: this.#transformScope(logRecords),
85
+ }]
86
+ }
87
+
88
+ return this._serializeToProtobuf(protoLogsService, logsData)
89
+ }
90
+
91
+ /**
92
+ * Transforms log records to JSON format.
93
+ * @param {LogRecord[]} logRecords - Array of enriched log records to transform
94
+ * @returns {Buffer} JSON-encoded log records
95
+ * @private
96
+ */
97
+ #transformToJson (logRecords) {
98
+ const logsData = {
99
+ resourceLogs: [{
100
+ resource: this._transformResource(),
101
+ scopeLogs: this.#transformScope(logRecords)
102
+ }]
103
+ }
104
+ return this._serializeToJson(logsData)
105
+ }
106
+
107
+ /**
108
+ * Creates scope logs grouped by instrumentation library.
109
+ * @param {LogRecord[]} logRecords - Array of log records to transform
110
+ * @returns {Object[]} Array of scope log objects
111
+ * @private
112
+ */
113
+ #transformScope (logRecords) {
114
+ const groupedRecords = this._groupByInstrumentationScope(logRecords)
115
+ const scopeLogs = []
116
+
117
+ for (const records of groupedRecords.values()) {
118
+ const schemaUrl = records[0]?.instrumentationScope?.schemaUrl || ''
119
+ scopeLogs.push({
120
+ scope: {
121
+ name: records[0]?.instrumentationScope?.name || 'dd-trace-js',
122
+ version: records[0]?.instrumentationScope?.version || '',
123
+ attributes: [],
124
+ droppedAttributesCount: 0
125
+ },
126
+ schemaUrl,
127
+ logRecords: records.map(record => this.#transformLogRecord(record))
128
+ })
129
+ }
130
+
131
+ return scopeLogs
132
+ }
133
+
134
+ /**
135
+ * Transforms a single log record to OTLP format.
136
+ * @param {LogRecord} logRecord - Log record to transform
137
+ * @returns {Object} OTLP log record object
138
+ * @private
139
+ */
140
+ #transformLogRecord (logRecord) {
141
+ const spanContext = this.#extractSpanContext(logRecord.context)
142
+
143
+ const result = {
144
+ timeUnixNano: logRecord.timestamp,
145
+ body: this.#transformBody(logRecord.body)
146
+ }
147
+
148
+ // Add optional fields only if they are set
149
+ if (logRecord.observedTimestamp) {
150
+ result.observedTimeUnixNano = logRecord.observedTimestamp
151
+ }
152
+
153
+ if (logRecord.severityNumber !== undefined) {
154
+ result.severityNumber = this.#mapSeverityNumber(logRecord.severityNumber)
155
+ }
156
+
157
+ if (logRecord.severityText) {
158
+ result.severityText = logRecord.severityText
159
+ }
160
+
161
+ if (logRecord.attributes) {
162
+ result.attributes = this._transformAttributes(logRecord.attributes)
163
+ }
164
+
165
+ if (spanContext?.traceFlags !== undefined) {
166
+ result.flags = spanContext.traceFlags
167
+ }
168
+
169
+ // Only include traceId and spanId if they are valid
170
+ if (spanContext?.traceId && spanContext.traceId !== '00000000000000000000000000000000') {
171
+ result.traceId = this.#hexToBytes(spanContext.traceId)
172
+ }
173
+
174
+ if (spanContext?.spanId && spanContext.spanId !== '0000000000000000') {
175
+ result.spanId = this.#hexToBytes(spanContext.spanId)
176
+ }
177
+
178
+ return result
179
+ }
180
+
181
+ /**
182
+ * Extracts span context from the log record's context.
183
+ * @param {Object} logContext - The log record's context
184
+ * @returns {Object|null} Span context or null if not available
185
+ * @private
186
+ */
187
+ #extractSpanContext (logContext) {
188
+ if (!logContext) return null
189
+
190
+ const activeSpan = trace.getSpan(logContext)
191
+ if (activeSpan) {
192
+ return activeSpan.spanContext()
193
+ }
194
+
195
+ return null
196
+ }
197
+
198
+ /**
199
+ * Maps OpenTelemetry severity number to protobuf severity number.
200
+ * @param {number} severityNumber - OpenTelemetry severity number
201
+ * @returns {number} Protobuf severity number
202
+ * @private
203
+ */
204
+ #mapSeverityNumber (severityNumber) {
205
+ const { protoSeverityNumber } = getProtobufTypes()
206
+ const severityName = SEVERITY_MAP[severityNumber] || 'SEVERITY_NUMBER_INFO'
207
+ return protoSeverityNumber.values[severityName]
208
+ }
209
+
210
+ /**
211
+ * Converts a hex string to a Buffer.
212
+ * @param {string} hexString - Hex string to convert
213
+ * @returns {Buffer} Buffer containing the hex data
214
+ * @private
215
+ */
216
+ #hexToBytes (hexString) {
217
+ const cleanHex = hexString ? (hexString.startsWith('0x') ? hexString.slice(2) : hexString) : ''
218
+ const paddedHex = cleanHex.length % 2 === 0 ? cleanHex : '0' + cleanHex
219
+ return Buffer.from(paddedHex, 'hex')
220
+ }
221
+
222
+ /**
223
+ * Transforms log body to OTLP AnyValue format.
224
+ * @param {any} body - Log body to transform
225
+ * @returns {Object} OTLP AnyValue object
226
+ * @private
227
+ */
228
+ #transformBody (body) {
229
+ if (typeof body === 'string') {
230
+ return { stringValue: body }
231
+ } else if (typeof body === 'number') {
232
+ if (Number.isInteger(body)) {
233
+ return { intValue: body }
234
+ }
235
+ return { doubleValue: body }
236
+ } else if (typeof body === 'boolean') {
237
+ return { boolValue: body }
238
+ } else if (body && typeof body === 'object') {
239
+ return {
240
+ kvlistValue: {
241
+ values: Object.entries(body).map(([key, value]) => ({
242
+ key,
243
+ value: this._transformAnyValue(value)
244
+ }))
245
+ }
246
+ }
247
+ }
248
+ return { stringValue: String(body) }
249
+ }
250
+ }
251
+
252
+ module.exports = OtlpTransformer