dd-trace 5.42.0 → 5.43.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.42.0",
3
+ "version": "5.43.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -89,7 +89,7 @@
89
89
  "@datadog/native-iast-rewriter": "2.8.0",
90
90
  "@datadog/native-iast-taint-tracking": "3.3.0",
91
91
  "@datadog/native-metrics": "^3.1.0",
92
- "@datadog/pprof": "5.5.1",
92
+ "@datadog/pprof": "5.6.0",
93
93
  "@datadog/sketches-js": "^2.1.0",
94
94
  "@isaacs/ttlcache": "^1.4.1",
95
95
  "@opentelemetry/api": ">=1.0.0 <1.9.0",
@@ -94,6 +94,10 @@ function isReporterPackageNewest (vitestPackage) {
94
94
  return vitestPackage.h?.name === 'BaseSequencer'
95
95
  }
96
96
 
97
+ function isBaseSequencer (vitestPackage) {
98
+ return vitestPackage.b?.name === 'BaseSequencer'
99
+ }
100
+
97
101
  function getChannelPromise (channelToPublishTo) {
98
102
  return new Promise(resolve => {
99
103
  sessionAsyncResource.runInAsyncScope(() => {
@@ -615,11 +619,22 @@ addHook({
615
619
 
616
620
  addHook({
617
621
  name: 'vitest',
618
- versions: ['>=3.0.0'],
622
+ versions: ['>=3.0.9'],
623
+ filePattern: 'dist/chunks/coverage.*'
624
+ }, (coveragePackage) => {
625
+ if (isBaseSequencer(coveragePackage)) {
626
+ shimmer.wrap(coveragePackage.b.prototype, 'sort', getSortWrapper)
627
+ }
628
+ return coveragePackage
629
+ })
630
+
631
+ addHook({
632
+ name: 'vitest',
633
+ versions: ['>=3.0.0 <3.0.9'],
619
634
  filePattern: 'dist/chunks/resolveConfig.*'
620
- }, (randomSequencerPackage) => {
621
- shimmer.wrap(randomSequencerPackage.B.prototype, 'sort', getSortWrapper)
622
- return randomSequencerPackage
635
+ }, (resolveConfigPackage) => {
636
+ shimmer.wrap(resolveConfigPackage.B.prototype, 'sort', getSortWrapper)
637
+ return resolveConfigPackage
623
638
  })
624
639
 
625
640
  // Can't specify file because compiled vitest includes hashes in their files
@@ -7,7 +7,8 @@ const tags = {
7
7
  RULE_TRIGGERED: 'rule_triggered',
8
8
  WAF_TIMEOUT: 'waf_timeout',
9
9
  WAF_VERSION: 'waf_version',
10
- EVENT_RULES_VERSION: 'event_rules_version'
10
+ EVENT_RULES_VERSION: 'event_rules_version',
11
+ INPUT_TRUNCATED: 'input_truncated'
11
12
  }
12
13
 
13
14
  function getVersionsTags (wafVersion, rulesVersion) {
@@ -7,6 +7,12 @@ const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
7
7
 
8
8
  const DD_TELEMETRY_WAF_RESULT_TAGS = Symbol('_dd.appsec.telemetry.waf.result.tags')
9
9
 
10
+ const TRUNCATION_FLAGS = {
11
+ STRING: 1,
12
+ CONTAINER_SIZE: 2,
13
+ CONTAINER_DEPTH: 4
14
+ }
15
+
10
16
  function addWafRequestMetrics (store, { duration, durationExt, wafTimeout, errorCode }) {
11
17
  store[DD_TELEMETRY_REQUEST_METRICS].duration += duration || 0
12
18
  store[DD_TELEMETRY_REQUEST_METRICS].durationExt += durationExt || 0
@@ -58,6 +64,12 @@ function trackWafMetrics (store, metrics) {
58
64
  metricTags[tags.WAF_TIMEOUT] = true
59
65
  }
60
66
 
67
+ const truncationReason = getTruncationReason(metrics)
68
+ if (truncationReason > 0) {
69
+ metricTags[tags.INPUT_TRUNCATED] = true
70
+ incrementTruncatedMetrics(metrics, truncationReason)
71
+ }
72
+
61
73
  return metricTags
62
74
  }
63
75
 
@@ -69,6 +81,7 @@ function getOrCreateMetricTags (store, versionsTags) {
69
81
  [tags.REQUEST_BLOCKED]: false,
70
82
  [tags.RULE_TRIGGERED]: false,
71
83
  [tags.WAF_TIMEOUT]: false,
84
+ [tags.INPUT_TRUNCATED]: false,
72
85
 
73
86
  ...versionsTags
74
87
  }
@@ -98,6 +111,39 @@ function incrementWafRequests (store) {
98
111
  }
99
112
  }
100
113
 
114
+ function incrementTruncatedMetrics (metrics, truncationReason) {
115
+ const truncationTags = { truncation_reason: truncationReason }
116
+ appsecMetrics.count('waf.input_truncated', truncationTags).inc(1)
117
+
118
+ if (metrics?.maxTruncatedString) {
119
+ appsecMetrics.distribution('waf.truncated_value_size', {
120
+ truncation_reason: TRUNCATION_FLAGS.STRING
121
+ }).track(metrics.maxTruncatedString)
122
+ }
123
+
124
+ if (metrics?.maxTruncatedContainerSize) {
125
+ appsecMetrics.distribution('waf.truncated_value_size', {
126
+ truncation_reason: TRUNCATION_FLAGS.CONTAINER_SIZE
127
+ }).track(metrics.maxTruncatedContainerSize)
128
+ }
129
+
130
+ if (metrics?.maxTruncatedContainerDepth) {
131
+ appsecMetrics.distribution('waf.truncated_value_size', {
132
+ truncation_reason: TRUNCATION_FLAGS.CONTAINER_DEPTH
133
+ }).track(metrics.maxTruncatedContainerDepth)
134
+ }
135
+ }
136
+
137
+ function getTruncationReason ({ maxTruncatedString, maxTruncatedContainerSize, maxTruncatedContainerDepth }) {
138
+ let reason = 0
139
+
140
+ if (maxTruncatedString) reason |= TRUNCATION_FLAGS.STRING
141
+ if (maxTruncatedContainerSize) reason |= TRUNCATION_FLAGS.CONTAINER_SIZE
142
+ if (maxTruncatedContainerDepth) reason |= TRUNCATION_FLAGS.CONTAINER_DEPTH
143
+
144
+ return reason
145
+ }
146
+
101
147
  module.exports = {
102
148
  addWafRequestMetrics,
103
149
  trackWafMetrics,
@@ -595,6 +595,7 @@ class Config {
595
595
  this._setValue(defaults, 'vertexai.spanCharLimit', 128)
596
596
  this._setValue(defaults, 'vertexai.spanPromptCompletionSampleRate', 1.0)
597
597
  this._setValue(defaults, 'trace.aws.addSpanPointers', true)
598
+ this._setValue(defaults, 'trace.nativeSpanEvents', false)
598
599
  }
599
600
 
600
601
  _applyLocalStableConfig () {
@@ -765,6 +766,7 @@ class Config {
765
766
  DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE,
766
767
  DD_VERTEXAI_SPAN_CHAR_LIMIT,
767
768
  DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED,
769
+ DD_TRACE_NATIVE_SPAN_EVENTS,
768
770
  OTEL_METRICS_EXPORTER,
769
771
  OTEL_PROPAGATORS,
770
772
  OTEL_RESOURCE_ATTRIBUTES,
@@ -977,6 +979,7 @@ class Config {
977
979
  this._setBoolean(env, 'trace.aws.addSpanPointers', DD_TRACE_AWS_ADD_SPAN_POINTERS)
978
980
  this._setString(env, 'trace.dynamoDb.tablePrimaryKeys', DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS)
979
981
  this._setArray(env, 'graphqlErrorExtensions', DD_TRACE_GRAPHQL_ERROR_EXTENSIONS)
982
+ this._setBoolean(env, 'trace.nativeSpanEvents', DD_TRACE_NATIVE_SPAN_EVENTS)
980
983
  this._setValue(
981
984
  env,
982
985
  'vertexai.spanPromptCompletionSampleRate',
@@ -1114,6 +1117,7 @@ class Config {
1114
1117
  this._setString(opts, 'version', options.version || tags.version)
1115
1118
  this._setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled)
1116
1119
  this._setBoolean(opts, 'graphqlErrorExtensions', options.graphqlErrorExtensions)
1120
+ this._setBoolean(opts, 'trace.nativeSpanEvents', options.trace?.nativeSpanEvents)
1117
1121
 
1118
1122
  // For LLMObs, we want the environment variable to take precedence over the options.
1119
1123
  // This is reliant on environment config being set before options.
@@ -234,7 +234,7 @@ class MetricsAggregationClient {
234
234
  this._histograms[name].get(tag).record(value)
235
235
  }
236
236
 
237
- count (name, count, tag, monotonic = false) {
237
+ count (name, count, tag, monotonic = true) {
238
238
  if (typeof tag === 'boolean') {
239
239
  monotonic = tag
240
240
  tag = undefined
@@ -254,8 +254,8 @@ class MetricsAggregationClient {
254
254
  this._gauges[name].set(tag, value)
255
255
  }
256
256
 
257
- increment (name, count = 1, tag, monotonic) {
258
- this.count(name, count, tag, monotonic)
257
+ increment (name, count = 1, tag) {
258
+ this.count(name, count, tag)
259
259
  }
260
260
 
261
261
  decrement (name, count = 1, tag) {
@@ -5,11 +5,22 @@ const { Chunk, MsgpackEncoder } = require('../msgpack')
5
5
  const log = require('../log')
6
6
  const { isTrue } = require('../util')
7
7
  const coalesce = require('koalas')
8
+ const { memoize } = require('../log/utils')
8
9
 
9
10
  const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
10
11
 
11
- function formatSpan (span) {
12
- return normalizeSpan(truncateSpan(span, false))
12
+ function formatSpan (span, config) {
13
+ span = normalizeSpan(truncateSpan(span, false))
14
+ if (span.span_events) {
15
+ // ensure span events are encoded as tags if agent doesn't support native top level span events
16
+ if (!config?.trace?.nativeSpanEvents) {
17
+ span.meta.events = JSON.stringify(span.span_events)
18
+ delete span.span_events
19
+ } else {
20
+ formatSpanEvents(span)
21
+ }
22
+ }
23
+ return span
13
24
  }
14
25
 
15
26
  class AgentEncoder {
@@ -24,6 +35,7 @@ class AgentEncoder {
24
35
  process.env.DD_TRACE_ENCODING_DEBUG,
25
36
  false
26
37
  ))
38
+ this._config = this._writer?._config
27
39
  }
28
40
 
29
41
  count () {
@@ -74,16 +86,18 @@ class AgentEncoder {
74
86
  this._encodeArrayPrefix(bytes, trace)
75
87
 
76
88
  for (let span of trace) {
77
- span = formatSpan(span)
89
+ span = formatSpan(span, this._config)
78
90
  bytes.reserve(1)
79
91
 
80
- if (span.type && span.meta_struct) {
81
- bytes.buffer[bytes.length - 1] = 0x8d
82
- } else if (span.type || span.meta_struct) {
83
- bytes.buffer[bytes.length - 1] = 0x8c
84
- } else {
85
- bytes.buffer[bytes.length - 1] = 0x8b
86
- }
92
+ // this is the original size of the fixed map for span attributes that always exist
93
+ let mapSize = 11
94
+
95
+ // increment the payload map size depending on if some optional fields exist
96
+ if (span.type) mapSize += 1
97
+ if (span.meta_struct) mapSize += 1
98
+ if (span.span_events) mapSize += 1
99
+
100
+ bytes.buffer[bytes.length - 1] = 0x80 + mapSize
87
101
 
88
102
  if (span.type) {
89
103
  this._encodeString(bytes, 'type')
@@ -112,6 +126,10 @@ class AgentEncoder {
112
126
  this._encodeMap(bytes, span.meta)
113
127
  this._encodeString(bytes, 'metrics')
114
128
  this._encodeMap(bytes, span.metrics)
129
+ if (span.span_events) {
130
+ this._encodeString(bytes, 'span_events')
131
+ this._encodeObjectAsArray(bytes, span.span_events, new Set())
132
+ }
115
133
  if (span.meta_struct) {
116
134
  this._encodeString(bytes, 'meta_struct')
117
135
  this._encodeMetaStruct(bytes, span.meta_struct)
@@ -200,6 +218,9 @@ class AgentEncoder {
200
218
  case 'number':
201
219
  this._encodeFloat(bytes, value)
202
220
  break
221
+ case 'boolean':
222
+ this._encodeBool(bytes, value)
223
+ break
203
224
  default:
204
225
  // should not happen
205
226
  }
@@ -258,7 +279,7 @@ class AgentEncoder {
258
279
  this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
259
280
  } else if (value !== null && typeof value === 'object') {
260
281
  this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
261
- } else if (typeof value === 'string' || typeof value === 'number') {
282
+ } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
262
283
  this._encodeValue(bytes, value)
263
284
  }
264
285
  }
@@ -268,7 +289,7 @@ class AgentEncoder {
268
289
  const validKeys = keys.filter(key => {
269
290
  const v = value[key]
270
291
  return typeof v === 'string' ||
271
- typeof v === 'number' ||
292
+ typeof v === 'number' || typeof v === 'boolean' ||
272
293
  (v !== null && typeof v === 'object' && !circularReferencesDetector.has(v))
273
294
  })
274
295
 
@@ -319,4 +340,79 @@ class AgentEncoder {
319
340
  }
320
341
  }
321
342
 
343
+ const memoizedLogDebug = memoize((key, message) => {
344
+ log.debug(message)
345
+ // return something to store in memoize cache
346
+ return true
347
+ })
348
+
349
+ function formatSpanEvents (span) {
350
+ for (const spanEvent of span.span_events) {
351
+ if (spanEvent.attributes) {
352
+ for (const [key, value] of Object.entries(spanEvent.attributes)) {
353
+ const newValue = convertSpanEventAttributeValues(key, value)
354
+ if (newValue !== undefined) {
355
+ spanEvent.attributes[key] = newValue
356
+ } else {
357
+ delete spanEvent.attributes[key] // delete from attributes if undefined
358
+ }
359
+ }
360
+ if (Object.entries(spanEvent.attributes).length === 0) {
361
+ delete spanEvent.attributes
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ function convertSpanEventAttributeValues (key, value, depth = 0) {
368
+ if (typeof value === 'string') {
369
+ return {
370
+ type: 0,
371
+ string_value: value
372
+ }
373
+ } else if (typeof value === 'boolean') {
374
+ return {
375
+ type: 1,
376
+ bool_value: value
377
+ }
378
+ } else if (Number.isInteger(value)) {
379
+ return {
380
+ type: 2,
381
+ int_value: value
382
+ }
383
+ } else if (typeof value === 'number') {
384
+ return {
385
+ type: 3,
386
+ double_value: value
387
+ }
388
+ } else if (Array.isArray(value)) {
389
+ if (depth === 0) {
390
+ const convertedArray = value
391
+ .map((val) => convertSpanEventAttributeValues(key, val, 1))
392
+ .filter((convertedVal) => convertedVal !== undefined)
393
+
394
+ // Only include array_value if there are valid elements
395
+ if (convertedArray.length > 0) {
396
+ return {
397
+ type: 4,
398
+ array_value: convertedArray
399
+ }
400
+ } else {
401
+ // If all elements were unsupported, return undefined
402
+ return undefined
403
+ }
404
+ } else {
405
+ memoizedLogDebug(key, 'Encountered nested array data type for span event v0.4 encoding. ' +
406
+ `Skipping encoding key: ${key}: with value: ${typeof value}.`
407
+ )
408
+ return undefined
409
+ }
410
+ } else {
411
+ memoizedLogDebug(key, 'Encountered unsupported data type for span event v0.4 encoding, key: ' +
412
+ `${key}: with value: ${typeof value}. Skipping encoding of pair.`
413
+ )
414
+ return undefined
415
+ }
416
+ }
417
+
322
418
  module.exports = { AgentEncoder }
@@ -7,7 +7,13 @@ const ARRAY_OF_TWO = 0x92
7
7
  const ARRAY_OF_TWELVE = 0x9c
8
8
 
9
9
  function formatSpan (span) {
10
- return normalizeSpan(truncateSpan(span, false))
10
+ span = normalizeSpan(truncateSpan(span, false))
11
+ // ensure span events are encoded as tags
12
+ if (span.span_events) {
13
+ span.meta.events = JSON.stringify(span.span_events)
14
+ delete span.span_events
15
+ }
16
+ return span
11
17
  }
12
18
 
13
19
  class AgentEncoder extends BaseEncoder {
@@ -24,7 +24,8 @@ class AgentExporter {
24
24
  prioritySampler,
25
25
  lookup,
26
26
  protocolVersion,
27
- headers
27
+ headers,
28
+ config
28
29
  })
29
30
 
30
31
  this._timer = undefined
@@ -10,15 +10,16 @@ const BaseWriter = require('../common/writer')
10
10
  const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'
11
11
 
12
12
  class Writer extends BaseWriter {
13
- constructor ({ prioritySampler, lookup, protocolVersion, headers }) {
13
+ constructor ({ prioritySampler, lookup, protocolVersion, headers, config = {} }) {
14
14
  super(...arguments)
15
15
  const AgentEncoder = getEncoder(protocolVersion)
16
16
 
17
17
  this._prioritySampler = prioritySampler
18
18
  this._lookup = lookup
19
19
  this._protocolVersion = protocolVersion
20
- this._encoder = new AgentEncoder(this)
21
20
  this._headers = headers
21
+ this._config = config
22
+ this._encoder = new AgentEncoder(this)
22
23
  }
23
24
 
24
25
  _sendPayload (data, count, done) {
@@ -68,7 +68,7 @@ function setSingleSpanIngestionTags (span, options) {
68
68
  addTag({}, span.metrics, SPAN_SAMPLING_MAX_PER_SECOND, options.maxPerSecond)
69
69
  }
70
70
 
71
- function extractSpanLinks (trace, span) {
71
+ function extractSpanLinks (formattedSpan, span) {
72
72
  const links = []
73
73
  if (span._links) {
74
74
  for (const link of span._links) {
@@ -87,10 +87,10 @@ function extractSpanLinks (trace, span) {
87
87
  links.push(formattedLink)
88
88
  }
89
89
  }
90
- if (links.length > 0) { trace.meta['_dd.span_links'] = JSON.stringify(links) }
90
+ if (links.length > 0) { formattedSpan.meta['_dd.span_links'] = JSON.stringify(links) }
91
91
  }
92
92
 
93
- function extractSpanEvents (trace, span) {
93
+ function extractSpanEvents (formattedSpan, span) {
94
94
  const events = []
95
95
  if (span._events) {
96
96
  for (const event of span._events) {
@@ -103,10 +103,12 @@ function extractSpanEvents (trace, span) {
103
103
  events.push(formattedEvent)
104
104
  }
105
105
  }
106
- if (events.length > 0) { trace.meta.events = JSON.stringify(events) }
106
+ if (events.length > 0) {
107
+ formattedSpan.span_events = events
108
+ }
107
109
  }
108
110
 
109
- function extractTags (trace, span) {
111
+ function extractTags (formattedSpan, span) {
110
112
  const context = span.context()
111
113
  const origin = context._trace.origin
112
114
  const tags = context._tags
@@ -114,7 +116,7 @@ function extractTags (trace, span) {
114
116
  const priority = context._sampling.priority
115
117
 
116
118
  if (tags['span.kind'] && tags['span.kind'] !== 'internal') {
117
- addTag({}, trace.metrics, MEASURED, 1)
119
+ addTag({}, formattedSpan.metrics, MEASURED, 1)
118
120
  }
119
121
 
120
122
  const tracerService = span.tracer()._service.toLowerCase()
@@ -129,22 +131,22 @@ function extractTags (trace, span) {
129
131
  case 'service.name':
130
132
  case 'span.type':
131
133
  case 'resource.name':
132
- addTag(trace, {}, map[tag], tags[tag])
134
+ addTag(formattedSpan, {}, map[tag], tags[tag])
133
135
  break
134
136
  // HACK: remove when Datadog supports numeric status code
135
137
  case 'http.status_code':
136
- addTag(trace.meta, {}, tag, tags[tag] && String(tags[tag]))
138
+ addTag(formattedSpan.meta, {}, tag, tags[tag] && String(tags[tag]))
137
139
  break
138
140
  case 'analytics.event':
139
- addTag({}, trace.metrics, ANALYTICS, tags[tag] === undefined || tags[tag] ? 1 : 0)
141
+ addTag({}, formattedSpan.metrics, ANALYTICS, tags[tag] === undefined || tags[tag] ? 1 : 0)
140
142
  break
141
143
  case HOSTNAME_KEY:
142
144
  case MEASURED:
143
- addTag({}, trace.metrics, tag, tags[tag] === undefined || tags[tag] ? 1 : 0)
145
+ addTag({}, formattedSpan.metrics, tag, tags[tag] === undefined || tags[tag] ? 1 : 0)
144
146
  break
145
147
  case 'error':
146
148
  if (context._name !== 'fs.operation') {
147
- extractError(trace, tags[tag])
149
+ extractError(formattedSpan, tags[tag])
148
150
  }
149
151
  break
150
152
  case ERROR_TYPE:
@@ -152,60 +154,60 @@ function extractTags (trace, span) {
152
154
  case ERROR_STACK:
153
155
  // HACK: remove when implemented in the backend
154
156
  if (context._name !== 'fs.operation') {
155
- // HACK: to ensure otel.recordException does not influence trace.error
157
+ // HACK: to ensure otel.recordException does not influence formattedSpan.error
156
158
  if (tags.setTraceError) {
157
- trace.error = 1
159
+ formattedSpan.error = 1
158
160
  }
159
161
  } else {
160
162
  break
161
163
  }
162
164
  default: // eslint-disable-line no-fallthrough
163
- addTag(trace.meta, trace.metrics, tag, tags[tag])
165
+ addTag(formattedSpan.meta, formattedSpan.metrics, tag, tags[tag])
164
166
  }
165
167
  }
166
- setSingleSpanIngestionTags(trace, context._spanSampling)
168
+ setSingleSpanIngestionTags(formattedSpan, context._spanSampling)
167
169
 
168
- addTag(trace.meta, trace.metrics, 'language', 'javascript')
169
- addTag(trace.meta, trace.metrics, PROCESS_ID, process.pid)
170
- addTag(trace.meta, trace.metrics, SAMPLING_PRIORITY_KEY, priority)
171
- addTag(trace.meta, trace.metrics, ORIGIN_KEY, origin)
172
- addTag(trace.meta, trace.metrics, HOSTNAME_KEY, hostname)
170
+ addTag(formattedSpan.meta, formattedSpan.metrics, 'language', 'javascript')
171
+ addTag(formattedSpan.meta, formattedSpan.metrics, PROCESS_ID, process.pid)
172
+ addTag(formattedSpan.meta, formattedSpan.metrics, SAMPLING_PRIORITY_KEY, priority)
173
+ addTag(formattedSpan.meta, formattedSpan.metrics, ORIGIN_KEY, origin)
174
+ addTag(formattedSpan.meta, formattedSpan.metrics, HOSTNAME_KEY, hostname)
173
175
  }
174
176
 
175
- function extractRootTags (trace, span) {
177
+ function extractRootTags (formattedSpan, span) {
176
178
  const context = span.context()
177
179
  const isLocalRoot = span === context._trace.started[0]
178
180
  const parentId = context._parentId
179
181
 
180
182
  if (!isLocalRoot || (parentId && parentId.toString(10) !== '0')) return
181
183
 
182
- addTag({}, trace.metrics, SAMPLING_RULE_DECISION, context._trace[SAMPLING_RULE_DECISION])
183
- addTag({}, trace.metrics, SAMPLING_LIMIT_DECISION, context._trace[SAMPLING_LIMIT_DECISION])
184
- addTag({}, trace.metrics, SAMPLING_AGENT_DECISION, context._trace[SAMPLING_AGENT_DECISION])
185
- addTag({}, trace.metrics, TOP_LEVEL_KEY, 1)
184
+ addTag({}, formattedSpan.metrics, SAMPLING_RULE_DECISION, context._trace[SAMPLING_RULE_DECISION])
185
+ addTag({}, formattedSpan.metrics, SAMPLING_LIMIT_DECISION, context._trace[SAMPLING_LIMIT_DECISION])
186
+ addTag({}, formattedSpan.metrics, SAMPLING_AGENT_DECISION, context._trace[SAMPLING_AGENT_DECISION])
187
+ addTag({}, formattedSpan.metrics, TOP_LEVEL_KEY, 1)
186
188
  }
187
189
 
188
- function extractChunkTags (trace, span) {
190
+ function extractChunkTags (formattedSpan, span) {
189
191
  const context = span.context()
190
192
  const isLocalRoot = span === context._trace.started[0]
191
193
 
192
194
  if (!isLocalRoot) return
193
195
 
194
196
  for (const key in context._trace.tags) {
195
- addTag(trace.meta, trace.metrics, key, context._trace.tags[key])
197
+ addTag(formattedSpan.meta, formattedSpan.metrics, key, context._trace.tags[key])
196
198
  }
197
199
  }
198
200
 
199
- function extractError (trace, error) {
201
+ function extractError (formattedSpan, error) {
200
202
  if (!error) return
201
203
 
202
- trace.error = 1
204
+ formattedSpan.error = 1
203
205
 
204
206
  if (isError(error)) {
205
207
  // AggregateError only has a code and no message.
206
- addTag(trace.meta, trace.metrics, ERROR_MESSAGE, error.message || error.code)
207
- addTag(trace.meta, trace.metrics, ERROR_TYPE, error.name)
208
- addTag(trace.meta, trace.metrics, ERROR_STACK, error.stack)
208
+ addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_MESSAGE, error.message || error.code)
209
+ addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_TYPE, error.name)
210
+ addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_STACK, error.stack)
209
211
  }
210
212
  }
211
213
 
@@ -105,12 +105,12 @@ class LLMObs extends NoopLLMObs {
105
105
 
106
106
  if (fn.length > 1) {
107
107
  return this._tracer.trace(name, spanOptions, (span, cb) =>
108
- this._activate(span, { kind, options: llmobsOptions }, () => fn(span, cb))
108
+ this._activate(span, { kind, ...llmobsOptions }, () => fn(span, cb))
109
109
  )
110
110
  }
111
111
 
112
112
  return this._tracer.trace(name, spanOptions, span =>
113
- this._activate(span, { kind, options: llmobsOptions }, () => fn(span))
113
+ this._activate(span, { kind, ...llmobsOptions }, () => fn(span))
114
114
  )
115
115
  }
116
116
 
@@ -166,7 +166,7 @@ class LLMObs extends NoopLLMObs {
166
166
  }
167
167
 
168
168
  try {
169
- const result = llmobs._activate(span, { kind, options: llmobsOptions }, () => fn.apply(this, fnArgs))
169
+ const result = llmobs._activate(span, { kind, ...llmobsOptions }, () => fn.apply(this, fnArgs))
170
170
 
171
171
  if (result && typeof result.then === 'function') {
172
172
  return result.then(
@@ -6,19 +6,7 @@ const { isTrue } = require('../util')
6
6
  const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
7
7
  const logWriter = require('./writer')
8
8
  const { Log } = require('./log')
9
-
10
- const memoize = func => {
11
- const cache = {}
12
- const memoized = function (key) {
13
- if (!cache[key]) {
14
- cache[key] = func.apply(this, arguments)
15
- }
16
-
17
- return cache[key]
18
- }
19
-
20
- return memoized
21
- }
9
+ const { memoize } = require('./utils')
22
10
 
23
11
  const config = {
24
12
  enabled: false,
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const memoize = func => {
4
+ const cache = {}
5
+ const memoized = function (key) {
6
+ if (!cache[key]) {
7
+ cache[key] = func.apply(this, arguments)
8
+ }
9
+
10
+ return cache[key]
11
+ }
12
+
13
+ return memoized
14
+ }
15
+
16
+ module.exports = { memoize }
@@ -111,11 +111,11 @@ const runtimeMetrics = module.exports = {
111
111
  },
112
112
 
113
113
  increment (name, tag, monotonic) {
114
- client && client.increment(name, 1, tag, monotonic)
114
+ this.count(name, 1, tag, monotonic)
115
115
  },
116
116
 
117
117
  decrement (name, tag) {
118
- client && client.decrement(name, 1, tag)
118
+ this.count(name, -1, tag)
119
119
  }
120
120
  }
121
121
 
@@ -211,7 +211,7 @@ function captureGCMetrics () {
211
211
  histogram('runtime.node.gc.pause', pauseAll)
212
212
 
213
213
  for (const type in pause) {
214
- histogram('runtime.node.gc.pause.by.type', pause[type], [`gc_type:${type}`])
214
+ histogram('runtime.node.gc.pause.by.type', pause[type], `gc_type:${type}`)
215
215
  }
216
216
 
217
217
  gcProfiler.start()
@@ -265,7 +265,7 @@ function captureNativeMetrics () {
265
265
  if (type === 'all') {
266
266
  histogram('runtime.node.gc.pause', stats.gc[type])
267
267
  } else {
268
- histogram('runtime.node.gc.pause.by.type', stats.gc[type], [`gc_type:${type}`])
268
+ histogram('runtime.node.gc.pause.by.type', stats.gc[type], `gc_type:${type}`)
269
269
  }
270
270
  })
271
271
 
@@ -279,16 +279,15 @@ function captureNativeMetrics () {
279
279
  }
280
280
  }
281
281
 
282
- function histogram (name, stats, tags) {
283
- tags = tags ? [].concat(tags) : []
284
-
285
- if (tags.length > 0) {
286
- for (const tag of tags) {
287
- client.histogram(name, stats, tag)
288
- }
289
- } else {
290
- client.histogram(name, stats)
291
- }
282
+ function histogram (name, stats, tag) {
283
+ client.gauge(`${name}.min`, stats.min, tag)
284
+ client.gauge(`${name}.max`, stats.max, tag)
285
+ client.increment(`${name}.sum`, stats.sum, tag)
286
+ client.increment(`${name}.total`, stats.sum, tag)
287
+ client.gauge(`${name}.avg`, stats.avg, tag)
288
+ client.increment(`${name}.count`, stats.count, tag)
289
+ client.gauge(`${name}.median`, stats.median, tag)
290
+ client.gauge(`${name}.95percentile`, stats.p95, tag)
292
291
  }
293
292
 
294
293
  function startGCObserver () {