dd-trace 4.47.1 → 4.49.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 (156) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/ext/types.d.ts +1 -0
  3. package/ext/types.js +1 -0
  4. package/index.d.ts +361 -0
  5. package/package.json +18 -13
  6. package/packages/datadog-code-origin/index.js +38 -0
  7. package/packages/datadog-core/index.js +2 -2
  8. package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
  9. package/packages/datadog-esbuild/index.js +4 -2
  10. package/packages/datadog-instrumentations/src/amqplib.js +65 -5
  11. package/packages/datadog-instrumentations/src/avsc.js +37 -0
  12. package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
  13. package/packages/datadog-instrumentations/src/child_process.js +144 -27
  14. package/packages/datadog-instrumentations/src/express.js +37 -4
  15. package/packages/datadog-instrumentations/src/fastify.js +12 -1
  16. package/packages/datadog-instrumentations/src/fs.js +27 -7
  17. package/packages/datadog-instrumentations/src/helpers/hooks.js +6 -0
  18. package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
  19. package/packages/datadog-instrumentations/src/jest.js +2 -1
  20. package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
  21. package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
  23. package/packages/datadog-instrumentations/src/multer.js +37 -0
  24. package/packages/datadog-instrumentations/src/mysql2.js +220 -1
  25. package/packages/datadog-instrumentations/src/openai.js +2 -2
  26. package/packages/datadog-instrumentations/src/protobufjs.js +127 -0
  27. package/packages/datadog-instrumentations/src/url.js +84 -0
  28. package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
  29. package/packages/datadog-instrumentations/src/winston.js +22 -0
  30. package/packages/datadog-plugin-amqplib/src/consumer.js +4 -4
  31. package/packages/datadog-plugin-avsc/src/index.js +9 -0
  32. package/packages/datadog-plugin-avsc/src/schema_iterator.js +169 -0
  33. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  34. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  35. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -0
  36. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  37. package/packages/datadog-plugin-azure-functions/src/index.js +77 -0
  38. package/packages/datadog-plugin-fastify/src/code_origin.js +31 -0
  39. package/packages/datadog-plugin-fastify/src/index.js +10 -12
  40. package/packages/datadog-plugin-fastify/src/tracing.js +19 -0
  41. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +8 -1
  42. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
  43. package/packages/datadog-plugin-grpc/src/client.js +3 -0
  44. package/packages/datadog-plugin-grpc/src/server.js +3 -0
  45. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
  46. package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -4
  47. package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
  48. package/packages/datadog-plugin-mocha/src/index.js +4 -1
  49. package/packages/datadog-plugin-openai/src/index.js +9 -1015
  50. package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
  51. package/packages/datadog-plugin-protobufjs/src/index.js +14 -0
  52. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +180 -0
  53. package/packages/dd-trace/src/appsec/addresses.js +8 -1
  54. package/packages/dd-trace/src/appsec/channels.js +7 -1
  55. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +13 -1
  56. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +8 -1
  57. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  58. package/packages/dd-trace/src/appsec/iast/index.js +3 -0
  59. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
  60. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
  61. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +15 -0
  62. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
  63. package/packages/dd-trace/src/appsec/index.js +61 -43
  64. package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
  65. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +99 -0
  66. package/packages/dd-trace/src/appsec/rasp/index.js +27 -10
  67. package/packages/dd-trace/src/appsec/rasp/lfi.js +112 -0
  68. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +24 -4
  69. package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
  70. package/packages/dd-trace/src/appsec/rasp/utils.js +4 -2
  71. package/packages/dd-trace/src/appsec/recommended.json +3 -7
  72. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
  73. package/packages/dd-trace/src/appsec/remote_config/index.js +10 -0
  74. package/packages/dd-trace/src/appsec/reporter.js +17 -9
  75. package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -3
  76. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
  77. package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
  78. package/packages/dd-trace/src/azure_metadata.js +120 -0
  79. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
  80. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
  81. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -14
  82. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
  83. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
  84. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
  85. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
  86. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +53 -0
  87. package/packages/dd-trace/src/config.js +86 -6
  88. package/packages/dd-trace/src/constants.js +3 -1
  89. package/packages/dd-trace/src/datastreams/pathway.js +1 -0
  90. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +25 -17
  91. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
  92. package/packages/dd-trace/src/debugger/devtools_client/index.js +52 -5
  93. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +4 -4
  94. package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -2
  95. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +187 -0
  96. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +40 -0
  97. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +252 -0
  98. package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
  99. package/packages/dd-trace/src/debugger/devtools_client/state.js +19 -4
  100. package/packages/dd-trace/src/debugger/index.js +10 -3
  101. package/packages/dd-trace/src/exporters/common/request.js +8 -34
  102. package/packages/dd-trace/src/exporters/common/url-to-http-options-polyfill.js +31 -0
  103. package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
  104. package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
  105. package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
  106. package/packages/dd-trace/src/llmobs/index.js +103 -0
  107. package/packages/dd-trace/src/llmobs/noop.js +82 -0
  108. package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
  109. package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
  110. package/packages/dd-trace/src/llmobs/sdk.js +377 -0
  111. package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
  112. package/packages/dd-trace/src/llmobs/storage.js +7 -0
  113. package/packages/dd-trace/src/llmobs/tagger.js +322 -0
  114. package/packages/dd-trace/src/llmobs/util.js +176 -0
  115. package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
  116. package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
  117. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
  118. package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
  119. package/packages/dd-trace/src/llmobs/writers/spans/base.js +49 -0
  120. package/packages/dd-trace/src/noop/proxy.js +3 -0
  121. package/packages/dd-trace/src/noop/span.js +3 -0
  122. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  123. package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
  124. package/packages/dd-trace/src/opentracing/propagation/text_map.js +73 -12
  125. package/packages/dd-trace/src/opentracing/span.js +12 -0
  126. package/packages/dd-trace/src/opentracing/tracer.js +8 -1
  127. package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
  128. package/packages/dd-trace/src/payload-tagging/index.js +1 -1
  129. package/packages/dd-trace/src/payload-tagging/jsonpath-plus.js +2094 -0
  130. package/packages/dd-trace/src/plugin_manager.js +4 -2
  131. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
  132. package/packages/dd-trace/src/plugins/index.js +3 -0
  133. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  134. package/packages/dd-trace/src/plugins/outbound.js +9 -0
  135. package/packages/dd-trace/src/plugins/schema.js +35 -0
  136. package/packages/dd-trace/src/plugins/util/ci.js +23 -1
  137. package/packages/dd-trace/src/plugins/util/serverless.js +7 -0
  138. package/packages/dd-trace/src/plugins/util/stacktrace.js +94 -0
  139. package/packages/dd-trace/src/plugins/util/tags.js +7 -0
  140. package/packages/dd-trace/src/plugins/util/test.js +20 -22
  141. package/packages/dd-trace/src/plugins/util/web.js +6 -4
  142. package/packages/dd-trace/src/priority_sampler.js +16 -0
  143. package/packages/dd-trace/src/profiling/config.js +3 -1
  144. package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
  145. package/packages/dd-trace/src/profiling/profiler.js +24 -14
  146. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +95 -66
  148. package/packages/dd-trace/src/proxy.js +20 -1
  149. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  150. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  151. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  152. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  153. package/packages/dd-trace/src/span_processor.js +5 -0
  154. package/packages/dd-trace/src/telemetry/index.js +11 -1
  155. package/packages/datadog-core/src/storage/async_resource.js +0 -108
  156. package/packages/datadog-core/src/storage/index.js +0 -5
@@ -0,0 +1,377 @@
1
+ 'use strict'
2
+
3
+ const { SPAN_KIND, OUTPUT_VALUE } = require('./constants/tags')
4
+
5
+ const {
6
+ getFunctionArguments,
7
+ validateKind
8
+ } = require('./util')
9
+ const { isTrue } = require('../util')
10
+
11
+ const { storage } = require('./storage')
12
+
13
+ const Span = require('../opentracing/span')
14
+
15
+ const tracerVersion = require('../../../../package.json').version
16
+ const logger = require('../log')
17
+
18
+ const LLMObsTagger = require('./tagger')
19
+
20
+ // communicating with writer
21
+ const { channel } = require('dc-polyfill')
22
+ const evalMetricAppendCh = channel('llmobs:eval-metric:append')
23
+ const flushCh = channel('llmobs:writers:flush')
24
+ const NoopLLMObs = require('./noop')
25
+
26
+ class LLMObs extends NoopLLMObs {
27
+ constructor (tracer, llmobsModule, config) {
28
+ super(tracer)
29
+
30
+ this._config = config
31
+ this._llmobsModule = llmobsModule
32
+ this._tagger = new LLMObsTagger(config)
33
+ }
34
+
35
+ get enabled () {
36
+ return this._config.llmobs.enabled
37
+ }
38
+
39
+ enable (options = {}) {
40
+ if (this.enabled) {
41
+ logger.debug('LLMObs is already enabled.')
42
+ return
43
+ }
44
+
45
+ logger.debug('Enabling LLMObs')
46
+
47
+ const { mlApp, agentlessEnabled } = options
48
+
49
+ const { DD_LLMOBS_ENABLED } = process.env
50
+
51
+ const llmobsConfig = {
52
+ mlApp,
53
+ agentlessEnabled
54
+ }
55
+
56
+ const enabled = DD_LLMOBS_ENABLED == null || isTrue(DD_LLMOBS_ENABLED)
57
+ if (!enabled) {
58
+ logger.debug('LLMObs.enable() called when DD_LLMOBS_ENABLED is false. No action taken.')
59
+ return
60
+ }
61
+
62
+ this._config.llmobs.enabled = true
63
+ this._config.configure({ ...this._config, llmobs: llmobsConfig })
64
+
65
+ // configure writers and channel subscribers
66
+ this._llmobsModule.enable(this._config)
67
+ }
68
+
69
+ disable () {
70
+ if (!this.enabled) {
71
+ logger.debug('LLMObs is already disabled.')
72
+ return
73
+ }
74
+
75
+ logger.debug('Disabling LLMObs')
76
+
77
+ this._config.llmobs.enabled = false
78
+
79
+ // disable writers and channel subscribers
80
+ this._llmobsModule.disable()
81
+ }
82
+
83
+ trace (options = {}, fn) {
84
+ if (typeof options === 'function') {
85
+ fn = options
86
+ options = {}
87
+ }
88
+
89
+ const kind = validateKind(options.kind) // will throw if kind is undefined or not an expected kind
90
+
91
+ // name is required for spans generated with `trace`
92
+ // while `kind` is required, this should never throw (as otherwise it would have thrown above)
93
+ const name = options.name || kind
94
+ if (!name) {
95
+ throw new Error('No span name provided for `trace`.')
96
+ }
97
+
98
+ const {
99
+ spanOptions,
100
+ ...llmobsOptions
101
+ } = this._extractOptions(options)
102
+
103
+ if (fn.length > 1) {
104
+ return this._tracer.trace(name, spanOptions, (span, cb) =>
105
+ this._activate(span, { kind, options: llmobsOptions }, () => fn(span, cb))
106
+ )
107
+ }
108
+
109
+ return this._tracer.trace(name, spanOptions, span =>
110
+ this._activate(span, { kind, options: llmobsOptions }, () => fn(span))
111
+ )
112
+ }
113
+
114
+ wrap (options = {}, fn) {
115
+ if (typeof options === 'function') {
116
+ fn = options
117
+ options = {}
118
+ }
119
+
120
+ const kind = validateKind(options.kind) // will throw if kind is undefined or not an expected kind
121
+ let name = options.name || (fn?.name ? fn.name : undefined) || kind
122
+
123
+ if (!name) {
124
+ logger.warn('No span name provided for `wrap`. Defaulting to "unnamed-anonymous-function".')
125
+ name = 'unnamed-anonymous-function'
126
+ }
127
+
128
+ const {
129
+ spanOptions,
130
+ ...llmobsOptions
131
+ } = this._extractOptions(options)
132
+
133
+ const llmobs = this
134
+
135
+ function wrapped () {
136
+ const span = llmobs._tracer.scope().active()
137
+
138
+ const result = llmobs._activate(span, { kind, options: llmobsOptions }, () => {
139
+ if (!['llm', 'embedding'].includes(kind)) {
140
+ llmobs.annotate(span, { inputData: getFunctionArguments(fn, arguments) })
141
+ }
142
+
143
+ return fn.apply(this, arguments)
144
+ })
145
+
146
+ if (result && typeof result.then === 'function') {
147
+ return result.then(value => {
148
+ if (value && !['llm', 'retrieval'].includes(kind) && !LLMObsTagger.tagMap.get(span)?.[OUTPUT_VALUE]) {
149
+ llmobs.annotate(span, { outputData: value })
150
+ }
151
+ return value
152
+ })
153
+ }
154
+
155
+ if (result && !['llm', 'retrieval'].includes(kind) && !LLMObsTagger.tagMap.get(span)?.[OUTPUT_VALUE]) {
156
+ llmobs.annotate(span, { outputData: result })
157
+ }
158
+
159
+ return result
160
+ }
161
+
162
+ return this._tracer.wrap(name, spanOptions, wrapped)
163
+ }
164
+
165
+ annotate (span, options) {
166
+ if (!this.enabled) return
167
+
168
+ if (!span) {
169
+ span = this._active()
170
+ }
171
+
172
+ if ((span && !options) && !(span instanceof Span)) {
173
+ options = span
174
+ span = this._active()
175
+ }
176
+
177
+ if (!span) {
178
+ throw new Error('No span provided and no active LLMObs-generated span found')
179
+ }
180
+ if (!options) {
181
+ throw new Error('No options provided for annotation.')
182
+ }
183
+
184
+ if (!LLMObsTagger.tagMap.has(span)) {
185
+ throw new Error('Span must be an LLMObs-generated span')
186
+ }
187
+ if (span._duration !== undefined) {
188
+ throw new Error('Cannot annotate a finished span')
189
+ }
190
+
191
+ const spanKind = LLMObsTagger.tagMap.get(span)[SPAN_KIND]
192
+ if (!spanKind) {
193
+ throw new Error('LLMObs span must have a span kind specified')
194
+ }
195
+
196
+ const { inputData, outputData, metadata, metrics, tags } = options
197
+
198
+ if (inputData || outputData) {
199
+ if (spanKind === 'llm') {
200
+ this._tagger.tagLLMIO(span, inputData, outputData)
201
+ } else if (spanKind === 'embedding') {
202
+ this._tagger.tagEmbeddingIO(span, inputData, outputData)
203
+ } else if (spanKind === 'retrieval') {
204
+ this._tagger.tagRetrievalIO(span, inputData, outputData)
205
+ } else {
206
+ this._tagger.tagTextIO(span, inputData, outputData)
207
+ }
208
+ }
209
+
210
+ if (metadata) {
211
+ this._tagger.tagMetadata(span, metadata)
212
+ }
213
+
214
+ if (metrics) {
215
+ this._tagger.tagMetrics(span, metrics)
216
+ }
217
+
218
+ if (tags) {
219
+ this._tagger.tagSpanTags(span, tags)
220
+ }
221
+ }
222
+
223
+ exportSpan (span) {
224
+ span = span || this._active()
225
+
226
+ if (!span) {
227
+ throw new Error('No span provided and no active LLMObs-generated span found')
228
+ }
229
+
230
+ if (!(span instanceof Span)) {
231
+ throw new Error('Span must be a valid Span object.')
232
+ }
233
+
234
+ if (!LLMObsTagger.tagMap.has(span)) {
235
+ throw new Error('Span must be an LLMObs-generated span')
236
+ }
237
+
238
+ try {
239
+ return {
240
+ traceId: span.context().toTraceId(true),
241
+ spanId: span.context().toSpanId()
242
+ }
243
+ } catch {
244
+ logger.warn('Faild to export span. Span must be a valid Span object.')
245
+ }
246
+ }
247
+
248
+ submitEvaluation (llmobsSpanContext, options = {}) {
249
+ if (!this.enabled) return
250
+
251
+ if (!this._config.apiKey) {
252
+ throw new Error(
253
+ 'DD_API_KEY is required for sending evaluation metrics. Evaluation metric data will not be sent.\n' +
254
+ 'Ensure this configuration is set before running your application.'
255
+ )
256
+ }
257
+
258
+ const { traceId, spanId } = llmobsSpanContext
259
+ if (!traceId || !spanId) {
260
+ throw new Error(
261
+ 'spanId and traceId must both be specified for the given evaluation metric to be submitted.'
262
+ )
263
+ }
264
+
265
+ const mlApp = options.mlApp || this._config.llmobs.mlApp
266
+ if (!mlApp) {
267
+ throw new Error(
268
+ 'ML App name is required for sending evaluation metrics. Evaluation metric data will not be sent.'
269
+ )
270
+ }
271
+
272
+ const timestampMs = options.timestampMs || Date.now()
273
+ if (typeof timestampMs !== 'number' || timestampMs < 0) {
274
+ throw new Error('timestampMs must be a non-negative integer. Evaluation metric data will not be sent')
275
+ }
276
+
277
+ const { label, value, tags } = options
278
+ const metricType = options.metricType?.toLowerCase()
279
+ if (!label) {
280
+ throw new Error('label must be the specified name of the evaluation metric')
281
+ }
282
+ if (!metricType || !['categorical', 'score'].includes(metricType)) {
283
+ throw new Error('metricType must be one of "categorical" or "score"')
284
+ }
285
+
286
+ if (metricType === 'categorical' && typeof value !== 'string') {
287
+ throw new Error('value must be a string for a categorical metric.')
288
+ }
289
+ if (metricType === 'score' && typeof value !== 'number') {
290
+ throw new Error('value must be a number for a score metric.')
291
+ }
292
+
293
+ const evaluationTags = {
294
+ 'dd-trace.version': tracerVersion,
295
+ ml_app: mlApp
296
+ }
297
+
298
+ if (tags) {
299
+ for (const key in tags) {
300
+ const tag = tags[key]
301
+ if (typeof tag === 'string') {
302
+ evaluationTags[key] = tag
303
+ } else if (typeof tag.toString === 'function') {
304
+ evaluationTags[key] = tag.toString()
305
+ } else if (tag == null) {
306
+ evaluationTags[key] = Object.prototype.toString.call(tag)
307
+ } else {
308
+ // should be a rare case
309
+ // every object in JS has a toString, otherwise every primitive has its own toString
310
+ // null and undefined are handled above
311
+ throw new Error('Failed to parse tags. Tags for evaluation metrics must be strings')
312
+ }
313
+ }
314
+ }
315
+
316
+ const payload = {
317
+ span_id: spanId,
318
+ trace_id: traceId,
319
+ label,
320
+ metric_type: metricType,
321
+ ml_app: mlApp,
322
+ [`${metricType}_value`]: value,
323
+ timestamp_ms: timestampMs,
324
+ tags: Object.entries(evaluationTags).map(([key, value]) => `${key}:${value}`)
325
+ }
326
+
327
+ evalMetricAppendCh.publish(payload)
328
+ }
329
+
330
+ flush () {
331
+ if (!this.enabled) return
332
+
333
+ flushCh.publish()
334
+ }
335
+
336
+ _active () {
337
+ const store = storage.getStore()
338
+ return store?.span
339
+ }
340
+
341
+ _activate (span, { kind, options } = {}, fn) {
342
+ const parent = this._active()
343
+ if (this.enabled) storage.enterWith({ span })
344
+
345
+ this._tagger.registerLLMObsSpan(span, {
346
+ ...options,
347
+ parent,
348
+ kind
349
+ })
350
+
351
+ try {
352
+ return fn()
353
+ } finally {
354
+ if (this.enabled) storage.enterWith({ span: parent })
355
+ }
356
+ }
357
+
358
+ _extractOptions (options) {
359
+ const {
360
+ modelName,
361
+ modelProvider,
362
+ sessionId,
363
+ mlApp,
364
+ ...spanOptions
365
+ } = options
366
+
367
+ return {
368
+ mlApp,
369
+ modelName,
370
+ modelProvider,
371
+ sessionId,
372
+ spanOptions
373
+ }
374
+ }
375
+ }
376
+
377
+ module.exports = LLMObs
@@ -0,0 +1,195 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ SPAN_KIND,
5
+ MODEL_NAME,
6
+ MODEL_PROVIDER,
7
+ METADATA,
8
+ INPUT_MESSAGES,
9
+ INPUT_VALUE,
10
+ OUTPUT_MESSAGES,
11
+ INPUT_DOCUMENTS,
12
+ OUTPUT_DOCUMENTS,
13
+ OUTPUT_VALUE,
14
+ METRICS,
15
+ ML_APP,
16
+ TAGS,
17
+ PARENT_ID_KEY,
18
+ SESSION_ID,
19
+ NAME
20
+ } = require('./constants/tags')
21
+ const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text')
22
+
23
+ const {
24
+ ERROR_MESSAGE,
25
+ ERROR_TYPE,
26
+ ERROR_STACK
27
+ } = require('../constants')
28
+
29
+ const LLMObsTagger = require('./tagger')
30
+
31
+ const tracerVersion = require('../../../../package.json').version
32
+ const logger = require('../log')
33
+
34
+ class LLMObsSpanProcessor {
35
+ constructor (config) {
36
+ this._config = config
37
+ }
38
+
39
+ setWriter (writer) {
40
+ this._writer = writer
41
+ }
42
+
43
+ // TODO: instead of relying on the tagger's weakmap registry, can we use some namespaced storage correlation?
44
+ process ({ span }) {
45
+ if (!this._config.llmobs.enabled) return
46
+ // if the span is not in our private tagger map, it is not an llmobs span
47
+ if (!LLMObsTagger.tagMap.has(span)) return
48
+
49
+ try {
50
+ const formattedEvent = this.format(span)
51
+ this._writer.append(formattedEvent)
52
+ } catch (e) {
53
+ // this should be a rare case
54
+ // we protect against unserializable properties in the format function, and in
55
+ // safeguards in the tagger
56
+ logger.warn(`
57
+ Failed to append span to LLM Observability writer, likely due to an unserializable property.
58
+ Span won't be sent to LLM Observability: ${e.message}
59
+ `)
60
+ }
61
+ }
62
+
63
+ format (span) {
64
+ const spanTags = span.context()._tags
65
+ const mlObsTags = LLMObsTagger.tagMap.get(span)
66
+
67
+ const spanKind = mlObsTags[SPAN_KIND]
68
+
69
+ const meta = { 'span.kind': spanKind, input: {}, output: {} }
70
+ const input = {}
71
+ const output = {}
72
+
73
+ if (['llm', 'embedding'].includes(spanKind)) {
74
+ meta.model_name = mlObsTags[MODEL_NAME] || 'custom'
75
+ meta.model_provider = (mlObsTags[MODEL_PROVIDER] || 'custom').toLowerCase()
76
+ }
77
+ if (mlObsTags[METADATA]) {
78
+ this._addObject(mlObsTags[METADATA], meta.metadata = {})
79
+ }
80
+ if (spanKind === 'llm' && mlObsTags[INPUT_MESSAGES]) {
81
+ input.messages = mlObsTags[INPUT_MESSAGES]
82
+ }
83
+ if (mlObsTags[INPUT_VALUE]) {
84
+ input.value = mlObsTags[INPUT_VALUE]
85
+ }
86
+ if (spanKind === 'llm' && mlObsTags[OUTPUT_MESSAGES]) {
87
+ output.messages = mlObsTags[OUTPUT_MESSAGES]
88
+ }
89
+ if (spanKind === 'embedding' && mlObsTags[INPUT_DOCUMENTS]) {
90
+ input.documents = mlObsTags[INPUT_DOCUMENTS]
91
+ }
92
+ if (mlObsTags[OUTPUT_VALUE]) {
93
+ output.value = mlObsTags[OUTPUT_VALUE]
94
+ }
95
+ if (spanKind === 'retrieval' && mlObsTags[OUTPUT_DOCUMENTS]) {
96
+ output.documents = mlObsTags[OUTPUT_DOCUMENTS]
97
+ }
98
+
99
+ const error = spanTags.error || spanTags[ERROR_TYPE]
100
+ if (error) {
101
+ meta[ERROR_MESSAGE] = spanTags[ERROR_MESSAGE] || error.message || error.code
102
+ meta[ERROR_TYPE] = spanTags[ERROR_TYPE] || error.name
103
+ meta[ERROR_STACK] = spanTags[ERROR_STACK] || error.stack
104
+ }
105
+
106
+ if (input) meta.input = input
107
+ if (output) meta.output = output
108
+
109
+ const metrics = mlObsTags[METRICS] || {}
110
+
111
+ const mlApp = mlObsTags[ML_APP]
112
+ const sessionId = mlObsTags[SESSION_ID]
113
+ const parentId = mlObsTags[PARENT_ID_KEY]
114
+
115
+ const name = mlObsTags[NAME] || span._name
116
+
117
+ const llmObsSpanEvent = {
118
+ trace_id: span.context().toTraceId(true),
119
+ span_id: span.context().toSpanId(),
120
+ parent_id: parentId,
121
+ name,
122
+ tags: this._processTags(span, mlApp, sessionId, error),
123
+ start_ns: Math.round(span._startTime * 1e6),
124
+ duration: Math.round(span._duration * 1e6),
125
+ status: error ? 'error' : 'ok',
126
+ meta,
127
+ metrics,
128
+ _dd: {
129
+ span_id: span.context().toSpanId(),
130
+ trace_id: span.context().toTraceId(true)
131
+ }
132
+ }
133
+
134
+ if (sessionId) llmObsSpanEvent.session_id = sessionId
135
+
136
+ return llmObsSpanEvent
137
+ }
138
+
139
+ // For now, this only applies to metadata, as we let users annotate this field with any object
140
+ // However, we want to protect against circular references or BigInts (unserializable)
141
+ // This function can be reused for other fields if needed
142
+ // Messages, Documents, and Metrics are safeguarded in `llmobs/tagger.js`
143
+ _addObject (obj, carrier) {
144
+ const seenObjects = new WeakSet()
145
+ seenObjects.add(obj) // capture root object
146
+
147
+ const isCircular = value => {
148
+ if (typeof value !== 'object') return false
149
+ if (seenObjects.has(value)) return true
150
+ seenObjects.add(value)
151
+ return false
152
+ }
153
+
154
+ const add = (obj, carrier) => {
155
+ for (const key in obj) {
156
+ const value = obj[key]
157
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) continue
158
+ if (typeof value === 'bigint' || isCircular(value)) {
159
+ // mark as unserializable instead of dropping
160
+ logger.warn(`Unserializable property found in metadata: ${key}`)
161
+ carrier[key] = UNSERIALIZABLE_VALUE_TEXT
162
+ continue
163
+ }
164
+ if (typeof value === 'object') {
165
+ add(value, carrier[key] = {})
166
+ } else {
167
+ carrier[key] = value
168
+ }
169
+ }
170
+ }
171
+
172
+ add(obj, carrier)
173
+ }
174
+
175
+ _processTags (span, mlApp, sessionId, error) {
176
+ let tags = {
177
+ version: this._config.version,
178
+ env: this._config.env,
179
+ service: this._config.service,
180
+ source: 'integration',
181
+ ml_app: mlApp,
182
+ 'dd-trace.version': tracerVersion,
183
+ error: Number(!!error) || 0,
184
+ language: 'javascript'
185
+ }
186
+ const errType = span.context()._tags[ERROR_TYPE] || error?.name
187
+ if (errType) tags.error_type = errType
188
+ if (sessionId) tags.session_id = sessionId
189
+ const existingTags = LLMObsTagger.tagMap.get(span)?.[TAGS] || {}
190
+ if (existingTags) tags = { ...tags, ...existingTags }
191
+ return Object.entries(tags).map(([key, value]) => `${key}:${value ?? ''}`)
192
+ }
193
+ }
194
+
195
+ module.exports = LLMObsSpanProcessor
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ // TODO: remove this and use namespaced storage once available
4
+ const { AsyncLocalStorage } = require('async_hooks')
5
+ const storage = new AsyncLocalStorage()
6
+
7
+ module.exports = { storage }