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.
- package/LICENSE-3rdparty.csv +3 -0
- package/index.d.ts +49 -0
- package/package.json +11 -6
- package/packages/datadog-core/src/utils/src/set.js +5 -1
- package/packages/datadog-esbuild/index.js +112 -36
- package/packages/datadog-esbuild/src/utils.js +198 -0
- package/packages/datadog-instrumentations/src/azure-service-bus.js +49 -22
- package/packages/datadog-instrumentations/src/cookie-parser.js +2 -0
- package/packages/datadog-instrumentations/src/express-session.js +1 -0
- package/packages/datadog-instrumentations/src/express.js +82 -0
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +238 -0
- package/packages/datadog-instrumentations/src/jest.js +60 -14
- package/packages/datadog-instrumentations/src/mocha/utils.js +3 -4
- package/packages/datadog-instrumentations/src/playwright.js +110 -56
- package/packages/datadog-instrumentations/src/router.js +63 -6
- package/packages/datadog-instrumentations/src/ws.js +3 -3
- package/packages/datadog-plugin-amqplib/src/consumer.js +1 -1
- package/packages/datadog-plugin-azure-functions/src/index.js +24 -14
- package/packages/datadog-plugin-azure-service-bus/src/index.js +1 -1
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +60 -12
- package/packages/datadog-plugin-express/src/code_origin.js +2 -0
- package/packages/datadog-plugin-jest/src/index.js +53 -18
- package/packages/datadog-plugin-ws/src/close.js +2 -2
- package/packages/datadog-plugin-ws/src/producer.js +1 -1
- package/packages/datadog-plugin-ws/src/receiver.js +1 -1
- package/packages/dd-trace/src/appsec/index.js +9 -1
- package/packages/dd-trace/src/appsec/reporter.js +2 -3
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -0
- package/packages/dd-trace/src/config-helper.js +3 -1
- package/packages/dd-trace/src/config.js +437 -446
- package/packages/dd-trace/src/config_defaults.js +5 -12
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +8 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +11 -12
- package/packages/dd-trace/src/llmobs/sdk.js +20 -4
- package/packages/dd-trace/src/llmobs/tagger.js +12 -0
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +7 -127
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +19 -134
- package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +720 -0
- package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +78 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +177 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +163 -0
- package/packages/dd-trace/src/opentelemetry/{protos → otlp}/protobuf_loader.js +24 -6
- package/packages/dd-trace/src/plugins/util/ci.js +3 -2
- package/packages/dd-trace/src/plugins/util/stacktrace.js +16 -1
- package/packages/dd-trace/src/supported-configurations.json +2 -0
- package/packages/dd-trace/src/telemetry/endpoints.js +27 -1
- package/packages/dd-trace/src/telemetry/index.js +16 -13
- package/scripts/preinstall.js +3 -1
- package/version.js +2 -1
- /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/common.proto +0 -0
- /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/logs.proto +0 -0
- /package/packages/dd-trace/src/opentelemetry/{protos → otlp}/logs_service.proto +0 -0
- /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 {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
45
|
-
|
|
46
|
-
this._tagger.registerLLMObsSpan(span, {
|
|
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
|
|
60
|
-
llmobsStorage.enterWith(
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
78
|
-
|
|
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('../
|
|
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
|
-
|
|
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
|
-
|
|
80
|
+
|
|
115
81
|
const logsData = {
|
|
116
82
|
resourceLogs: [{
|
|
117
|
-
resource: this
|
|
83
|
+
resource: this._transformResource(),
|
|
118
84
|
scopeLogs: this.#transformScope(logRecords),
|
|
119
85
|
}]
|
|
120
86
|
}
|
|
121
87
|
|
|
122
|
-
|
|
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
|
|
100
|
+
resource: this._transformResource(),
|
|
139
101
|
scopeLogs: this.#transformScope(logRecords)
|
|
140
102
|
}]
|
|
141
103
|
}
|
|
142
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
248
|
+
return { stringValue: String(body) }
|
|
364
249
|
}
|
|
365
250
|
}
|
|
366
251
|
|