dd-trace 5.74.0 → 5.75.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.
@@ -47,6 +47,7 @@ const {
47
47
  TELEMETRY_EVENT_FINISHED
48
48
  } = require('../../dd-trace/src/ci-visibility/telemetry')
49
49
  const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
50
+ const log = require('../../dd-trace/src/log')
50
51
 
51
52
  class PlaywrightPlugin extends CiPlugin {
52
53
  static id = 'playwright'
@@ -174,7 +175,10 @@ class PlaywrightPlugin extends CiPlugin {
174
175
  }) => {
175
176
  const store = storage('legacy').getStore()
176
177
  const span = store && store.span
177
- if (!span) return
178
+ if (!span) {
179
+ log.error('ci:playwright:test:page-goto: test span not found')
180
+ return
181
+ }
178
182
 
179
183
  if (isRumActive) {
180
184
  span.setTag(TEST_IS_RUM_ACTIVE, 'true')
@@ -198,36 +202,6 @@ class PlaywrightPlugin extends CiPlugin {
198
202
  }
199
203
  })
200
204
 
201
- this.addBind('ci:playwright:test:start', (ctx) => {
202
- const {
203
- testName,
204
- testSuiteAbsolutePath,
205
- testSourceLine,
206
- browserName,
207
- isDisabled
208
- } = ctx
209
- const store = storage('legacy').getStore()
210
- const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
211
- const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
212
- const span = this.startTestSpan(
213
- testName,
214
- testSuiteAbsolutePath,
215
- testSuite,
216
- testSourceFile,
217
- testSourceLine,
218
- browserName
219
- )
220
-
221
- if (isDisabled) {
222
- span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
223
- }
224
-
225
- ctx.parentStore = store
226
- ctx.currentStore = { ...store, span }
227
-
228
- return ctx.currentStore
229
- })
230
-
231
205
  this.addSub('ci:playwright:worker:report', (serializedTraces) => {
232
206
  const traces = JSON.parse(serializedTraces)
233
207
  const formattedTraces = []
@@ -277,6 +251,36 @@ class PlaywrightPlugin extends CiPlugin {
277
251
  })
278
252
  })
279
253
 
254
+ this.addBind('ci:playwright:test:start', (ctx) => {
255
+ const {
256
+ testName,
257
+ testSuiteAbsolutePath,
258
+ testSourceLine,
259
+ browserName,
260
+ isDisabled
261
+ } = ctx
262
+ const store = storage('legacy').getStore()
263
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
264
+ const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
265
+ const span = this.startTestSpan(
266
+ testName,
267
+ testSuiteAbsolutePath,
268
+ testSuite,
269
+ testSourceFile,
270
+ testSourceLine,
271
+ browserName
272
+ )
273
+
274
+ if (isDisabled) {
275
+ span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
276
+ }
277
+
278
+ ctx.parentStore = store
279
+ ctx.currentStore = { ...store, span }
280
+
281
+ return ctx.currentStore
282
+ })
283
+
280
284
  this.addSub('ci:playwright:test:finish', ({
281
285
  span,
282
286
  testStatus,
@@ -393,6 +397,45 @@ class PlaywrightPlugin extends CiPlugin {
393
397
  this.tracer._exporter.flush(onDone)
394
398
  }
395
399
  })
400
+
401
+ this.addSub('ci:playwright:test:skip', ({
402
+ testName,
403
+ testSuiteAbsolutePath,
404
+ testSourceLine,
405
+ browserName,
406
+ isNew,
407
+ isDisabled,
408
+ isModified,
409
+ isQuarantined
410
+ }) => {
411
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
412
+ const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
413
+ const span = this.startTestSpan(
414
+ testName,
415
+ testSuiteAbsolutePath,
416
+ testSuite,
417
+ testSourceFile,
418
+ testSourceLine,
419
+ browserName
420
+ )
421
+
422
+ span.setTag(TEST_STATUS, 'skip')
423
+
424
+ if (isNew) {
425
+ span.setTag(TEST_IS_NEW, 'true')
426
+ }
427
+ if (isDisabled) {
428
+ span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
429
+ }
430
+ if (isModified) {
431
+ span.setTag(TEST_IS_MODIFIED, 'true')
432
+ }
433
+ if (isQuarantined) {
434
+ span.setTag(TEST_MANAGEMENT_IS_QUARANTINED, 'true')
435
+ }
436
+
437
+ span.finish()
438
+ })
396
439
  }
397
440
 
398
441
  // TODO: this runs both in worker and main process (main process: skipped tests that do not go through _runTest)
@@ -73,6 +73,8 @@ function copyObjectProperties (original, wrapped, skipKey) {
73
73
  * @returns {Function} The wrapped function.
74
74
  */
75
75
  function wrapFunction (original, wrapper) {
76
+ if (typeof original !== 'function') return original
77
+
76
78
  const wrapped = wrapper(original)
77
79
 
78
80
  if (typeof original === 'function') {
@@ -9,9 +9,15 @@ const {
9
9
  AI_GUARD_ACTION_TAG_KEY,
10
10
  AI_GUARD_BLOCKED_TAG_KEY,
11
11
  AI_GUARD_META_STRUCT_KEY,
12
- AI_GUARD_TOOL_NAME_TAG_KEY
12
+ AI_GUARD_TOOL_NAME_TAG_KEY,
13
+ AI_GUARD_TELEMETRY_REQUESTS,
14
+ AI_GUARD_TELEMETRY_TRUNCATED
13
15
  } = require('./tags')
14
16
  const log = require('../log')
17
+ const telemetryMetrics = require('../telemetry/metrics')
18
+ const tracerVersion = require('../../../../package.json').version
19
+
20
+ const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
15
21
 
16
22
  const ALLOW = 'ALLOW'
17
23
 
@@ -58,6 +64,9 @@ class AIGuard extends NoopAIGuard {
58
64
  this.#headers = {
59
65
  'DD-API-KEY': config.apiKey,
60
66
  'DD-APPLICATION-KEY': config.appKey,
67
+ 'DD-AI-GUARD-VERSION': tracerVersion,
68
+ 'DD-AI-GUARD-SOURCE': 'SDK',
69
+ 'DD-AI-GUARD-LANGUAGE': 'nodejs'
61
70
  }
62
71
  const endpoint = config.experimental.aiguard.endpoint || `https://app.${config.site}/api/v2/ai-guard`
63
72
  this.#evaluateUrl = `${endpoint}/evaluate`
@@ -70,14 +79,22 @@ class AIGuard extends NoopAIGuard {
70
79
 
71
80
  #truncate (messages) {
72
81
  const size = Math.min(messages.length, this.#maxMessagesLength)
82
+ if (messages.length > size) {
83
+ appsecMetrics.count(AI_GUARD_TELEMETRY_TRUNCATED, { type: 'messages' }).inc(1)
84
+ }
73
85
  const result = messages.slice(-size)
74
86
 
87
+ let contentTruncated = false
75
88
  for (let i = 0; i < size; i++) {
76
89
  const message = result[i]
77
90
  if (message.content?.length > this.#maxContentSize) {
91
+ contentTruncated = true
78
92
  result[i] = { ...message, content: message.content.slice(0, this.#maxContentSize) }
79
93
  }
80
94
  }
95
+ if (contentTruncated) {
96
+ appsecMetrics.count(AI_GUARD_TELEMETRY_TRUNCATED, { type: 'content' }).inc(1)
97
+ }
81
98
  return result
82
99
  }
83
100
 
@@ -140,9 +157,11 @@ class AIGuard extends NoopAIGuard {
140
157
  payload,
141
158
  { url: this.#evaluateUrl, headers: this.#headers, timeout: this.#timeout })
142
159
  } catch (e) {
143
- throw new AIGuardClientError('Unexpected error calling AI Guard service', { cause: e })
160
+ appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
161
+ throw new AIGuardClientError(`Unexpected error calling AI Guard service: ${e.message}`, { cause: e })
144
162
  }
145
163
  if (response.status !== 200) {
164
+ appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
146
165
  throw new AIGuardClientError(
147
166
  `AI Guard service call failed, status ${response.status}`,
148
167
  { errors: response.body?.errors })
@@ -157,11 +176,14 @@ class AIGuard extends NoopAIGuard {
157
176
  reason = attr.reason
158
177
  blockingEnabled = attr.is_blocking_enabled ?? false
159
178
  } catch (e) {
179
+ appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { error: true }).inc(1)
160
180
  throw new AIGuardClientError(`AI Guard service returned unexpected response : ${response.body}`, { cause: e })
161
181
  }
182
+ const shouldBlock = block && blockingEnabled && action !== ALLOW
183
+ appsecMetrics.count(AI_GUARD_TELEMETRY_REQUESTS, { action, error: false, block: shouldBlock }).inc(1)
162
184
  span.setTag(AI_GUARD_ACTION_TAG_KEY, action)
163
185
  span.setTag(AI_GUARD_REASON_TAG_KEY, reason)
164
- if (block && blockingEnabled && action !== ALLOW) {
186
+ if (shouldBlock) {
165
187
  span.setTag(AI_GUARD_BLOCKED_TAG_KEY, 'true')
166
188
  throw new AIGuardAbortError(reason)
167
189
  }
@@ -7,5 +7,8 @@ module.exports = {
7
7
  AI_GUARD_ACTION_TAG_KEY: 'ai_guard.action',
8
8
  AI_GUARD_REASON_TAG_KEY: 'ai_guard.reason',
9
9
  AI_GUARD_BLOCKED_TAG_KEY: 'ai_guard.blocked',
10
- AI_GUARD_META_STRUCT_KEY: 'ai_guard'
10
+ AI_GUARD_META_STRUCT_KEY: 'ai_guard',
11
+
12
+ AI_GUARD_TELEMETRY_REQUESTS: 'ai_guard.requests',
13
+ AI_GUARD_TELEMETRY_TRUNCATED: 'ai_guard.truncated'
11
14
  }
@@ -80,6 +80,7 @@ module.exports = {
80
80
  * @returns {string|undefined}
81
81
  * @throws {Error} if the configuration is not supported
82
82
  */
83
+ // This method, and callers of this method, need to be updated to check for declarative config sources as well.
83
84
  getEnvironmentVariable (name) {
84
85
  if ((name.startsWith('DD_') || name.startsWith('OTEL_') || aliasToCanonical[name]) &&
85
86
  !supportedConfigurations[name]) {