dd-trace 5.47.0 → 5.48.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/index.d.ts CHANGED
@@ -869,6 +869,39 @@ declare namespace tracer {
869
869
  flush(): void
870
870
  }
871
871
 
872
+ export interface EventTrackingV2 {
873
+ /**
874
+ * Links a successful login event to the current trace. Will link the passed user to the current trace with Appsec.setUser() internally.
875
+ * @param {string} login The login key (username, email...) used by the user to authenticate.
876
+ * @param {User} user Properties of the authenticated user. Accepts custom fields. Can be null.
877
+ * @param {any} metadata Custom fields to link to the login success event.
878
+ */
879
+ trackUserLoginSuccess(login: string, user?: User | null, metadata?: any): void;
880
+
881
+ /**
882
+ * Links a successful login event to the current trace. Will link the passed user to the current trace with Appsec.setUser() internally.
883
+ * @param {string} login The login key (username, email...) used by the user to authenticate.
884
+ * @param {string} userId Identifier of the authenticated user.
885
+ * @param {any} metadata Custom fields to link to the login success event.
886
+ */
887
+ trackUserLoginSuccess(login: string, userId: string, metadata?: any): void;
888
+
889
+ /**
890
+ * Links a failed login event to the current trace.
891
+ * @param {string} login The login key (username, email...) used by the user to authenticate.
892
+ * @param {boolean} exists If the user exists.
893
+ * @param {any} metadata Custom fields to link to the login failure event.
894
+ */
895
+ trackUserLoginFailure(login: string, exists: boolean, metadata?: any): void;
896
+
897
+ /**
898
+ * Links a failed login event to the current trace.
899
+ * @param {string} login The login key (username, email...) used by the user to authenticate.
900
+ * @param {any} metadata Custom fields to link to the login failure event.
901
+ */
902
+ trackUserLoginFailure(login: string, metadata?: any): void;
903
+ }
904
+
872
905
  export interface Appsec {
873
906
  /**
874
907
  * Links a successful login event to the current trace. Will link the passed user to the current trace with Appsec.setUser() internally.
@@ -876,6 +909,8 @@ declare namespace tracer {
876
909
  * @param {[key: string]: string} metadata Custom fields to link to the login success event.
877
910
  *
878
911
  * @beta This method is in beta and could change in future versions.
912
+ *
913
+ * @deprecated In favor of eventTrackingV2.trackUserLoginSuccess
879
914
  */
880
915
  trackUserLoginSuccessEvent(user: User, metadata?: { [key: string]: string }): void
881
916
 
@@ -886,6 +921,8 @@ declare namespace tracer {
886
921
  * @param {[key: string]: string} metadata Custom fields to link to the login failure event.
887
922
  *
888
923
  * @beta This method is in beta and could change in future versions.
924
+ *
925
+ * @deprecated In favor of eventTrackingV2.trackUserLoginFailure
889
926
  */
890
927
  trackUserLoginFailureEvent(userId: string, exists: boolean, metadata?: { [key: string]: string }): void
891
928
 
@@ -926,6 +963,8 @@ declare namespace tracer {
926
963
  * @beta This method is in beta and could change in the future
927
964
  */
928
965
  setUser(user: User): void
966
+
967
+ eventTrackingV2: EventTrackingV2
929
968
  }
930
969
 
931
970
  /** @hidden */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.47.0",
3
+ "version": "5.48.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -1,16 +1,38 @@
1
1
  'use strict'
2
2
 
3
- const { trackUserLoginSuccessEvent, trackUserLoginFailureEvent, trackCustomEvent } = require('./track_event')
3
+ const {
4
+ trackUserLoginSuccessEvent,
5
+ trackUserLoginFailureEvent,
6
+ trackCustomEvent,
7
+ trackUserLoginSuccessV2,
8
+ trackUserLoginFailureV2
9
+ } = require('./track_event')
4
10
  const { checkUserAndSetUser, blockRequest } = require('./user_blocking')
5
11
  const { setTemplates } = require('../blocking')
6
12
  const { setUser } = require('./set_user')
7
13
 
14
+ class EventTrackingV2 {
15
+ constructor (tracer) {
16
+ this._tracer = tracer
17
+ }
18
+
19
+ trackUserLoginSuccess (login, user, metadata) {
20
+ trackUserLoginSuccessV2(this._tracer, login, user, metadata)
21
+ }
22
+
23
+ trackUserLoginFailure (login, exists, metadata) {
24
+ trackUserLoginFailureV2(this._tracer, login, exists, metadata)
25
+ }
26
+ }
27
+
8
28
  class AppsecSdk {
9
29
  constructor (tracer, config) {
10
30
  this._tracer = tracer
11
31
  if (config) {
12
32
  setTemplates(config)
13
33
  }
34
+
35
+ this.eventTrackingV2 = new EventTrackingV2(tracer)
14
36
  }
15
37
 
16
38
  trackUserLoginSuccessEvent (user, metadata) {
@@ -1,6 +1,16 @@
1
1
  'use strict'
2
2
 
3
+ class NoopEventTrackingV2 {
4
+ trackUserLoginSuccess () {}
5
+
6
+ trackUserLoginFailure () {}
7
+ }
8
+
3
9
  class NoopAppsecSdk {
10
+ constructor () {
11
+ this.eventTrackingV2 = new NoopEventTrackingV2()
12
+ }
13
+
4
14
  trackUserLoginSuccessEvent () {}
5
15
 
6
16
  trackUserLoginFailureEvent () {}
@@ -9,6 +9,8 @@ function setUserTags (user, rootSpan) {
9
9
  for (const k of Object.keys(user)) {
10
10
  rootSpan.setTag(`usr.${k}`, '' + user[k])
11
11
  }
12
+
13
+ rootSpan.setTag('_dd.appsec.user.collection_mode', 'sdk')
12
14
  }
13
15
 
14
16
  function setUser (tracer, user) {
@@ -24,7 +26,6 @@ function setUser (tracer, user) {
24
26
  }
25
27
 
26
28
  setUserTags(user, rootSpan)
27
- rootSpan.setTag('_dd.appsec.user.collection_mode', 'sdk')
28
29
 
29
30
  const persistent = {
30
31
  [addresses.USER_ID]: '' + user.id
@@ -9,6 +9,9 @@ const addresses = require('../addresses')
9
9
  const { ASM } = require('../../standalone/product')
10
10
  const { incrementSdkEventMetric } = require('../telemetry')
11
11
 
12
+ /**
13
+ * @deprecated in favor of trackUserLoginSuccessV2
14
+ */
12
15
  function trackUserLoginSuccessEvent (tracer, user, metadata) {
13
16
  // TODO: better user check here and in _setUser() ?
14
17
  if (!user || !user.id) {
@@ -16,7 +19,7 @@ function trackUserLoginSuccessEvent (tracer, user, metadata) {
16
19
  return
17
20
  }
18
21
 
19
- incrementSdkEventMetric('login_success')
22
+ incrementSdkEventMetric('login_success', 'v1')
20
23
 
21
24
  const rootSpan = getRootSpan(tracer)
22
25
  if (!rootSpan) {
@@ -35,6 +38,9 @@ function trackUserLoginSuccessEvent (tracer, user, metadata) {
35
38
  runWaf('users.login.success', { id: user.id, login })
36
39
  }
37
40
 
41
+ /**
42
+ * @deprecated in favor of trackUserLoginFailureV2
43
+ */
38
44
  function trackUserLoginFailureEvent (tracer, userId, exists, metadata) {
39
45
  if (!userId || typeof userId !== 'string') {
40
46
  log.warn('[ASM] Invalid userId provided to trackUserLoginFailureEvent')
@@ -52,7 +58,7 @@ function trackUserLoginFailureEvent (tracer, userId, exists, metadata) {
52
58
 
53
59
  runWaf('users.login.failure', { login: userId })
54
60
 
55
- incrementSdkEventMetric('login_failure')
61
+ incrementSdkEventMetric('login_failure', 'v1')
56
62
  }
57
63
 
58
64
  function trackCustomEvent (tracer, eventName, metadata) {
@@ -63,7 +69,112 @@ function trackCustomEvent (tracer, eventName, metadata) {
63
69
 
64
70
  trackEvent(eventName, metadata, 'trackCustomEvent', getRootSpan(tracer))
65
71
 
66
- incrementSdkEventMetric('custom')
72
+ incrementSdkEventMetric('custom', 'v1')
73
+
74
+ if (eventName === 'users.login.success' || eventName === 'users.login.failure') {
75
+ runWaf(eventName)
76
+ }
77
+ }
78
+
79
+ function trackUserLoginSuccessV2 (tracer, login, user, metadata) {
80
+ if (!login || typeof login !== 'string') {
81
+ log.warn('[ASM] Invalid login provided to eventTrackingV2.trackUserLoginSuccess')
82
+ return
83
+ }
84
+
85
+ incrementSdkEventMetric('login_success', 'v2')
86
+
87
+ const rootSpan = getRootSpan(tracer)
88
+ if (!rootSpan) {
89
+ log.warn('[ASM] Root span not available in eventTrackingV2.trackUserLoginSuccess')
90
+ return
91
+ }
92
+
93
+ const wafData = { login }
94
+
95
+ metadata = {
96
+ 'usr.login': login,
97
+ ...metadata
98
+ }
99
+
100
+ if (user) {
101
+ if (typeof user !== 'object') {
102
+ user = { id: user }
103
+ }
104
+
105
+ if (user.id) {
106
+ wafData.id = user.id
107
+ setUserTags(user, rootSpan)
108
+ metadata.usr = user
109
+ }
110
+ }
111
+
112
+ trackEvent('users.login.success', metadata, 'eventTrackingV2.trackUserLoginSuccess', rootSpan)
113
+
114
+ runWaf('users.login.success', wafData)
115
+ }
116
+
117
+ function trackUserLoginFailureV2 (tracer, login, exists, metadata) {
118
+ if (!login || typeof login !== 'string') {
119
+ log.warn('[ASM] Invalid login provided to eventTrackingV2.trackUserLoginFailure')
120
+ return
121
+ }
122
+
123
+ incrementSdkEventMetric('login_failure', 'v2')
124
+
125
+ const rootSpan = getRootSpan(tracer)
126
+ if (!rootSpan) {
127
+ log.warn('[ASM] Root span not available in eventTrackingV2.trackUserLoginFailure')
128
+ return
129
+ }
130
+
131
+ const wafData = { login }
132
+
133
+ if (typeof exists === 'object' && metadata === undefined) {
134
+ metadata = exists
135
+ exists = false
136
+ }
137
+
138
+ metadata = {
139
+ 'usr.login': login,
140
+ 'usr.exists': exists ? 'true' : 'false',
141
+ ...metadata
142
+ }
143
+
144
+ trackEvent('users.login.failure', metadata, 'eventTrackingV2.trackUserLoginFailure', rootSpan)
145
+
146
+ runWaf('users.login.failure', wafData)
147
+ }
148
+
149
+ function flattenFields (fields, depth = 0) {
150
+ if (depth > 4) {
151
+ return {
152
+ truncated: true
153
+ }
154
+ }
155
+
156
+ const result = {}
157
+ let truncated = false
158
+ for (const key of Object.keys(fields)) {
159
+ const value = fields[key]
160
+
161
+ if (value && typeof value === 'object') {
162
+ const { result: flatValue, truncated: inheritTruncated } = flattenFields(value, depth + 1)
163
+ truncated = truncated || inheritTruncated
164
+
165
+ if (flatValue) {
166
+ for (const flatKey of Object.keys(flatValue)) {
167
+ result[`${key}.${flatKey}`] = flatValue[flatKey]
168
+ }
169
+ }
170
+ } else {
171
+ if (value !== undefined) {
172
+ result[key] = value
173
+ }
174
+ }
175
+ }
176
+
177
+ return { result, truncated }
67
178
  }
68
179
 
69
180
  function trackEvent (eventName, fields, sdkMethodName, rootSpan) {
@@ -78,8 +189,14 @@ function trackEvent (eventName, fields, sdkMethodName, rootSpan) {
78
189
  }
79
190
 
80
191
  if (fields) {
81
- for (const metadataKey of Object.keys(fields)) {
82
- tags[`appsec.events.${eventName}.${metadataKey}`] = '' + fields[metadataKey]
192
+ const { result: flatFields, truncated } = flattenFields(fields)
193
+
194
+ if (truncated) {
195
+ log.warn('[ASM] Too deep object provided in the SDK method %s, object truncated', sdkMethodName)
196
+ }
197
+
198
+ for (const metadataKey of Object.keys(flatFields)) {
199
+ tags[`appsec.events.${eventName}.${metadataKey}`] = '' + flatFields[metadataKey]
83
200
  }
84
201
  }
85
202
 
@@ -93,11 +210,11 @@ function runWaf (eventName, user) {
93
210
  [`server.business_logic.${eventName}`]: null
94
211
  }
95
212
 
96
- if (user.id) {
213
+ if (user?.id) {
97
214
  persistent[addresses.USER_ID] = '' + user.id
98
215
  }
99
216
 
100
- if (user.login) {
217
+ if (user?.login) {
101
218
  persistent[addresses.USER_LOGIN] = '' + user.login
102
219
  }
103
220
 
@@ -107,5 +224,9 @@ function runWaf (eventName, user) {
107
224
  module.exports = {
108
225
  trackUserLoginSuccessEvent,
109
226
  trackUserLoginFailureEvent,
110
- trackCustomEvent
227
+ trackCustomEvent,
228
+ trackUserLoginSuccessV2,
229
+ trackUserLoginFailureV2,
230
+ trackEvent,
231
+ runWaf
111
232
  }
@@ -23,7 +23,6 @@ function checkUserAndSetUser (tracer, user) {
23
23
  if (rootSpan) {
24
24
  if (!rootSpan.context()._tags['usr.id']) {
25
25
  setUserTags(user, rootSpan)
26
- rootSpan.setTag('_dd.appsec.user.collection_mode', 'sdk')
27
26
  }
28
27
  } else {
29
28
  log.warn('[ASM] Root span not available in isUserBlocked')
@@ -143,10 +143,10 @@ function incrementMissingUserIdMetric (framework, eventType) {
143
143
  incrementMissingUserId(framework, eventType)
144
144
  }
145
145
 
146
- function incrementSdkEventMetric (framework, eventType) {
146
+ function incrementSdkEventMetric (eventType, sdkVersion) {
147
147
  if (!enabled) return
148
148
 
149
- incrementSdkEvent(framework, eventType)
149
+ incrementSdkEvent(eventType, sdkVersion)
150
150
  }
151
151
 
152
152
  function getRequestMetrics (req) {
@@ -18,10 +18,10 @@ function incrementMissingUserId (framework, eventType) {
18
18
  }).inc()
19
19
  }
20
20
 
21
- function incrementSdkEvent (eventType) {
21
+ function incrementSdkEvent (eventType, sdkVersion = 'v1') {
22
22
  appsecMetrics.count('sdk.event', {
23
23
  event_type: eventType,
24
- sdk_version: 'v1'
24
+ sdk_version: sdkVersion
25
25
  }).inc()
26
26
  }
27
27
 
@@ -3,7 +3,7 @@
3
3
  const { getGeneratedPosition } = require('./source-maps')
4
4
  const lock = require('./lock')()
5
5
  const session = require('./session')
6
- const compileCondition = require('./condition')
6
+ const { compile: compileCondition, compileSegments, templateRequiresEvaluation } = require('./condition')
7
7
  const { MAX_SNAPSHOTS_PER_SECOND_PER_PROBE, MAX_NON_SNAPSHOTS_PER_SECOND_PER_PROBE } = require('./defaults')
8
8
  const { findScriptFromPartialPath, locationToBreakpoint, breakpointToProbes, probeToLocation } = require('./state')
9
9
  const log = require('../../log')
@@ -26,6 +26,13 @@ async function addBreakpoint (probe) {
26
26
  probe.location = { file, lines: [String(lineNumber)] }
27
27
  delete probe.where
28
28
 
29
+ // Optimize for fast calculations when probe is hit
30
+ probe.templateRequiresEvaluation = templateRequiresEvaluation(probe.segments)
31
+ if (probe.templateRequiresEvaluation) {
32
+ probe.template = compileSegments(probe.segments)
33
+ }
34
+ delete probe.segments
35
+
29
36
  // Optimize for fast calculations when probe is hit
30
37
  const snapshotsPerSecond = probe.sampling?.snapshotsPerSecond ?? (probe.captureSnapshot
31
38
  ? MAX_SNAPSHOTS_PER_SECOND_PER_PROBE
@@ -41,6 +48,10 @@ async function addBreakpoint (probe) {
41
48
  const { url, scriptId, sourceMapURL, source } = script
42
49
 
43
50
  if (sourceMapURL) {
51
+ log.debug(
52
+ '[debugger:devtools_client] Translating location using source map for %s:%d:%d (probe: %s, version: %d)',
53
+ file, lineNumber, columnNumber, probe.id, probe.version
54
+ );
44
55
  ({ line: lineNumber, column: columnNumber } = await getGeneratedPosition(url, source, lineNumber, sourceMapURL))
45
56
  }
46
57
 
@@ -156,6 +167,8 @@ function stop () {
156
167
 
157
168
  // Only if all probes have a condition can we use a compound condition.
158
169
  // Otherwise, we need to evaluate each probe individually once the breakpoint is hit.
170
+ // TODO: Handle errors - if there's 2 conditons, and one fails but the other returns true, we should still pause the
171
+ // breakpoint
159
172
  function compileCompoundCondition (probes) {
160
173
  return probes.every(p => p.condition)
161
174
  ? probes.map(p => p.condition).filter(Boolean).join(' || ')
@@ -1,6 +1,10 @@
1
1
  'use strict'
2
2
 
3
- module.exports = compile
3
+ module.exports = {
4
+ compile,
5
+ compileSegments,
6
+ templateRequiresEvaluation
7
+ }
4
8
 
5
9
  const identifierRegex = /^[@a-zA-Z_$][\w$]*$/
6
10
 
@@ -35,7 +39,37 @@ const reservedWords = new Set([
35
39
 
36
40
  const PRIMITIVE_TYPES = new Set(['string', 'number', 'bigint', 'boolean', 'undefined', 'symbol', 'null'])
37
41
 
38
- // TODO: Consider storing some of these functions on `process` so they can be reused across probes
42
+ function templateRequiresEvaluation (segments) {
43
+ if (segments === undefined) return false // There should always be segments, but just in case
44
+ for (const { str } of segments) {
45
+ if (str === undefined) return true
46
+ }
47
+ return false
48
+ }
49
+
50
+ function compileSegments (segments) {
51
+ let result = '['
52
+ for (let i = 0; i < segments.length; i++) {
53
+ const { str, dsl, json } = segments[i]
54
+ result += str !== undefined
55
+ ? JSON.stringify(str)
56
+ : `(() => {
57
+ try {
58
+ const result = ${compile(json)}
59
+ return typeof result === 'string' ? result : $dd_inspect(result, $dd_segmentInspectOptions)
60
+ } catch (e) {
61
+ return { expr: ${JSON.stringify(dsl)}, message: \`\${e.name}: \${e.message}\` }
62
+ }
63
+ })()`
64
+ if (i !== segments.length - 1) {
65
+ result += ','
66
+ }
67
+ }
68
+ return `${result}]`
69
+ }
70
+
71
+ // TODO: Consider storing some of these functions that doesn't require closure access to the current scope on `process`
72
+ // so they can be reused across probes
39
73
  function compile (node) {
40
74
  if (node === null || typeof node === 'number' || typeof node === 'boolean') {
41
75
  return node
@@ -16,10 +16,20 @@ const { NODE_MAJOR } = require('../../../../../version')
16
16
  require('./remote_config')
17
17
 
18
18
  // Expression to run on a call frame of the paused thread to get its active trace and span id.
19
- const expression = `
20
- const context = global.require('dd-trace').scope().active()?.context();
21
- ({ trace_id: context?.toTraceId(), span_id: context?.toSpanId() })
19
+ const templateExpressionSetupCode = `
20
+ const $dd_inspect = global.require('node:util').inspect;
21
+ const $dd_segmentInspectOptions = {
22
+ depth: 0,
23
+ customInspect: false,
24
+ maxArrayLength: 3,
25
+ maxStringLength: 8 * 1024,
26
+ breakLength: Infinity
27
+ };
22
28
  `
29
+ const getDDTagsExpression = `(() => {
30
+ const context = global.require('dd-trace').scope().active()?.context();
31
+ return { trace_id: context?.toTraceId(), span_id: context?.toSpanId() }
32
+ })()`
23
33
 
24
34
  // There doesn't seem to be an official standard for the content of these fields, so we're just populating them with
25
35
  // something that should be useful to a Node.js developer.
@@ -45,6 +55,7 @@ session.on('Debugger.paused', async ({ params }) => {
45
55
  let sampled = false
46
56
  let numberOfProbesWithSnapshots = 0
47
57
  const probes = []
58
+ let templateExpressions = ''
48
59
 
49
60
  // V8 doesn't allow setting more than one breakpoint at a specific location, however, it's possible to set two
50
61
  // breakpoints just next to each other that will "snap" to the same logical location, which in turn will be hit at the
@@ -79,15 +90,6 @@ session.on('Debugger.paused', async ({ params }) => {
79
90
  continue
80
91
  }
81
92
 
82
- if (shouldVerifyConditions && probe.condition !== undefined) {
83
- const { result } = await session.post('Debugger.evaluateOnCallFrame', {
84
- callFrameId: params.callFrames[0].callFrameId,
85
- expression: probe.condition,
86
- returnByValue: true
87
- })
88
- if (result.value !== true) continue
89
- }
90
-
91
93
  if (probe.captureSnapshot === true) {
92
94
  // This algorithm to calculate number of sampled snapshots within the last second is not perfect, as it's not a
93
95
  // sliding window. But it's quick and easy :)
@@ -107,9 +109,24 @@ session.on('Debugger.paused', async ({ params }) => {
107
109
  maxLength = highestOrUndefined(probe.capture.maxLength, maxLength)
108
110
  }
109
111
 
112
+ if (shouldVerifyConditions && probe.condition !== undefined) {
113
+ // TODO: Bundle all conditions and evaluate them in a single call
114
+ // TODO: Handle errors
115
+ const { result } = await session.post('Debugger.evaluateOnCallFrame', {
116
+ callFrameId: params.callFrames[0].callFrameId,
117
+ expression: probe.condition,
118
+ returnByValue: true
119
+ })
120
+ if (result.value !== true) continue
121
+ }
122
+
110
123
  sampled = true
111
124
  probe.lastCaptureNs = start
112
125
 
126
+ if (probe.templateRequiresEvaluation) {
127
+ templateExpressions += `,${probe.template}`
128
+ }
129
+
113
130
  probes.push(probe)
114
131
  }
115
132
  }
@@ -119,7 +136,22 @@ session.on('Debugger.paused', async ({ params }) => {
119
136
  }
120
137
 
121
138
  const timestamp = Date.now()
122
- const dd = await getDD(params.callFrames[0].callFrameId)
139
+
140
+ let evalResults = null
141
+ const { result } = await session.post('Debugger.evaluateOnCallFrame', {
142
+ callFrameId: params.callFrames[0].callFrameId,
143
+ expression: templateExpressions.length === 0
144
+ ? `[${getDDTagsExpression}]`
145
+ : `${templateExpressionSetupCode}[${getDDTagsExpression}${templateExpressions}]`,
146
+ returnByValue: true,
147
+ includeCommandLineAPI: true
148
+ })
149
+ if (result?.subtype === 'error') {
150
+ log.error('[debugger:devtools_client] Error evaluating code on call frame: %s', result?.description)
151
+ evalResults = []
152
+ } else {
153
+ evalResults = result?.value ?? []
154
+ }
123
155
 
124
156
  let processLocalState
125
157
  if (numberOfProbesWithSnapshots !== 0) {
@@ -155,6 +187,8 @@ session.on('Debugger.paused', async ({ params }) => {
155
187
  }
156
188
 
157
189
  const stack = getStackFromCallFrames(params.callFrames)
190
+ const dd = processDD(evalResults[0]) // the first result is the dd tags, the rest are the probe template results
191
+ let messageIndex = 1
158
192
 
159
193
  // TODO: Send multiple probes in one HTTP request as an array (DEBUG-2848)
160
194
  for (const probe of probes) {
@@ -179,9 +213,33 @@ session.on('Debugger.paused', async ({ params }) => {
179
213
  }
180
214
  }
181
215
 
216
+ let message = ''
217
+ if (probe.templateRequiresEvaluation) {
218
+ const results = evalResults[messageIndex++]
219
+ if (results === undefined) {
220
+ log.error('[debugger:devtools_client] No evaluation results for probe %s', probe.id)
221
+ } else {
222
+ for (const result of results) {
223
+ if (typeof result === 'string') {
224
+ message += result
225
+ } else {
226
+ // If `result` isn't a string, it's an evaluation error object
227
+ if (snapshot.evaluationErrors === undefined) {
228
+ snapshot.evaluationErrors = [result]
229
+ } else {
230
+ snapshot.evaluationErrors.push(result)
231
+ }
232
+ message += `{${result.message}}`
233
+ }
234
+ }
235
+ }
236
+ } else {
237
+ message = probe.template
238
+ }
239
+
182
240
  ackEmitting(probe)
183
- // TODO: Process template (DEBUG-2628)
184
- send(probe.template, logger, dd, snapshot)
241
+
242
+ send(message, logger, dd, snapshot)
185
243
  }
186
244
  })
187
245
 
@@ -189,22 +247,6 @@ function highestOrUndefined (num, max) {
189
247
  return num === undefined ? max : Math.max(num, max ?? 0)
190
248
  }
191
249
 
192
- async function getDD (callFrameId) {
193
- // TODO: Consider if an `objectGroup` should be used, so it can be explicitly released using
194
- // `Runtime.releaseObjectGroup`
195
- const { result } = await session.post('Debugger.evaluateOnCallFrame', {
196
- callFrameId,
197
- expression,
198
- returnByValue: true,
199
- includeCommandLineAPI: true
200
- })
201
-
202
- if (result?.value?.trace_id === undefined) {
203
- if (result?.subtype === 'error') {
204
- log.error('[debugger:devtools_client] Error getting trace/span id:', result.description)
205
- }
206
- return
207
- }
208
-
209
- return result.value
250
+ function processDD (result) {
251
+ return result?.trace_id === undefined ? undefined : result
210
252
  }
@@ -12,6 +12,7 @@ const { version } = require('../../../../../package.json')
12
12
 
13
13
  module.exports = send
14
14
 
15
+ const MAX_MESSAGE_LENGTH = 8 * 1024 // 8KB
15
16
  const MAX_LOG_PAYLOAD_SIZE = 1024 * 1024 // 1MB
16
17
 
17
18
  const ddsource = 'dd_debugger'
@@ -36,7 +37,9 @@ function send (message, logger, dd, snapshot) {
36
37
  ddsource,
37
38
  hostname,
38
39
  service,
39
- message,
40
+ message: message?.length > MAX_MESSAGE_LENGTH
41
+ ? message.slice(0, MAX_MESSAGE_LENGTH) + '…'
42
+ : message,
40
43
  logger,
41
44
  dd,
42
45
  debugger: { snapshot }
@@ -27,7 +27,7 @@ const self = module.exports = {
27
27
 
28
28
  async getGeneratedPosition (url, source, line, sourceMapURL) {
29
29
  const dir = dirname(new URL(url).pathname)
30
- return await SourceMapConsumer.with(
30
+ return SourceMapConsumer.with(
31
31
  await self.loadSourceMap(dir, sourceMapURL),
32
32
  null,
33
33
  (consumer) => consumer.generatedPositionFor({ source, line, column: 0 })