dd-trace 5.25.0 → 5.27.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 (74) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/index.d.ts +17 -8
  3. package/init.js +60 -47
  4. package/package.json +5 -2
  5. package/packages/datadog-core/index.js +1 -3
  6. package/packages/datadog-core/src/storage.js +21 -0
  7. package/packages/datadog-instrumentations/src/express.js +1 -1
  8. package/packages/datadog-instrumentations/src/handlebars.js +40 -0
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -0
  10. package/packages/datadog-instrumentations/src/jest.js +6 -2
  11. package/packages/datadog-instrumentations/src/langchain.js +77 -0
  12. package/packages/datadog-instrumentations/src/next.js +19 -7
  13. package/packages/datadog-instrumentations/src/pug.js +23 -0
  14. package/packages/datadog-instrumentations/src/router.js +2 -3
  15. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +7 -6
  17. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +34 -0
  18. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +8 -8
  19. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +59 -45
  20. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  21. package/packages/datadog-plugin-http/src/client.js +42 -1
  22. package/packages/datadog-plugin-http2/src/client.js +26 -1
  23. package/packages/datadog-plugin-langchain/src/handlers/chain.js +50 -0
  24. package/packages/datadog-plugin-langchain/src/handlers/default.js +53 -0
  25. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +63 -0
  26. package/packages/datadog-plugin-langchain/src/handlers/language_models/chat_model.js +99 -0
  27. package/packages/datadog-plugin-langchain/src/handlers/language_models/index.js +48 -0
  28. package/packages/datadog-plugin-langchain/src/handlers/language_models/llm.js +57 -0
  29. package/packages/datadog-plugin-langchain/src/index.js +89 -0
  30. package/packages/datadog-plugin-langchain/src/tokens.js +35 -0
  31. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  32. package/packages/datadog-plugin-moleculer/src/server.js +0 -1
  33. package/packages/dd-trace/src/appsec/api_security_sampler.js +50 -27
  34. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  35. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +33 -16
  36. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +18 -0
  37. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -2
  38. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  39. package/packages/dd-trace/src/appsec/index.js +6 -6
  40. package/packages/dd-trace/src/appsec/recommended.json +353 -155
  41. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -1
  42. package/packages/dd-trace/src/appsec/remote_config/index.js +0 -7
  43. package/packages/dd-trace/src/appsec/reporter.js +1 -0
  44. package/packages/dd-trace/src/appsec/sdk/utils.js +21 -2
  45. package/packages/dd-trace/src/config.js +21 -4
  46. package/packages/dd-trace/src/constants.js +6 -1
  47. package/packages/dd-trace/src/crashtracking/crashtracker.js +98 -0
  48. package/packages/dd-trace/src/crashtracking/index.js +15 -0
  49. package/packages/dd-trace/src/crashtracking/noop.js +8 -0
  50. package/packages/dd-trace/src/llmobs/sdk.js +1 -1
  51. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  52. package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -0
  53. package/packages/dd-trace/src/log/index.js +10 -13
  54. package/packages/dd-trace/src/log/log.js +52 -0
  55. package/packages/dd-trace/src/log/writer.js +50 -19
  56. package/packages/dd-trace/src/noop/span.js +1 -0
  57. package/packages/dd-trace/src/opentelemetry/span.js +15 -0
  58. package/packages/dd-trace/src/opentracing/propagation/text_map.js +35 -22
  59. package/packages/dd-trace/src/opentracing/span.js +14 -0
  60. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  61. package/packages/dd-trace/src/plugins/index.js +3 -0
  62. package/packages/dd-trace/src/plugins/tracing.js +2 -2
  63. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +121 -0
  64. package/packages/dd-trace/src/plugins/util/ip_extractor.js +0 -1
  65. package/packages/dd-trace/src/plugins/util/web.js +39 -11
  66. package/packages/dd-trace/src/profiling/exporters/agent.js +42 -5
  67. package/packages/dd-trace/src/profiling/profiler.js +5 -2
  68. package/packages/dd-trace/src/proxy.js +5 -0
  69. package/packages/dd-trace/src/telemetry/logs/index.js +16 -11
  70. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -8
  71. package/packages/dd-trace/src/telemetry/metrics.js +6 -1
  72. package/packages/dd-trace/src/util.js +16 -1
  73. package/version.js +4 -2
  74. /package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/{code-injection-sensitive-analyzer.js → tainted-range-based-sensitive-analyzer.js} +0 -0
@@ -11,7 +11,7 @@ module.exports = {
11
11
  ASM_CUSTOM_RULES: 1n << 8n,
12
12
  ASM_CUSTOM_BLOCKING_RESPONSE: 1n << 9n,
13
13
  ASM_TRUSTED_IPS: 1n << 10n,
14
- ASM_API_SECURITY_SAMPLE_RATE: 1n << 11n,
14
+ ASM_API_SECURITY_SAMPLE_RATE: 1n << 11n, // deprecated
15
15
  APM_TRACING_SAMPLE_RATE: 1n << 12n,
16
16
  APM_TRACING_LOGS_INJECTION: 1n << 13n,
17
17
  APM_TRACING_HTTP_HEADER_TAGS: 1n << 14n,
@@ -4,7 +4,6 @@ const Activation = require('../activation')
4
4
 
5
5
  const RemoteConfigManager = require('./manager')
6
6
  const RemoteConfigCapabilities = require('./capabilities')
7
- const apiSecuritySampler = require('../api_security_sampler')
8
7
 
9
8
  let rc
10
9
 
@@ -24,18 +23,12 @@ function enable (config, appsec) {
24
23
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_ACTIVATION, true)
25
24
  }
26
25
 
27
- if (config.appsec.apiSecurity?.enabled) {
28
- rc.updateCapabilities(RemoteConfigCapabilities.ASM_API_SECURITY_SAMPLE_RATE, true)
29
- }
30
-
31
26
  rc.setProductHandler('ASM_FEATURES', (action, rcConfig) => {
32
27
  if (!rcConfig) return
33
28
 
34
29
  if (activation === Activation.ONECLICK) {
35
30
  enableOrDisableAppsec(action, rcConfig, config, appsec)
36
31
  }
37
-
38
- apiSecuritySampler.setRequestSampling(rcConfig.api_security?.request_sample_rate)
39
32
  })
40
33
  }
41
34
 
@@ -32,6 +32,7 @@ const contentHeaderList = [
32
32
 
33
33
  const EVENT_HEADERS_MAP = mapHeaderAndTags([
34
34
  ...ipHeaderList,
35
+ 'x-forwarded',
35
36
  'forwarded',
36
37
  'via',
37
38
  ...contentHeaderList,
@@ -1,8 +1,27 @@
1
1
  'use strict'
2
2
 
3
3
  function getRootSpan (tracer) {
4
- const span = tracer.scope().active()
5
- return span && span.context()._trace.started[0]
4
+ let span = tracer.scope().active()
5
+ if (!span) return
6
+
7
+ const context = span.context()
8
+ const started = context._trace.started
9
+
10
+ let parentId = context._parentId
11
+ while (parentId) {
12
+ const parent = started.find(s => s.context()._spanId === parentId)
13
+ const pContext = parent?.context()
14
+
15
+ if (!pContext) break
16
+
17
+ parentId = pContext._parentId
18
+
19
+ if (!pContext._tags?._inferred_span) {
20
+ span = parent
21
+ }
22
+ }
23
+
24
+ return span
6
25
  }
7
26
 
8
27
  module.exports = {
@@ -444,7 +444,7 @@ class Config {
444
444
  const defaults = setHiddenProperty(this, '_defaults', {})
445
445
 
446
446
  this._setValue(defaults, 'appsec.apiSecurity.enabled', true)
447
- this._setValue(defaults, 'appsec.apiSecurity.requestSampling', 0.1)
447
+ this._setValue(defaults, 'appsec.apiSecurity.sampleDelay', 30)
448
448
  this._setValue(defaults, 'appsec.blockedTemplateGraphql', undefined)
449
449
  this._setValue(defaults, 'appsec.blockedTemplateHtml', undefined)
450
450
  this._setValue(defaults, 'appsec.blockedTemplateJson', undefined)
@@ -467,6 +467,7 @@ class Config {
467
467
  this._setValue(defaults, 'ciVisibilityTestSessionName', '')
468
468
  this._setValue(defaults, 'clientIpEnabled', false)
469
469
  this._setValue(defaults, 'clientIpHeader', null)
470
+ this._setValue(defaults, 'crashtracking.enabled', false)
470
471
  this._setValue(defaults, 'codeOriginForSpans.enabled', false)
471
472
  this._setValue(defaults, 'dbmPropagationMode', 'disabled')
472
473
  this._setValue(defaults, 'dogstatsd.hostname', '127.0.0.1')
@@ -504,6 +505,8 @@ class Config {
504
505
  this._setValue(defaults, 'isGitUploadEnabled', false)
505
506
  this._setValue(defaults, 'isIntelligentTestRunnerEnabled', false)
506
507
  this._setValue(defaults, 'isManualApiEnabled', false)
508
+ this._setValue(defaults, 'langchain.spanCharLimit', 128)
509
+ this._setValue(defaults, 'langchain.spanPromptCompletionSampleRate', 1.0)
507
510
  this._setValue(defaults, 'llmobs.agentlessEnabled', false)
508
511
  this._setValue(defaults, 'llmobs.enabled', false)
509
512
  this._setValue(defaults, 'llmobs.mlApp', undefined)
@@ -513,6 +516,7 @@ class Config {
513
516
  this._setValue(defaults, 'isTestDynamicInstrumentationEnabled', false)
514
517
  this._setValue(defaults, 'logInjection', false)
515
518
  this._setValue(defaults, 'lookup', undefined)
519
+ this._setValue(defaults, 'inferredProxyServicesEnabled', false)
516
520
  this._setValue(defaults, 'memcachedCommandEnabled', false)
517
521
  this._setValue(defaults, 'openAiLogsEnabled', false)
518
522
  this._setValue(defaults, 'openaiSpanCharLimit', 128)
@@ -569,7 +573,7 @@ class Config {
569
573
  AWS_LAMBDA_FUNCTION_NAME,
570
574
  DD_AGENT_HOST,
571
575
  DD_API_SECURITY_ENABLED,
572
- DD_API_SECURITY_REQUEST_SAMPLE_RATE,
576
+ DD_API_SECURITY_SAMPLE_DELAY,
573
577
  DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING,
574
578
  DD_APPSEC_ENABLED,
575
579
  DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON,
@@ -585,6 +589,7 @@ class Config {
585
589
  DD_APPSEC_RASP_ENABLED,
586
590
  DD_APPSEC_TRACE_RATE_LIMIT,
587
591
  DD_APPSEC_WAF_TIMEOUT,
592
+ DD_CRASHTRACKING_ENABLED,
588
593
  DD_CODE_ORIGIN_FOR_SPANS_ENABLED,
589
594
  DD_DATA_STREAMS_ENABLED,
590
595
  DD_DBM_PROPAGATION_MODE,
@@ -612,6 +617,8 @@ class Config {
612
617
  DD_INSTRUMENTATION_TELEMETRY_ENABLED,
613
618
  DD_INSTRUMENTATION_CONFIG_ID,
614
619
  DD_LOGS_INJECTION,
620
+ DD_LANGCHAIN_SPAN_CHAR_LIMIT,
621
+ DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE,
615
622
  DD_LLMOBS_AGENTLESS_ENABLED,
616
623
  DD_LLMOBS_ENABLED,
617
624
  DD_LLMOBS_ML_APP,
@@ -675,6 +682,7 @@ class Config {
675
682
  DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH,
676
683
  DD_TRACING_ENABLED,
677
684
  DD_VERSION,
685
+ DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED,
678
686
  OTEL_METRICS_EXPORTER,
679
687
  OTEL_PROPAGATORS,
680
688
  OTEL_RESOURCE_ATTRIBUTES,
@@ -696,7 +704,7 @@ class Config {
696
704
  DD_API_SECURITY_ENABLED && isTrue(DD_API_SECURITY_ENABLED),
697
705
  DD_EXPERIMENTAL_API_SECURITY_ENABLED && isTrue(DD_EXPERIMENTAL_API_SECURITY_ENABLED)
698
706
  ))
699
- this._setUnit(env, 'appsec.apiSecurity.requestSampling', DD_API_SECURITY_REQUEST_SAMPLE_RATE)
707
+ this._setValue(env, 'appsec.apiSecurity.sampleDelay', maybeFloat(DD_API_SECURITY_SAMPLE_DELAY))
700
708
  this._setValue(env, 'appsec.blockedTemplateGraphql', maybeFile(DD_APPSEC_GRAPHQL_BLOCKED_TEMPLATE_JSON))
701
709
  this._setValue(env, 'appsec.blockedTemplateHtml', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML))
702
710
  this._envUnprocessed['appsec.blockedTemplateHtml'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML
@@ -728,6 +736,7 @@ class Config {
728
736
  this._setValue(env, 'baggageMaxItems', DD_TRACE_BAGGAGE_MAX_ITEMS)
729
737
  this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
730
738
  this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
739
+ this._setBoolean(env, 'crashtracking.enabled', DD_CRASHTRACKING_ENABLED)
731
740
  this._setBoolean(env, 'codeOriginForSpans.enabled', DD_CODE_ORIGIN_FOR_SPANS_ENABLED)
732
741
  this._setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE)
733
742
  this._setString(env, 'dogstatsd.hostname', DD_DOGSTATSD_HOSTNAME)
@@ -766,6 +775,10 @@ class Config {
766
775
  this._setArray(env, 'injectionEnabled', DD_INJECTION_ENABLED)
767
776
  this._setBoolean(env, 'isAzureFunction', getIsAzureFunction())
768
777
  this._setBoolean(env, 'isGCPFunction', getIsGCPFunction())
778
+ this._setValue(env, 'langchain.spanCharLimit', maybeInt(DD_LANGCHAIN_SPAN_CHAR_LIMIT))
779
+ this._setValue(
780
+ env, 'langchain.spanPromptCompletionSampleRate', maybeFloat(DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE)
781
+ )
769
782
  this._setBoolean(env, 'legacyBaggageEnabled', DD_TRACE_LEGACY_BAGGAGE_ENABLED)
770
783
  this._setBoolean(env, 'llmobs.agentlessEnabled', DD_LLMOBS_AGENTLESS_ENABLED)
771
784
  this._setBoolean(env, 'llmobs.enabled', DD_LLMOBS_ENABLED)
@@ -862,6 +875,7 @@ class Config {
862
875
  : !!OTEL_PROPAGATORS)
863
876
  this._setBoolean(env, 'tracing', DD_TRACING_ENABLED)
864
877
  this._setString(env, 'version', DD_VERSION || tags.version)
878
+ this._setBoolean(env, 'inferredProxyServicesEnabled', DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED)
865
879
  }
866
880
 
867
881
  _applyOptions (options) {
@@ -874,7 +888,6 @@ class Config {
874
888
  tagger.add(tags, options.tags)
875
889
 
876
890
  this._setBoolean(opts, 'appsec.apiSecurity.enabled', options.appsec.apiSecurity?.enabled)
877
- this._setUnit(opts, 'appsec.apiSecurity.requestSampling', options.appsec.apiSecurity?.requestSampling)
878
891
  this._setValue(opts, 'appsec.blockedTemplateGraphql', maybeFile(options.appsec.blockedTemplateGraphql))
879
892
  this._setValue(opts, 'appsec.blockedTemplateHtml', maybeFile(options.appsec.blockedTemplateHtml))
880
893
  this._optsUnprocessed['appsec.blockedTemplateHtml'] = options.appsec.blockedTemplateHtml
@@ -980,6 +993,7 @@ class Config {
980
993
  this._setBoolean(opts, 'traceId128BitGenerationEnabled', options.traceId128BitGenerationEnabled)
981
994
  this._setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled)
982
995
  this._setString(opts, 'version', options.version || tags.version)
996
+ this._setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled)
983
997
 
984
998
  // For LLMObs, we want the environment variable to take precedence over the options.
985
999
  // This is reliant on environment config being set before options.
@@ -1134,6 +1148,9 @@ class Config {
1134
1148
  if (iastEnabled || ['auto', 'true'].includes(profilingEnabled) || injectionIncludesProfiler) {
1135
1149
  this._setBoolean(calc, 'telemetry.logCollection', true)
1136
1150
  }
1151
+ if (this._env.injectionEnabled?.length > 0) {
1152
+ this._setBoolean(calc, 'crashtracking.enabled', true)
1153
+ }
1137
1154
  }
1138
1155
 
1139
1156
  _applyRemote (options) {
@@ -46,5 +46,10 @@ module.exports = {
46
46
  SCHEMA_OPERATION: 'schema.operation',
47
47
  SCHEMA_NAME: 'schema.name',
48
48
  GRPC_CLIENT_ERROR_STATUSES: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
49
- GRPC_SERVER_ERROR_STATUSES: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
49
+ GRPC_SERVER_ERROR_STATUSES: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
50
+ S3_PTR_KIND: 'aws.s3.object',
51
+ SPAN_POINTER_DIRECTION: Object.freeze({
52
+ UPSTREAM: 'u',
53
+ DOWNSTREAM: 'd'
54
+ })
50
55
  }
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ // Load binding first to not import other modules if it throws
4
+ const libdatadog = require('@datadog/libdatadog')
5
+ const binding = libdatadog.load('crashtracker')
6
+
7
+ const log = require('../log')
8
+ const { URL } = require('url')
9
+ const pkg = require('../../../../package.json')
10
+
11
+ class Crashtracker {
12
+ constructor () {
13
+ this._started = false
14
+ }
15
+
16
+ configure (config) {
17
+ if (!this._started) return
18
+
19
+ try {
20
+ binding.updateConfig(this._getConfig(config))
21
+ binding.updateMetadata(this._getMetadata(config))
22
+ } catch (e) {
23
+ log.error(e)
24
+ }
25
+ }
26
+
27
+ start (config) {
28
+ if (this._started) return this.configure(config)
29
+
30
+ this._started = true
31
+
32
+ try {
33
+ binding.init(
34
+ this._getConfig(config),
35
+ this._getReceiverConfig(config),
36
+ this._getMetadata(config)
37
+ )
38
+ } catch (e) {
39
+ log.error(e)
40
+ }
41
+ }
42
+
43
+ // TODO: Send only configured values when defaults are fixed.
44
+ _getConfig (config) {
45
+ const { hostname = '127.0.0.1', port = 8126 } = config
46
+ const url = config.url || new URL(`http://${hostname}:${port}`)
47
+
48
+ return {
49
+ additional_files: [],
50
+ create_alt_stack: true,
51
+ use_alt_stack: true,
52
+ endpoint: {
53
+ // TODO: Use the string directly when deserialization is fixed.
54
+ url: {
55
+ scheme: url.protocol.slice(0, -1),
56
+ authority: url.protocol === 'unix:'
57
+ ? Buffer.from(url.pathname).toString('hex')
58
+ : url.host,
59
+ path_and_query: ''
60
+ },
61
+ timeout_ms: 3000
62
+ },
63
+ timeout_ms: 5000,
64
+ // TODO: Use `EnabledWithSymbolsInReceiver` instead for Linux when fixed.
65
+ resolve_frames: 'EnabledWithInprocessSymbols'
66
+ }
67
+ }
68
+
69
+ _getMetadata (config) {
70
+ const tags = Object.keys(config.tags).map(key => `${key}:${config.tags[key]}`)
71
+
72
+ return {
73
+ library_name: pkg.name,
74
+ library_version: pkg.version,
75
+ family: 'nodejs',
76
+ tags: [
77
+ ...tags,
78
+ 'is_crash:true',
79
+ 'language:javascript',
80
+ `library_version:${pkg.version}`,
81
+ 'runtime:nodejs',
82
+ 'severity:crash'
83
+ ]
84
+ }
85
+ }
86
+
87
+ _getReceiverConfig () {
88
+ return {
89
+ args: [],
90
+ env: [],
91
+ path_to_receiver_binary: libdatadog.find('crashtracker-receiver', true),
92
+ stderr_filename: null,
93
+ stdout_filename: null
94
+ }
95
+ }
96
+ }
97
+
98
+ module.exports = new Crashtracker()
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ const { isMainThread } = require('worker_threads')
4
+ const log = require('../log')
5
+
6
+ if (isMainThread) {
7
+ try {
8
+ module.exports = require('./crashtracker')
9
+ } catch (e) {
10
+ log.warn(e.message)
11
+ module.exports = require('./noop')
12
+ }
13
+ } else {
14
+ module.exports = require('./noop')
15
+ }
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ class NoopCrashtracker {
4
+ configure () {}
5
+ start () {}
6
+ }
7
+
8
+ module.exports = new NoopCrashtracker()
@@ -291,7 +291,7 @@ class LLMObs extends NoopLLMObs {
291
291
  }
292
292
 
293
293
  const evaluationTags = {
294
- 'dd-trace.version': tracerVersion,
294
+ 'ddtrace.version': tracerVersion,
295
295
  ml_app: mlApp
296
296
  }
297
297
 
@@ -179,7 +179,7 @@ class LLMObsSpanProcessor {
179
179
  service: this._config.service,
180
180
  source: 'integration',
181
181
  ml_app: mlApp,
182
- 'dd-trace.version': tracerVersion,
182
+ 'ddtrace.version': tracerVersion,
183
183
  error: Number(!!error) || 0,
184
184
  language: 'javascript'
185
185
  }
@@ -6,6 +6,8 @@ const { DROPPED_IO_COLLECTION_ERROR } = require('../../constants/tags')
6
6
  const BaseWriter = require('../base')
7
7
  const logger = require('../../../log')
8
8
 
9
+ const tracerVersion = require('../../../../../../package.json').version
10
+
9
11
  class LLMObsSpanWriter extends BaseWriter {
10
12
  constructor (options) {
11
13
  super({
@@ -32,6 +34,7 @@ class LLMObsSpanWriter extends BaseWriter {
32
34
  makePayload (events) {
33
35
  return {
34
36
  '_dd.stage': 'raw',
37
+ '_dd.tracer_version': tracerVersion,
35
38
  event_type: this._eventType,
36
39
  spans: events
37
40
  }
@@ -4,6 +4,7 @@ const coalesce = require('koalas')
4
4
  const { isTrue } = require('../util')
5
5
  const { debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
6
6
  const logWriter = require('./writer')
7
+ const { Log } = require('./log')
7
8
 
8
9
  const memoize = func => {
9
10
  const cache = {}
@@ -18,10 +19,6 @@ const memoize = func => {
18
19
  return memoized
19
20
  }
20
21
 
21
- function processMsg (msg) {
22
- return typeof msg === 'function' ? msg() : msg
23
- }
24
-
25
22
  const config = {
26
23
  enabled: false,
27
24
  logger: undefined,
@@ -52,37 +49,37 @@ const log = {
52
49
  reset () {
53
50
  logWriter.reset()
54
51
  this._deprecate = memoize((code, message) => {
55
- errorChannel.publish(message)
52
+ errorChannel.publish(Log.parse(message))
56
53
  return true
57
54
  })
58
55
 
59
56
  return this
60
57
  },
61
58
 
62
- debug (message) {
59
+ debug (...args) {
63
60
  if (debugChannel.hasSubscribers) {
64
- debugChannel.publish(processMsg(message))
61
+ debugChannel.publish(Log.parse(...args))
65
62
  }
66
63
  return this
67
64
  },
68
65
 
69
- info (message) {
66
+ info (...args) {
70
67
  if (infoChannel.hasSubscribers) {
71
- infoChannel.publish(processMsg(message))
68
+ infoChannel.publish(Log.parse(...args))
72
69
  }
73
70
  return this
74
71
  },
75
72
 
76
- warn (message) {
73
+ warn (...args) {
77
74
  if (warnChannel.hasSubscribers) {
78
- warnChannel.publish(processMsg(message))
75
+ warnChannel.publish(Log.parse(...args))
79
76
  }
80
77
  return this
81
78
  },
82
79
 
83
- error (err) {
80
+ error (...args) {
84
81
  if (errorChannel.hasSubscribers) {
85
- errorChannel.publish(processMsg(err))
82
+ errorChannel.publish(Log.parse(...args))
86
83
  }
87
84
  return this
88
85
  },
@@ -0,0 +1,52 @@
1
+ 'use strict'
2
+
3
+ const { format } = require('util')
4
+
5
+ class Log {
6
+ constructor (message, args, cause, delegate) {
7
+ this.message = message
8
+ this.args = args
9
+ this.cause = cause
10
+ this.delegate = delegate
11
+ }
12
+
13
+ get formatted () {
14
+ const { message, args } = this
15
+
16
+ let formatted = message
17
+ if (message && args && args.length) {
18
+ formatted = format(message, ...args)
19
+ }
20
+ return formatted
21
+ }
22
+
23
+ static parse (...args) {
24
+ let message, cause, delegate
25
+
26
+ const lastArg = args[args.length - 1]
27
+ if (lastArg && typeof lastArg === 'object' && lastArg.stack) { // lastArg instanceof Error?
28
+ cause = args.pop()
29
+ }
30
+
31
+ const firstArg = args.shift()
32
+ if (firstArg) {
33
+ if (typeof firstArg === 'string') {
34
+ message = firstArg
35
+ } else if (typeof firstArg === 'object') {
36
+ message = String(firstArg.message || firstArg)
37
+ } else if (typeof firstArg === 'function') {
38
+ delegate = firstArg
39
+ } else {
40
+ message = String(firstArg)
41
+ }
42
+ } else if (!cause) {
43
+ message = String(firstArg)
44
+ }
45
+
46
+ return new Log(message, args, cause, delegate)
47
+ }
48
+ }
49
+
50
+ module.exports = {
51
+ Log
52
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { storage } = require('../../../datadog-core')
4
4
  const { LogChannel } = require('./channels')
5
+ const { Log } = require('./log')
5
6
  const defaultLogger = {
6
7
  debug: msg => console.debug(msg), /* eslint-disable-line no-console */
7
8
  info: msg => console.info(msg), /* eslint-disable-line no-console */
@@ -22,7 +23,7 @@ function withNoop (fn) {
22
23
  }
23
24
 
24
25
  function unsubscribeAll () {
25
- logChannel.unsubscribe({ debug, info, warn, error })
26
+ logChannel.unsubscribe({ debug: onDebug, info: onInfo, warn: onWarn, error: onError })
26
27
  }
27
28
 
28
29
  function toggleSubscription (enable, level) {
@@ -30,7 +31,7 @@ function toggleSubscription (enable, level) {
30
31
 
31
32
  if (enable) {
32
33
  logChannel = new LogChannel(level)
33
- logChannel.subscribe({ debug, info, warn, error })
34
+ logChannel.subscribe({ debug: onDebug, info: onInfo, warn: onWarn, error: onError })
34
35
  }
35
36
  }
36
37
 
@@ -51,32 +52,62 @@ function reset () {
51
52
  toggleSubscription(false)
52
53
  }
53
54
 
54
- function error (err) {
55
- if (typeof err !== 'object' || !err) {
56
- err = String(err)
57
- } else if (!err.stack) {
58
- err = String(err.message || err)
55
+ function getErrorLog (err) {
56
+ if (err && typeof err.delegate === 'function') {
57
+ const result = err.delegate()
58
+ return Array.isArray(result) ? Log.parse(...result) : Log.parse(result)
59
+ } else {
60
+ return err
59
61
  }
62
+ }
60
63
 
61
- if (typeof err === 'string') {
62
- err = new Error(err)
63
- }
64
+ function onError (err) {
65
+ const { formatted, cause } = getErrorLog(err)
66
+
67
+ // calling twice logger.error() because Error cause is only available in nodejs v16.9.0
68
+ // TODO: replace it with Error(message, { cause }) when cause has broad support
69
+ if (formatted) withNoop(() => logger.error(new Error(formatted)))
70
+ if (cause) withNoop(() => logger.error(cause))
71
+ }
72
+
73
+ function onWarn (log) {
74
+ const { formatted, cause } = getErrorLog(log)
75
+ if (formatted) withNoop(() => logger.warn(formatted))
76
+ if (cause) withNoop(() => logger.warn(cause))
77
+ }
64
78
 
65
- withNoop(() => logger.error(err))
79
+ function onInfo (log) {
80
+ const { formatted, cause } = getErrorLog(log)
81
+ if (formatted) withNoop(() => logger.info(formatted))
82
+ if (cause) withNoop(() => logger.info(cause))
66
83
  }
67
84
 
68
- function warn (message) {
69
- if (!logger.warn) return debug(message)
70
- withNoop(() => logger.warn(message))
85
+ function onDebug (log) {
86
+ const { formatted, cause } = getErrorLog(log)
87
+ if (formatted) withNoop(() => logger.debug(formatted))
88
+ if (cause) withNoop(() => logger.debug(cause))
71
89
  }
72
90
 
73
- function info (message) {
74
- if (!logger.info) return debug(message)
75
- withNoop(() => logger.info(message))
91
+ function error (...args) {
92
+ onError(Log.parse(...args))
93
+ }
94
+
95
+ function warn (...args) {
96
+ const log = Log.parse(...args)
97
+ if (!logger.warn) return onDebug(log)
98
+
99
+ onWarn(log)
100
+ }
101
+
102
+ function info (...args) {
103
+ const log = Log.parse(...args)
104
+ if (!logger.info) return onDebug(log)
105
+
106
+ onInfo(log)
76
107
  }
77
108
 
78
- function debug (message) {
79
- withNoop(() => logger.debug(message))
109
+ function debug (...args) {
110
+ onDebug(Log.parse(...args))
80
111
  }
81
112
 
82
113
  module.exports = { use, toggle, reset, error, warn, info, debug }
@@ -22,6 +22,7 @@ class NoopSpan {
22
22
  setTag (key, value) { return this }
23
23
  addTags (keyValueMap) { return this }
24
24
  addLink (link) { return this }
25
+ addSpanPointer (ptrKind, ptrDir, ptrHash) { return this }
25
26
  log () { return this }
26
27
  logEvent () {}
27
28
  finish (finishTime) {}
@@ -14,6 +14,7 @@ const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags')
14
14
  const kinds = require('../../../../ext/kinds')
15
15
 
16
16
  const SpanContext = require('./span_context')
17
+ const id = require('../id')
17
18
 
18
19
  // The one built into OTel rounds so we lose sub-millisecond precision.
19
20
  function hrTimeToMilliseconds (time) {
@@ -217,6 +218,20 @@ class Span {
217
218
  return this
218
219
  }
219
220
 
221
+ addSpanPointer (ptrKind, ptrDir, ptrHash) {
222
+ const zeroContext = new SpanContext({
223
+ traceId: id('0'),
224
+ spanId: id('0')
225
+ })
226
+ const attributes = {
227
+ 'ptr.kind': ptrKind,
228
+ 'ptr.dir': ptrDir,
229
+ 'ptr.hash': ptrHash,
230
+ 'link.kind': 'span-pointer'
231
+ }
232
+ return this.addLink(zeroContext, attributes)
233
+ }
234
+
220
235
  setStatus ({ code, message }) {
221
236
  if (!this.ended && !this._hasStatus && code) {
222
237
  this._hasStatus = true