dd-trace 5.104.0 → 5.105.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 (151) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +13 -0
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +78 -5
  11. package/packages/datadog-instrumentations/src/dns.js +54 -18
  12. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  13. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  14. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  17. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  18. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  26. package/packages/datadog-instrumentations/src/hono.js +54 -3
  27. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  28. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  29. package/packages/datadog-instrumentations/src/jest.js +360 -150
  30. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  31. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  32. package/packages/datadog-instrumentations/src/nats.js +182 -0
  33. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  34. package/packages/datadog-instrumentations/src/openai.js +33 -18
  35. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  36. package/packages/datadog-instrumentations/src/pino.js +17 -5
  37. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  38. package/packages/datadog-instrumentations/src/router.js +76 -32
  39. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  41. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  42. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  43. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  44. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  45. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  46. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  48. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  49. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  50. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  52. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  53. package/packages/datadog-plugin-http/src/server.js +40 -15
  54. package/packages/datadog-plugin-jest/src/index.js +11 -3
  55. package/packages/datadog-plugin-jest/src/util.js +15 -8
  56. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  57. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  58. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  60. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  61. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  62. package/packages/datadog-plugin-nats/src/index.js +20 -0
  63. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  64. package/packages/datadog-plugin-nats/src/util.js +33 -0
  65. package/packages/datadog-plugin-next/src/index.js +5 -3
  66. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  67. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  68. package/packages/datadog-plugin-pino/src/index.js +42 -0
  69. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  70. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  71. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  72. package/packages/datadog-plugin-router/src/index.js +33 -44
  73. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  74. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  75. package/packages/datadog-plugin-winston/src/index.js +30 -0
  76. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  77. package/packages/dd-trace/src/aiguard/index.js +1 -1
  78. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  79. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  80. package/packages/dd-trace/src/appsec/index.js +1 -1
  81. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  82. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  83. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  84. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  85. package/packages/dd-trace/src/baggage.js +7 -1
  86. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  87. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  88. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  89. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  90. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  91. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  92. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  93. package/packages/dd-trace/src/encode/0.4.js +124 -108
  94. package/packages/dd-trace/src/encode/0.5.js +114 -26
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  96. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  97. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  98. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  99. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  100. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  101. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  102. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  103. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  104. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  105. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  106. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  107. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  108. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  109. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  110. package/packages/dd-trace/src/llmobs/util.js +66 -3
  111. package/packages/dd-trace/src/log/index.js +1 -1
  112. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  113. package/packages/dd-trace/src/msgpack/index.js +96 -2
  114. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  115. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  116. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  117. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  118. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  119. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  120. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  121. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  122. package/packages/dd-trace/src/opentracing/span.js +59 -19
  123. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  124. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  125. package/packages/dd-trace/src/plugins/database.js +7 -6
  126. package/packages/dd-trace/src/plugins/index.js +4 -0
  127. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  128. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  129. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  130. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  131. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  132. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  133. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  134. package/packages/dd-trace/src/priority_sampler.js +2 -2
  135. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  136. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  137. package/packages/dd-trace/src/sampling_rule.js +7 -7
  138. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  139. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  140. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  141. package/packages/dd-trace/src/span_format.js +190 -58
  142. package/packages/dd-trace/src/spanleak.js +1 -1
  143. package/packages/dd-trace/src/standalone/index.js +3 -3
  144. package/packages/dd-trace/src/tagger.js +0 -2
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  146. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  147. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  148. package/vendor/dist/protobufjs/index.js +1 -1
  149. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  150. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  151. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -229,11 +229,11 @@ const BASE_LIKE_BRANCH_FILTER = /^(main|master|preprod|prod|dev|development|trun
229
229
 
230
230
  /**
231
231
  * Returns request error tags from a test session span for propagation to child events.
232
- * @param {{ context: () => { _tags?: Record<string, string> } } | undefined} sessionSpan
232
+ * @param {{ context: () => { getTag?: (key: string) => string } } | undefined} sessionSpan
233
233
  * @returns {Record<string, string>}
234
234
  */
235
235
  function getSessionRequestErrorTags (sessionSpan) {
236
- const tags = sessionSpan?.context()._tags
236
+ const tags = sessionSpan?.context()?.getTags?.()
237
237
  const sessionRequestErrorTags = {}
238
238
  if (!tags || typeof tags !== 'object') return {}
239
239
  if (tags[DD_CI_LIBRARY_CONFIGURATION_ERROR_SETTINGS] === 'true') {
@@ -253,11 +253,11 @@ function getSessionRequestErrorTags (sessionSpan) {
253
253
 
254
254
  /**
255
255
  * Returns ITR skipping-enabled tags from a test session span for propagation to child events.
256
- * @param {{ context: () => { _tags?: Record<string, string> } } | undefined} sessionSpan
256
+ * @param {{ context: () => { getTags?: () => Record<string, string> } } | undefined} sessionSpan
257
257
  * @returns {Record<string, string>}
258
258
  */
259
259
  function getSessionItrSkippingEnabledTags (sessionSpan) {
260
- const tags = sessionSpan?.context()._tags
260
+ const tags = sessionSpan?.context()?.getTags?.()
261
261
  if (!tags || typeof tags !== 'object') return {}
262
262
  if (tags[TEST_ITR_SKIPPING_ENABLED] !== undefined) {
263
263
  return {
@@ -418,6 +418,12 @@ module.exports = {
418
418
  ITR_CORRELATION_ID,
419
419
  addIntelligentTestRunnerSpanTags,
420
420
  getCoveredFilenamesFromCoverage,
421
+ getCoveredFilesFromCoverage,
422
+ getExecutableFilesFromCoverage,
423
+ getRelativeCoverageFiles,
424
+ getLineCoverageBitmap,
425
+ applySkippedCoverageToCoverage,
426
+ getTestCoverageLinesPercentage,
421
427
  resetCoverage,
422
428
  mergeCoverage,
423
429
  fromCoverageMapToCoverage,
@@ -952,7 +958,6 @@ function getTestLevelCommonTags (command, testFrameworkVersion, testFramework) {
952
958
  return {
953
959
  [TEST_FRAMEWORK_VERSION]: testFrameworkVersion,
954
960
  [LIBRARY_VERSION]: ddTraceVersion,
955
- [TEST_COMMAND]: command,
956
961
  [TEST_TYPE]: getTestTypeFromFramework(testFramework),
957
962
  }
958
963
  }
@@ -1030,15 +1035,233 @@ function addIntelligentTestRunnerSpanTags (
1030
1035
  }
1031
1036
 
1032
1037
  function getCoveredFilenamesFromCoverage (coverage) {
1033
- const coverageMap = istanbul.createCoverageMap(coverage)
1038
+ return getCoveredFilesFromCoverage(coverage).map(({ filename }) => filename)
1039
+ }
1034
1040
 
1035
- return coverageMap
1036
- .files()
1037
- .filter(filename => {
1038
- const fileCoverage = coverageMap.fileCoverageFor(filename)
1039
- const lineCoverage = fileCoverage.getLineCoverage()
1040
- return Object.entries(lineCoverage).some(([, numExecutions]) => !!numExecutions)
1041
- })
1041
+ function getCoverageMap (coverage) {
1042
+ if (coverage?.files && coverage?.fileCoverageFor) {
1043
+ return coverage
1044
+ }
1045
+ return istanbul.createCoverageMap(coverage)
1046
+ }
1047
+
1048
+ function getCoveredFilesFromCoverage (coverage) {
1049
+ const coverageMap = getCoverageMap(coverage)
1050
+ const coverageFiles = []
1051
+
1052
+ for (const filename of coverageMap.files()) {
1053
+ const fileCoverage = coverageMap.fileCoverageFor(filename)
1054
+ const bitmap = getLineCoverageBitmap(fileCoverage.getLineCoverage(), true)
1055
+ if (bitmap) {
1056
+ coverageFiles.push({ filename, bitmap })
1057
+ }
1058
+ }
1059
+
1060
+ return coverageFiles
1061
+ }
1062
+
1063
+ function getExecutableFilesFromCoverage (coverage) {
1064
+ const coverageMap = getCoverageMap(coverage)
1065
+ const coverageFiles = []
1066
+
1067
+ for (const filename of coverageMap.files()) {
1068
+ const fileCoverage = coverageMap.fileCoverageFor(filename)
1069
+ const bitmap = getLineCoverageBitmap(fileCoverage.getLineCoverage())
1070
+ if (bitmap) {
1071
+ coverageFiles.push({ filename, bitmap })
1072
+ }
1073
+ }
1074
+
1075
+ return coverageFiles
1076
+ }
1077
+
1078
+ function getRelativeCoverageFiles (coverageFiles, rootDir) {
1079
+ return coverageFiles.map(({ filename, bitmap }) => ({
1080
+ filename: getTestSuitePath(filename, rootDir),
1081
+ bitmap,
1082
+ }))
1083
+ }
1084
+
1085
+ function getLineCoverageBitmap (lineCoverage, onlyCoveredLines = false) {
1086
+ let maxLine = 0
1087
+ const lines = []
1088
+
1089
+ for (const [line, hits] of Object.entries(lineCoverage)) {
1090
+ if (onlyCoveredLines && !hits) continue
1091
+
1092
+ const lineNumber = Number(line)
1093
+ if (!Number.isSafeInteger(lineNumber) || lineNumber <= 0) continue
1094
+
1095
+ lines.push(lineNumber)
1096
+ if (lineNumber > maxLine) {
1097
+ maxLine = lineNumber
1098
+ }
1099
+ }
1100
+
1101
+ if (maxLine === 0) return
1102
+
1103
+ const bitmap = Buffer.alloc(Math.ceil((maxLine + 1) / 8))
1104
+ for (const lineNumber of lines) {
1105
+ bitmap[lineNumber >> 3] |= 1 << (lineNumber % 8)
1106
+ }
1107
+
1108
+ return bitmap
1109
+ }
1110
+
1111
+ function mergeCoverageBitmaps (targetBitmap, bitmap) {
1112
+ if (!targetBitmap) {
1113
+ return Buffer.from(bitmap)
1114
+ }
1115
+
1116
+ if (targetBitmap.length < bitmap.length) {
1117
+ const biggerBitmap = Buffer.alloc(bitmap.length)
1118
+ targetBitmap.copy(biggerBitmap)
1119
+ targetBitmap = biggerBitmap
1120
+ }
1121
+
1122
+ for (let i = 0; i < bitmap.length; i++) {
1123
+ targetBitmap[i] |= bitmap[i]
1124
+ }
1125
+
1126
+ return targetBitmap
1127
+ }
1128
+
1129
+ function countBitmapBits (bitmap) {
1130
+ let count = 0
1131
+
1132
+ for (const byte of bitmap) {
1133
+ let value = byte
1134
+ while (value) {
1135
+ value &= value - 1
1136
+ count++
1137
+ }
1138
+ }
1139
+
1140
+ return count
1141
+ }
1142
+
1143
+ function countCoveredExecutableBits (coveredBitmap, executableBitmap) {
1144
+ if (!coveredBitmap) return 0
1145
+
1146
+ let count = 0
1147
+ const length = Math.min(coveredBitmap.length, executableBitmap.length)
1148
+
1149
+ for (let i = 0; i < length; i++) {
1150
+ let value = coveredBitmap[i] & executableBitmap[i]
1151
+ while (value) {
1152
+ value &= value - 1
1153
+ count++
1154
+ }
1155
+ }
1156
+
1157
+ return count
1158
+ }
1159
+
1160
+ function getCoverageFileBitmap (bitmap) {
1161
+ if (!bitmap) return
1162
+ if (Buffer.isBuffer(bitmap)) return bitmap
1163
+ if (ArrayBuffer.isView(bitmap)) {
1164
+ return Buffer.from(bitmap.buffer, bitmap.byteOffset, bitmap.byteLength)
1165
+ }
1166
+ if (typeof bitmap === 'string') {
1167
+ return Buffer.from(bitmap, 'base64')
1168
+ }
1169
+ }
1170
+
1171
+ function addCoverageFilesToMap (files, targetMap, rootDir) {
1172
+ for (const file of files) {
1173
+ const bitmap = getCoverageFileBitmap(file.bitmap)
1174
+ if (!bitmap) continue
1175
+
1176
+ const filename = rootDir ? getTestSuitePath(file.filename, rootDir) : file.filename
1177
+ targetMap.set(filename, mergeCoverageBitmaps(targetMap.get(filename), bitmap))
1178
+ }
1179
+ }
1180
+
1181
+ function addSkippedCoverageToMap (skippedCoverage, targetMap) {
1182
+ if (!skippedCoverage) return
1183
+
1184
+ for (const [filename, bitmap] of Object.entries(skippedCoverage)) {
1185
+ const coverageBitmap = getCoverageFileBitmap(bitmap)
1186
+ if (!coverageBitmap) continue
1187
+ targetMap.set(filename, mergeCoverageBitmaps(targetMap.get(filename), coverageBitmap))
1188
+ }
1189
+ }
1190
+
1191
+ function hasSkippedCoverage (skippedCoverage) {
1192
+ return skippedCoverage && typeof skippedCoverage === 'object' && Object.keys(skippedCoverage).length > 0
1193
+ }
1194
+
1195
+ function getTestCoverageLinesPercentage (coverage, skippedCoverage, rootDir) {
1196
+ const executableLinesByFile = new Map()
1197
+ const coveredLinesByFile = new Map()
1198
+
1199
+ addCoverageFilesToMap(getExecutableFilesFromCoverage(coverage), executableLinesByFile, rootDir)
1200
+ addCoverageFilesToMap(getCoveredFilesFromCoverage(coverage), coveredLinesByFile, rootDir)
1201
+ addSkippedCoverageToMap(skippedCoverage, coveredLinesByFile)
1202
+
1203
+ let totalExecutableLines = 0
1204
+ let totalCoveredLines = 0
1205
+
1206
+ for (const [filename, executableLines] of executableLinesByFile) {
1207
+ totalExecutableLines += countBitmapBits(executableLines)
1208
+ totalCoveredLines += countCoveredExecutableBits(coveredLinesByFile.get(filename), executableLines)
1209
+ }
1210
+
1211
+ return totalExecutableLines === 0 ? 0 : Math.floor((totalCoveredLines / totalExecutableLines) * 10_000) / 100
1212
+ }
1213
+
1214
+ function isLineCoveredByBitmap (bitmap, line) {
1215
+ if (!Number.isSafeInteger(line) || line <= 0) return false
1216
+
1217
+ const byteIndex = line >> 3
1218
+ return byteIndex < bitmap.length && !!(bitmap[byteIndex] & (1 << (line % 8)))
1219
+ }
1220
+
1221
+ function getSkippedCoverageByFilename (skippedCoverage) {
1222
+ const skippedCoverageByFilename = new Map()
1223
+ addSkippedCoverageToMap(skippedCoverage, skippedCoverageByFilename)
1224
+ return skippedCoverageByFilename
1225
+ }
1226
+
1227
+ function applySkippedCoverageToFileCoverage (fileCoverage, skippedBitmap) {
1228
+ let updated = false
1229
+ for (const [statementId, statementLocation] of Object.entries(fileCoverage.data.statementMap)) {
1230
+ const startLine = statementLocation?.start?.line
1231
+ if (!isLineCoveredByBitmap(skippedBitmap, startLine)) continue
1232
+ if (fileCoverage.data.s[statementId] > 0) continue
1233
+
1234
+ fileCoverage.data.s[statementId] = 1
1235
+ updated = true
1236
+ }
1237
+ return updated
1238
+ }
1239
+
1240
+ /**
1241
+ * Applies backend skipped-suite coverage to an Istanbul coverage map.
1242
+ * @param {object} coverage
1243
+ * @param {object} skippedCoverage
1244
+ * @param {string} [rootDir]
1245
+ * @returns {boolean}
1246
+ */
1247
+ function applySkippedCoverageToCoverage (coverage, skippedCoverage, rootDir) {
1248
+ if (!hasSkippedCoverage(skippedCoverage)) return false
1249
+
1250
+ const coverageMap = getCoverageMap(coverage)
1251
+ const skippedCoverageByFilename = getSkippedCoverageByFilename(skippedCoverage)
1252
+ let matched = false
1253
+
1254
+ for (const filename of coverageMap.files()) {
1255
+ const relativeFilename = rootDir ? getTestSuitePath(filename, rootDir) : filename
1256
+ const skippedBitmap = skippedCoverageByFilename.get(relativeFilename)
1257
+ if (!skippedBitmap) continue
1258
+
1259
+ const fileCoverage = coverageMap.fileCoverageFor(filename)
1260
+ applySkippedCoverageToFileCoverage(fileCoverage, skippedBitmap)
1261
+ matched = true
1262
+ }
1263
+
1264
+ return matched
1042
1265
  }
1043
1266
 
1044
1267
  function resetCoverage (coverage) {
@@ -10,16 +10,14 @@ const kinds = require('../../../../../ext/kinds')
10
10
  const { ERROR_MESSAGE } = require('../../constants')
11
11
  const TracingPlugin = require('../tracing')
12
12
  const { storage } = require('../../../../datadog-core')
13
+ const legacyStorage = storage('legacy')
13
14
  const urlFilter = require('./urlfilter')
14
15
  const { createInferredProxySpan, finishInferredProxySpan } = require('./inferred_proxy')
15
16
  const { extractURL, obfuscateQs, calculateHttpEndpoint } = require('./url')
16
17
 
17
- let extractIp
18
-
19
18
  const WEB = types.WEB
20
19
  const SERVER = kinds.SERVER
21
20
  const RESOURCE_NAME = tags.RESOURCE_NAME
22
- const SERVICE_NAME = tags.SERVICE_NAME
23
21
  const SPAN_TYPE = tags.SPAN_TYPE
24
22
  const SPAN_KIND = tags.SPAN_KIND
25
23
  const ERROR = tags.ERROR
@@ -35,7 +33,6 @@ const HTTP_CLIENT_IP = tags.HTTP_CLIENT_IP
35
33
  const MANUAL_DROP = tags.MANUAL_DROP
36
34
 
37
35
  const contexts = new WeakMap()
38
- const ends = new WeakMap()
39
36
 
40
37
  // TODO: change this to no longer rely on creating a dummy plugin to be able to access startSpan
41
38
  function createWebPlugin (tracer, config = {}) {
@@ -67,7 +64,9 @@ const web = {
67
64
  const middleware = getMiddlewareSetting(config)
68
65
  const queryStringObfuscation = getQsObfuscator(config)
69
66
 
70
- extractIp = config.clientIpEnabled && require('./ip_extractor').extractIp
67
+ const extractIp = config.clientIpEnabled
68
+ ? require('./ip_extractor').extractIp
69
+ : undefined
71
70
 
72
71
  return {
73
72
  ...config,
@@ -77,6 +76,7 @@ const web = {
77
76
  filter,
78
77
  middleware,
79
78
  queryStringObfuscation,
79
+ extractIp,
80
80
  }
81
81
  },
82
82
 
@@ -87,7 +87,7 @@ const web = {
87
87
  if (!span) return
88
88
 
89
89
  span.context()._name = `${name}.request`
90
- span.context()._tags.component = name
90
+ span.context().setTag('component', name)
91
91
  span._integrationName = name
92
92
 
93
93
  web.setConfig(req, config)
@@ -105,7 +105,7 @@ const web = {
105
105
  }
106
106
 
107
107
  if (config.service) {
108
- span.setTag(SERVICE_NAME, config.service)
108
+ web.plugin.setServiceName(span, config.service)
109
109
  }
110
110
 
111
111
  analyticsSampler.sample(span, config.measured, true)
@@ -126,7 +126,6 @@ const web = {
126
126
  context.tracer = tracer
127
127
  context.span = span
128
128
  context.res = res
129
- context.store = storage('legacy').getStore()
130
129
 
131
130
  this.setConfig(req, config)
132
131
  addRequestTags(context, this.TYPE)
@@ -204,7 +203,7 @@ const web = {
204
203
  startServerlessSpanWithInferredProxy (tracer, config, name, req, traceCtx) {
205
204
  const headers = req.headers
206
205
  const reqCtx = contexts.get(req)
207
- const store = storage('legacy').getStore()
206
+ const store = legacyStorage.getStore()
208
207
  const pubsubSpan = store?.span?._name === 'pubsub.push.receive' ? store.span : null
209
208
 
210
209
  let childOf = pubsubSpan || tracer.extract(FORMAT_HTTP_HEADERS, headers)
@@ -225,9 +224,11 @@ const web = {
225
224
  const context = contexts.get(req)
226
225
  const { span, inferredProxySpan, error } = context
227
226
 
228
- const spanHasExistingError = span.context()._tags.error || span.context()._tags[ERROR_MESSAGE]
227
+ const spanContext = span.context()
228
+ const spanHasExistingError = spanContext.getTag('error') || spanContext.getTag(ERROR_MESSAGE)
229
229
  const inferredSpanContext = inferredProxySpan?.context()
230
- const inferredSpanHasExistingError = inferredSpanContext?._tags.error || inferredSpanContext?._tags[ERROR_MESSAGE]
230
+ const inferredSpanHasExistingError = inferredSpanContext?.getTag('error') ||
231
+ inferredSpanContext?.getTag(ERROR_MESSAGE)
231
232
 
232
233
  const isValidStatusCode = context.config.validateStatus(statusCode)
233
234
 
@@ -266,7 +267,16 @@ const web = {
266
267
 
267
268
  if (context.finished && !req.stream) return
268
269
 
270
+ // `addRequestTags` is idempotent: in the normal HTTP path it ran during
271
+ // `web.startSpan`. Serverless callers (e.g. Azure Functions) skip
272
+ // `web.startSpan` and rely on this call to do the request-side work.
269
273
  addRequestTags(context, spanType)
274
+ // Configured-header tagging runs at finish time. Framework plugins
275
+ // (connect, express, ...) install their own config via `setFramework`
276
+ // after `web.startSpan` has already locked the http-plugin config in;
277
+ // tagging earlier would use the http-plugin's `headers` list and drop
278
+ // the framework's.
279
+ addRequestHeaders(context)
270
280
  addResponseTags(context)
271
281
 
272
282
  context.config.hooks.request(context.span, req, res)
@@ -293,11 +303,18 @@ const web = {
293
303
  const writeHead = res.writeHead
294
304
 
295
305
  return function (statusCode, statusMessage, headers) {
296
- headers = typeof statusMessage === 'string' ? headers : statusMessage
297
- headers = { ...res.getHeaders(), ...headers }
298
-
299
- if (req.method.toLowerCase() === 'options' && isOriginAllowed(req, headers)) {
300
- addAllowHeaders(req, res, headers)
306
+ // CORS preflight tagging only matters for OPTIONS requests. Skip the
307
+ // getHeaders() spread + isOriginAllowed work entirely for the common
308
+ // GET / POST / etc. case. Node's http module passes `req.method`
309
+ // through unchanged, so all standard methods are uppercase; the
310
+ // `toLowerCase` fallback covers any non-standard caller.
311
+ if (req.method === 'OPTIONS' || req.method.toLowerCase() === 'options') {
312
+ headers = typeof statusMessage === 'string' ? headers : statusMessage
313
+ headers = { ...res.getHeaders(), ...headers }
314
+
315
+ if (isOriginAllowed(req, headers)) {
316
+ addAllowHeaders(req, res, headers)
317
+ }
301
318
  }
302
319
 
303
320
  return writeHead.apply(this, arguments)
@@ -306,34 +323,6 @@ const web = {
306
323
  getContext (req) {
307
324
  return contexts.get(req)
308
325
  },
309
- wrapRes (context, req, res, end) {
310
- return function (...args) {
311
- web.finishAll(context)
312
-
313
- return end.apply(res, args)
314
- }
315
- },
316
- wrapEnd (context) {
317
- const req = context.req
318
- const res = context.res
319
- const end = res.end
320
-
321
- res.writeHead = web.wrapWriteHead(context)
322
-
323
- ends.set(res, this.wrapRes(context, req, res, end))
324
-
325
- Object.defineProperty(res, 'end', {
326
- configurable: true,
327
- get () {
328
- return ends.get(this)
329
- },
330
- set (value) {
331
- ends.set(this, function (...args) {
332
- return storage('legacy').run(context.store, value, ...args)
333
- })
334
- },
335
- })
336
- },
337
326
  setRouteOrEndpointTag (req) {
338
327
  const context = contexts.get(req)
339
328
 
@@ -379,6 +368,16 @@ function splitHeader (str) {
379
368
 
380
369
  function addRequestTags (context, spanType) {
381
370
  const { req, span, inferredProxySpan, config } = context
371
+ const spanContext = span.context()
372
+
373
+ // Idempotency guard. `addRequestTags` runs in `web.startSpan` for the
374
+ // normal HTTP path and again in `web.finishSpan`; without this guard the
375
+ // second call would re-extract the URL, re-obfuscate the query string,
376
+ // and re-publish five `tagsUpdateCh` events with the same values. The
377
+ // serverless path skips `startSpan` and lands here first, in which case
378
+ // HTTP_URL is unset and the work runs normally.
379
+ if (spanContext.hasTag(HTTP_URL)) return
380
+
382
381
  const url = extractURL(req)
383
382
  const type = spanType ?? WEB
384
383
 
@@ -391,8 +390,8 @@ function addRequestTags (context, spanType) {
391
390
  })
392
391
 
393
392
  // if client ip has already been set by appsec, no need to run it again
394
- if (extractIp && !span.context()._tags.hasOwnProperty(HTTP_CLIENT_IP)) {
395
- const clientIp = extractIp(config, req)
393
+ if (config.extractIp && !spanContext.hasTag(HTTP_CLIENT_IP)) {
394
+ const clientIp = config.extractIp(config, req)
396
395
 
397
396
  if (clientIp) {
398
397
  span.setTag(HTTP_CLIENT_IP, clientIp)
@@ -410,8 +409,6 @@ function addRequestTags (context, spanType) {
410
409
  if (securityTest !== undefined) {
411
410
  span.setTag(`${HTTP_REQUEST_HEADERS}.x-datadog-security-test`, securityTest)
412
411
  }
413
-
414
- addHeaders(context)
415
412
  }
416
413
 
417
414
  function addResponseTags (context) {
@@ -426,14 +423,26 @@ function addResponseTags (context) {
426
423
  [HTTP_STATUS_CODE]: res.statusCode,
427
424
  })
428
425
 
426
+ addResponseHeaders(context)
427
+
429
428
  web.addStatusError(req, res.statusCode)
430
429
  }
431
430
 
432
431
  function applyRouteOrEndpointTag (context) {
433
432
  const { paths, span, config } = context
434
433
  if (!span) return
435
- const tags = span.context()._tags
436
- const route = paths.join('')
434
+ const spanContext = span.context()
435
+
436
+ // AppSec calls `web.setRouteOrEndpointTag` from a pre-finish hook so the
437
+ // route/endpoint tags are available for API Security sampling, and the
438
+ // normal finish-time path runs this again. Either tag being present
439
+ // means the work has already been done; paths are stable between the
440
+ // two calls, so the second pass has nothing to add.
441
+ if (spanContext.hasTag(HTTP_ROUTE) || spanContext.hasTag(HTTP_ENDPOINT)) return
442
+
443
+ // Skip the `Array.prototype.join` builtin in the empty / single-segment
444
+ // cases; `paths[0]` covers both (`undefined` is falsy for the empty case).
445
+ const route = paths.length > 1 ? paths.join('') : paths[0]
437
446
 
438
447
  if (route) {
439
448
  // Use http.route from trusted framework instrumentation.
@@ -441,44 +450,49 @@ function applyRouteOrEndpointTag (context) {
441
450
  return
442
451
  }
443
452
 
444
- if (!config.resourceRenamingEnabled || tags[HTTP_ENDPOINT]) {
445
- return
446
- }
453
+ if (!config.resourceRenamingEnabled) return
447
454
 
448
455
  // Route is unavailable, compute http.endpoint once.
449
- const url = tags[HTTP_URL]
456
+ const url = spanContext.getTag(HTTP_URL)
450
457
  const endpoint = url ? calculateHttpEndpoint(url) : '/'
451
458
  span.setTag(HTTP_ENDPOINT, endpoint)
452
459
  }
453
460
 
454
461
  function addResourceTag (context) {
455
462
  const { req, span } = context
456
- const tags = span.context()._tags
463
+ const spanContext = span.context()
457
464
 
458
- if (tags[RESOURCE_NAME]) return
465
+ if (spanContext.getTag(RESOURCE_NAME)) return
459
466
 
460
- const resource = [req.method, tags[HTTP_ROUTE]]
467
+ const resource = [req.method, spanContext.getTag(HTTP_ROUTE)]
461
468
  .filter(Boolean)
462
469
  .join(' ')
463
470
 
464
471
  span.setTag(RESOURCE_NAME, resource)
465
472
  }
466
473
 
467
- function addHeaders (context) {
468
- const { req, res, config, span, inferredProxySpan } = context
474
+ function addRequestHeaders (context) {
475
+ const { req, config, span, inferredProxySpan } = context
469
476
 
470
477
  for (const [key, tag] of config.headers) {
471
478
  const reqHeader = req.headers[key]
472
- const resHeader = res.getHeader(key)
473
-
474
479
  if (reqHeader) {
475
- span.setTag(tag || `${HTTP_REQUEST_HEADERS}.${key}`, reqHeader)
476
- inferredProxySpan?.setTag(tag || `${HTTP_REQUEST_HEADERS}.${key}`, reqHeader)
480
+ const tagName = tag || `${HTTP_REQUEST_HEADERS}.${key}`
481
+ span.setTag(tagName, reqHeader)
482
+ inferredProxySpan?.setTag(tagName, reqHeader)
477
483
  }
484
+ }
485
+ }
486
+
487
+ function addResponseHeaders (context) {
488
+ const { res, config, span, inferredProxySpan } = context
478
489
 
490
+ for (const [key, tag] of config.headers) {
491
+ const resHeader = res.getHeader(key)
479
492
  if (resHeader) {
480
- span.setTag(tag || `${HTTP_RESPONSE_HEADERS}.${key}`, resHeader)
481
- inferredProxySpan?.setTag(tag || `${HTTP_RESPONSE_HEADERS}.${key}`, resHeader)
493
+ const tagName = tag || `${HTTP_RESPONSE_HEADERS}.${key}`
494
+ span.setTag(tagName, resHeader)
495
+ inferredProxySpan?.setTag(tagName, resHeader)
482
496
  }
483
497
  }
484
498
  }
@@ -125,7 +125,7 @@ class PrioritySampler {
125
125
 
126
126
  log.trace(span, auto)
127
127
 
128
- const tag = this._getPriorityFromTags(context._tags, context)
128
+ const tag = this._getPriorityFromTags(context.getTags(), context)
129
129
 
130
130
  if (this.validate(tag)) {
131
131
  context._sampling.priority = tag
@@ -300,7 +300,7 @@ class PrioritySampler {
300
300
  * @returns {SamplingPriority}
301
301
  */
302
302
  #getPriorityByAgent (context) {
303
- const key = `service:${context._tags[SERVICE_NAME]},env:${this._env}`
303
+ const key = `service:${context.getTag(SERVICE_NAME)},env:${this._env}`
304
304
  // TODO: Change underscored properties to private ones.
305
305
  const sampler = this._samplers[key] || this._samplers[DEFAULT_KEY]
306
306
 
@@ -17,7 +17,7 @@ function findWebSpan (startedSpans, spanId) {
17
17
  const ispan = startedSpans[i]
18
18
  const context = ispan.context()
19
19
  if (context._spanId === spanId) {
20
- if (isWebServerSpan(context._tags)) {
20
+ if (isWebServerSpan(context.getTags())) {
21
21
  return true
22
22
  }
23
23
  spanId = context._parentId
@@ -268,7 +268,7 @@ class Profiler extends EventEmitter {
268
268
 
269
269
  #onSpanFinish (span) {
270
270
  const context = span.context()
271
- const tags = context._tags
271
+ const tags = context.getTags()
272
272
  if (!isWebServerSpan(tags)) return
273
273
 
274
274
  const endpointName = endpointNameFromTags(tags)
@@ -247,6 +247,7 @@ class NativeWallProfiler {
247
247
  // context -- we simply can't tell which one it might've been across all
248
248
  // possible async context frames.
249
249
  if (this.#asyncContextFrameEnabled) {
250
+ const current = this.#pprof.time.getContext()
250
251
  if (this.#customLabelsActive) {
251
252
  // Custom labels may be active in this async context. The current CPED
252
253
  // context could be a 2-element array [profilingContext, customLabels].
@@ -254,7 +255,6 @@ class NativeWallProfiler {
254
255
  // This flag is monotonic (once set, stays true) because async
255
256
  // continuations from runWithLabels can fire at any time after the
256
257
  // synchronous runWithLabels call has returned.
257
- const current = this.#pprof.time.getContext()
258
258
  if (Array.isArray(current)) {
259
259
  if (current[0] !== sampleContext) {
260
260
  this.#pprof.time.setContext([sampleContext, current[1]])
@@ -262,7 +262,13 @@ class NativeWallProfiler {
262
262
  } else if (current !== sampleContext) {
263
263
  this.#pprof.time.setContext(sampleContext)
264
264
  }
265
- } else {
265
+ // Every setContext() call in ACF mode allocates a fresh contextHolder
266
+ // (a node::ObjectWrap with its own v8::Global<v8::Value>) in the native
267
+ // profiler. Skip the call if the CPED already holds this sampleContext,
268
+ // which is the common case when the same span is repeatedly activated:
269
+ // #getProfilingContext caches profilingContext on span[ProfilingContext],
270
+ // so identity comparison short-circuits.
271
+ } else if (current !== sampleContext) {
266
272
  this.#pprof.time.setContext(sampleContext)
267
273
  }
268
274
  } else {
@@ -294,7 +300,7 @@ class NativeWallProfiler {
294
300
 
295
301
  let webTags
296
302
  if (this.#endpointCollectionEnabled) {
297
- const tags = context._tags
303
+ const tags = context.getTags()
298
304
  if (isWebServerSpan(tags)) {
299
305
  webTags = tags
300
306
  } else {
@@ -333,7 +339,7 @@ class NativeWallProfiler {
333
339
  if (!this.#started) return
334
340
  const profilingContext = span[ProfilingContext]
335
341
  if (profilingContext === undefined || profilingContext.webTags !== undefined) return
336
- const tags = span.context()._tags
342
+ const tags = span.context().getTags()
337
343
  if (isWebServerSpan(tags)) {
338
344
  profilingContext.webTags = tags
339
345
  }