dd-trace 2.4.2 → 2.7.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 (96) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/ci/init.js +6 -0
  3. package/ci/jest/env.js +16 -3
  4. package/ext/exporters.d.ts +2 -1
  5. package/ext/exporters.js +2 -1
  6. package/index.d.ts +17 -8
  7. package/package.json +20 -23
  8. package/packages/datadog-instrumentations/index.js +14 -0
  9. package/packages/datadog-instrumentations/src/connect.js +111 -0
  10. package/packages/datadog-instrumentations/src/cypress.js +8 -0
  11. package/packages/datadog-instrumentations/src/express.js +27 -0
  12. package/packages/datadog-instrumentations/src/fastify.js +187 -0
  13. package/packages/datadog-instrumentations/src/find-my-way.js +30 -0
  14. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +100 -0
  15. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  16. package/packages/datadog-instrumentations/src/jest.js +175 -0
  17. package/packages/datadog-instrumentations/src/kafkajs.js +112 -0
  18. package/packages/datadog-instrumentations/src/knex.js +20 -0
  19. package/packages/datadog-instrumentations/src/koa.js +159 -0
  20. package/packages/datadog-instrumentations/src/limitd-client.js +21 -0
  21. package/packages/datadog-instrumentations/src/oracledb.js +128 -0
  22. package/packages/datadog-instrumentations/src/paperplane.js +77 -0
  23. package/packages/datadog-instrumentations/src/pg.js +2 -2
  24. package/packages/datadog-instrumentations/src/restify.js +58 -0
  25. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  26. package/packages/datadog-instrumentations/src/router.js +177 -0
  27. package/packages/datadog-plugin-aws-sdk/src/helpers.js +4 -4
  28. package/packages/datadog-plugin-aws-sdk/src/index.js +1 -1
  29. package/packages/datadog-plugin-connect/src/index.js +10 -114
  30. package/packages/datadog-plugin-cucumber/src/index.js +16 -16
  31. package/packages/datadog-plugin-cypress/src/index.js +10 -5
  32. package/packages/datadog-plugin-cypress/src/plugin.js +18 -17
  33. package/packages/datadog-plugin-dns/src/index.js +12 -1
  34. package/packages/datadog-plugin-express/src/index.js +11 -25
  35. package/packages/datadog-plugin-fastify/src/index.js +17 -4
  36. package/packages/datadog-plugin-find-my-way/src/index.js +20 -0
  37. package/packages/datadog-plugin-fs/src/index.js +2 -0
  38. package/packages/datadog-plugin-google-cloud-pubsub/src/index.js +56 -111
  39. package/packages/datadog-plugin-http/src/server.js +2 -10
  40. package/packages/datadog-plugin-jest/src/index.js +101 -3
  41. package/packages/datadog-plugin-jest/src/util.js +1 -29
  42. package/packages/datadog-plugin-kafkajs/src/index.js +64 -90
  43. package/packages/datadog-plugin-koa/src/index.js +12 -164
  44. package/packages/datadog-plugin-mocha/src/index.js +14 -15
  45. package/packages/datadog-plugin-oracledb/src/index.js +34 -100
  46. package/packages/datadog-plugin-paperplane/src/index.js +14 -100
  47. package/packages/datadog-plugin-paperplane/src/logger.js +11 -0
  48. package/packages/datadog-plugin-paperplane/src/server.js +24 -0
  49. package/packages/datadog-plugin-restify/src/index.js +13 -75
  50. package/packages/datadog-plugin-router/src/index.js +67 -164
  51. package/packages/datadog-plugin-web/src/index.js +20 -0
  52. package/packages/dd-trace/lib/version.js +1 -1
  53. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +34 -12
  54. package/packages/dd-trace/src/appsec/index.js +7 -3
  55. package/packages/dd-trace/src/appsec/recommended.json +15 -5
  56. package/packages/dd-trace/src/appsec/reporter.js +33 -3
  57. package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
  58. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +32 -0
  59. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +51 -0
  60. package/packages/dd-trace/src/config.js +33 -4
  61. package/packages/dd-trace/src/encode/0.4.js +0 -1
  62. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +193 -0
  63. package/packages/dd-trace/src/encode/tags-processors.js +116 -0
  64. package/packages/dd-trace/src/exporter.js +3 -0
  65. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  66. package/packages/dd-trace/src/exporters/agent/writer.js +7 -32
  67. package/packages/dd-trace/src/exporters/{agent → common}/docker.js +0 -0
  68. package/packages/dd-trace/src/exporters/common/request.js +83 -0
  69. package/packages/dd-trace/src/exporters/common/writer.js +36 -0
  70. package/packages/dd-trace/src/exporters/{agent/scheduler.js → scheduler.js} +0 -0
  71. package/packages/dd-trace/src/format.js +9 -5
  72. package/packages/dd-trace/src/instrumenter.js +3 -0
  73. package/packages/dd-trace/src/pkg.js +11 -6
  74. package/packages/dd-trace/src/plugin_manager.js +13 -7
  75. package/packages/dd-trace/src/plugins/index.js +1 -2
  76. package/packages/dd-trace/src/plugins/log_plugin.js +8 -4
  77. package/packages/dd-trace/src/plugins/plugin.js +8 -0
  78. package/packages/dd-trace/src/plugins/util/test.js +79 -1
  79. package/packages/dd-trace/src/plugins/util/web.js +41 -12
  80. package/packages/dd-trace/src/profiling/config.js +8 -8
  81. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  82. package/packages/dd-trace/src/profiling/index.js +4 -4
  83. package/packages/dd-trace/src/profiling/profilers/{heap.js → space.js} +2 -2
  84. package/packages/dd-trace/src/profiling/profilers/{cpu.js → wall.js} +3 -3
  85. package/packages/dd-trace/src/proxy.js +2 -0
  86. package/packages/dd-trace/src/span_processor.js +4 -1
  87. package/packages/dd-trace/src/telemetry.js +187 -0
  88. package/scripts/install_plugin_modules.js +1 -0
  89. package/packages/datadog-plugin-fastify/src/fastify.js +0 -198
  90. package/packages/datadog-plugin-fastify/src/find-my-way.js +0 -37
  91. package/packages/datadog-plugin-jest/src/jest-environment.js +0 -272
  92. package/packages/datadog-plugin-jest/src/jest-jasmine2.js +0 -185
  93. package/packages/datadog-plugin-knex/src/index.js +0 -23
  94. package/packages/datadog-plugin-limitd-client/src/index.js +0 -30
  95. package/packages/dd-trace/src/exporters/agent/request.js +0 -86
  96. package/scripts/postpublish.js +0 -24
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "2.2",
3
3
  "metadata": {
4
- "rules_version": "1.3.0"
4
+ "rules_version": "1.3.1"
5
5
  },
6
6
  "rules": [
7
7
  {
@@ -3040,7 +3040,9 @@
3040
3040
  "operator": "match_regex"
3041
3041
  }
3042
3042
  ],
3043
- "transformers": []
3043
+ "transformers": [
3044
+ "keys_only"
3045
+ ]
3044
3046
  },
3045
3047
  {
3046
3048
  "id": "crs-942-360",
@@ -4097,15 +4099,23 @@
4097
4099
  "parameters": {
4098
4100
  "inputs": [
4099
4101
  {
4100
- "address": "server.request.headers.no_cookies"
4102
+ "address": "server.request.query"
4103
+ },
4104
+ {
4105
+ "address": "server.request.body"
4106
+ },
4107
+ {
4108
+ "address": "server.request.path_params"
4101
4109
  }
4102
4110
  ],
4103
- "regex": "\\$(eq|ne|lte?|gte?|n?in)\\b"
4111
+ "regex": "^\\$(eq|ne|(l|g)te?|n?in|not|(n|x|)or|and|regex|where|expr|exists)$"
4104
4112
  },
4105
4113
  "operator": "match_regex"
4106
4114
  }
4107
4115
  ],
4108
- "transformers": []
4116
+ "transformers": [
4117
+ "keys_only"
4118
+ ]
4109
4119
  },
4110
4120
  {
4111
4121
  "id": "sqr-000-008",
@@ -35,6 +35,8 @@ const RESPONSE_HEADERS_PASSLIST = [
35
35
  'content-type'
36
36
  ]
37
37
 
38
+ const metricsQueue = new Map()
39
+
38
40
  function resolveHTTPRequest (context) {
39
41
  if (!context) return {}
40
42
 
@@ -82,6 +84,24 @@ function formatHeaderName (name) {
82
84
  .toLowerCase()
83
85
  }
84
86
 
87
+ function reportMetrics (metrics, store) {
88
+ const req = store && store.get('req')
89
+ const topSpan = web.root(req)
90
+ if (!topSpan) return false
91
+
92
+ if (metrics.duration) {
93
+ topSpan.setTag('_dd.appsec.waf.duration', metrics.duration)
94
+ }
95
+
96
+ if (metrics.durationExt) {
97
+ topSpan.setTag('_dd.appsec.waf.duration_ext', metrics.durationExt)
98
+ }
99
+
100
+ if (metrics.rulesVersion) {
101
+ topSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
102
+ }
103
+ }
104
+
85
105
  function reportAttack (attackData, store) {
86
106
  const req = store && store.get('req')
87
107
  const topSpan = web.root(req)
@@ -129,9 +149,17 @@ function reportAttack (attackData, store) {
129
149
  topSpan.addTags(newTags)
130
150
  }
131
151
 
132
- function finishAttacks (req, context) {
152
+ function finishRequest (req, context) {
133
153
  const topSpan = web.root(req)
134
- if (!topSpan || !context) return false
154
+ if (!topSpan) return false
155
+
156
+ if (metricsQueue.size) {
157
+ topSpan.addTags(Object.fromEntries(metricsQueue))
158
+
159
+ metricsQueue.clear()
160
+ }
161
+
162
+ if (!context || !topSpan.context()._tags['appsec.event']) return false
135
163
 
136
164
  const resolvedResponse = resolveHTTPResponse(context)
137
165
 
@@ -149,11 +177,13 @@ function setRateLimit (rateLimit) {
149
177
  }
150
178
 
151
179
  module.exports = {
180
+ metricsQueue,
152
181
  resolveHTTPRequest,
153
182
  resolveHTTPResponse,
154
183
  filterHeaders,
155
184
  formatHeaderName,
185
+ reportMetrics,
156
186
  reportAttack,
157
- finishAttacks,
187
+ finishRequest,
158
188
  setRateLimit
159
189
  }
@@ -4,11 +4,11 @@ const callbacks = require('./callbacks')
4
4
 
5
5
  const appliedCallbacks = new Map()
6
6
 
7
- function applyRules (rules) {
7
+ function applyRules (rules, config) {
8
8
  if (appliedCallbacks.has(rules)) return
9
9
 
10
10
  // for now there is only WAF
11
- const callback = new callbacks.DDWAF(rules)
11
+ const callback = new callbacks.DDWAF(rules, config)
12
12
 
13
13
  appliedCallbacks.set(rules, callback)
14
14
  }
@@ -0,0 +1,32 @@
1
+ 'use strict'
2
+
3
+ const URL = require('url').URL
4
+ const Writer = require('./writer')
5
+ const Scheduler = require('../../../exporters/scheduler')
6
+
7
+ class AgentlessCiVisibilityExporter {
8
+ constructor (config) {
9
+ const { flushInterval, tags, site, url } = config
10
+ this._url = url || new URL(`https://citestcycle-intake.${site}`)
11
+ this._writer = new Writer({ url: this._url, tags })
12
+
13
+ if (flushInterval > 0) {
14
+ this._scheduler = new Scheduler(() => this._writer.flush(), flushInterval)
15
+ }
16
+ this._scheduler && this._scheduler.start()
17
+ }
18
+
19
+ export (trace) {
20
+ this._writer.append(trace)
21
+
22
+ if (!this._scheduler) {
23
+ this._writer.flush()
24
+ }
25
+ }
26
+
27
+ flush () {
28
+ this._writer.flush()
29
+ }
30
+ }
31
+
32
+ module.exports = AgentlessCiVisibilityExporter
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+ const request = require('../../../exporters/common/request')
3
+ const log = require('../../../log')
4
+
5
+ const { AgentlessCiVisibilityEncoder } = require('../../../encode/agentless-ci-visibility')
6
+ const BaseWriter = require('../../../exporters/common/writer')
7
+
8
+ class Writer extends BaseWriter {
9
+ constructor ({ url, tags }) {
10
+ super(...arguments)
11
+ const { 'runtime-id': runtimeId, env, service } = tags
12
+ this._url = url
13
+ this._encoder = new AgentlessCiVisibilityEncoder({ runtimeId, env, service })
14
+ }
15
+
16
+ _sendPayload (data, _, done) {
17
+ makeRequest(data, this._url, (err, res) => {
18
+ if (err) {
19
+ log.error(err)
20
+ done()
21
+ return
22
+ }
23
+ log.debug(`Response from the intake: ${res}`)
24
+ done()
25
+ })
26
+ }
27
+ }
28
+
29
+ function makeRequest (data, url, cb) {
30
+ const options = {
31
+ path: '/api/v2/citestcycle',
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Type': 'application/msgpack',
35
+ 'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY
36
+ },
37
+ timeout: 15000
38
+ }
39
+
40
+ options.protocol = url.protocol
41
+ options.hostname = url.hostname
42
+ options.port = url.port
43
+
44
+ log.debug(() => `Request to the intake: ${JSON.stringify(options)}`)
45
+
46
+ request(data, options, false, (err, res) => {
47
+ cb(err, res)
48
+ })
49
+ }
50
+
51
+ module.exports = Writer
@@ -66,6 +66,7 @@ class Config {
66
66
  process.env.DD_TRACE_URL,
67
67
  null
68
68
  )
69
+ const DD_CIVISIBILITY_AGENTLESS_URL = process.env.DD_CIVISIBILITY_AGENTLESS_URL
69
70
  const DD_SERVICE = options.service ||
70
71
  process.env.DD_SERVICE ||
71
72
  process.env.DD_SERVICE_NAME ||
@@ -90,6 +91,10 @@ class Config {
90
91
  process.env.DD_TRACE_STARTUP_LOGS,
91
92
  false
92
93
  )
94
+ const DD_TRACE_TELEMETRY_ENABLED = coalesce(
95
+ process.env.DD_TRACE_TELEMETRY_ENABLED,
96
+ true
97
+ )
93
98
  const DD_TRACE_DEBUG = coalesce(
94
99
  process.env.DD_TRACE_DEBUG,
95
100
  false
@@ -145,10 +150,29 @@ class Config {
145
150
  path.join(__dirname, 'appsec', 'recommended.json')
146
151
  )
147
152
  const DD_APPSEC_TRACE_RATE_LIMIT = coalesce(
148
- appsec.rateLimit,
149
- process.env.DD_APPSEC_TRACE_RATE_LIMIT,
153
+ parseInt(appsec.rateLimit),
154
+ parseInt(process.env.DD_APPSEC_TRACE_RATE_LIMIT),
150
155
  100
151
156
  )
157
+ const DD_APPSEC_WAF_TIMEOUT = coalesce(
158
+ parseInt(appsec.wafTimeout),
159
+ parseInt(process.env.DD_APPSEC_WAF_TIMEOUT),
160
+ 5e3 // µs
161
+ )
162
+ const DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP = coalesce(
163
+ appsec.obfuscatorKeyRegex,
164
+ process.env.DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
165
+ `(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|se\
166
+ cret)|sign(?:ed|ature)|bearer|authorization`
167
+ )
168
+ const DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP = coalesce(
169
+ appsec.obfuscatorValueRegex,
170
+ process.env.DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
171
+ `(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|to\
172
+ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:\\s*=[^;]|"\\s*:\\s*"[^"]+")|bearer\
173
+ \\s+[a-z0-9\\._\\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(?:\\.[\\w.+\\/=-]+)?\
174
+ |[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY|ssh-rsa\\s*[a-z0-9\\/\\.+]{100,}`
175
+ )
152
176
 
153
177
  const sampler = (options.experimental && options.experimental.sampler) || {}
154
178
  const ingestion = options.ingestion || {}
@@ -171,7 +195,8 @@ class Config {
171
195
  this.debug = isTrue(DD_TRACE_DEBUG)
172
196
  this.logInjection = isTrue(DD_LOGS_INJECTION)
173
197
  this.env = DD_ENV
174
- this.url = getAgentUrl(DD_TRACE_AGENT_URL, options)
198
+ this.url = DD_CIVISIBILITY_AGENTLESS_URL ? new URL(DD_CIVISIBILITY_AGENTLESS_URL)
199
+ : getAgentUrl(DD_TRACE_AGENT_URL, options)
175
200
  this.site = coalesce(options.site, process.env.DD_SITE, 'datadoghq.com')
176
201
  this.hostname = DD_AGENT_HOST || (this.url && this.url.hostname)
177
202
  this.port = String(DD_TRACE_AGENT_PORT || (this.url && this.url.port))
@@ -212,11 +237,15 @@ class Config {
212
237
  }
213
238
  this.lookup = options.lookup
214
239
  this.startupLogs = isTrue(DD_TRACE_STARTUP_LOGS)
240
+ this.telemetryEnabled = isTrue(DD_TRACE_TELEMETRY_ENABLED)
215
241
  this.protocolVersion = DD_TRACE_AGENT_PROTOCOL_VERSION
216
242
  this.appsec = {
217
243
  enabled: isTrue(DD_APPSEC_ENABLED),
218
244
  rules: DD_APPSEC_RULES,
219
- rateLimit: DD_APPSEC_TRACE_RATE_LIMIT
245
+ rateLimit: DD_APPSEC_TRACE_RATE_LIMIT,
246
+ wafTimeout: DD_APPSEC_WAF_TIMEOUT,
247
+ obfuscatorKeyRegex: DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
248
+ obfuscatorValueRegex: DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP
220
249
  }
221
250
 
222
251
  tagger.add(this.tags, {
@@ -196,7 +196,6 @@ class AgentEncoder {
196
196
 
197
197
  for (const key of keys) {
198
198
  if (typeof value[key] !== 'string' && typeof value[key] !== 'number') return
199
-
200
199
  length++
201
200
 
202
201
  this._encodeString(bytes, key)
@@ -0,0 +1,193 @@
1
+ 'use strict'
2
+ const { truncateSpan, normalizeSpan } = require('./tags-processors')
3
+ const Chunk = require('./chunk')
4
+ const { AgentEncoder } = require('./0.4')
5
+
6
+ const ENCODING_VERSION = 1
7
+
8
+ function formatSpan (span) {
9
+ return {
10
+ type: span.type === 'test' ? 'test' : 'span',
11
+ version: ENCODING_VERSION,
12
+ content: normalizeSpan(truncateSpan(span))
13
+ }
14
+ }
15
+
16
+ class AgentlessCiVisibilityEncoder extends AgentEncoder {
17
+ constructor ({ runtimeId, service, env }) {
18
+ super(...arguments)
19
+ this._events = []
20
+ this.runtimeId = runtimeId
21
+ this.service = service
22
+ this.env = env
23
+ this._traceBytes = new Chunk()
24
+ this._stringBytes = new Chunk()
25
+ this._stringCount = 0
26
+ this._stringMap = {}
27
+
28
+ // Used to keep track of the number of encoded events to update the
29
+ // length of `payload.events` when calling `makePayload`
30
+ this._eventCount = 0
31
+
32
+ this.reset()
33
+ }
34
+
35
+ _encodeEventContent (bytes, content) {
36
+ this._encodeMapPrefix(bytes, content)
37
+ if (content.type) {
38
+ this._encodeString(bytes, 'type')
39
+ this._encodeString(bytes, content.type)
40
+ }
41
+ this._encodeString(bytes, 'trace_id')
42
+ this._encodeId(bytes, content.trace_id)
43
+ this._encodeString(bytes, 'span_id')
44
+ this._encodeId(bytes, content.span_id)
45
+ this._encodeString(bytes, 'parent_id')
46
+ this._encodeId(bytes, content.parent_id)
47
+ this._encodeString(bytes, 'name')
48
+ this._encodeString(bytes, content.name)
49
+ this._encodeString(bytes, 'resource')
50
+ this._encodeString(bytes, content.resource)
51
+ this._encodeString(bytes, 'service')
52
+ this._encodeString(bytes, content.service)
53
+ this._encodeString(bytes, 'error')
54
+ this._encodeNumber(bytes, content.error)
55
+ this._encodeString(bytes, 'start')
56
+ this._encodeNumber(bytes, content.start)
57
+ this._encodeString(bytes, 'duration')
58
+ this._encodeNumber(bytes, content.duration)
59
+ this._encodeString(bytes, 'meta')
60
+ this._encodeMap(bytes, content.meta)
61
+ this._encodeString(bytes, 'metrics')
62
+ this._encodeMap(bytes, content.metrics)
63
+ }
64
+
65
+ _encodeEvent (bytes, event) {
66
+ this._encodeMapPrefix(bytes, event)
67
+ this._encodeString(bytes, 'type')
68
+ this._encodeString(bytes, event.type)
69
+
70
+ this._encodeString(bytes, 'version')
71
+ this._encodeNumber(bytes, event.version)
72
+
73
+ this._encodeString(bytes, 'content')
74
+ this._encodeEventContent(bytes, event.content)
75
+ }
76
+
77
+ _encodeNumber (bytes, value) {
78
+ if (Math.floor(value) !== value) { // float 64
79
+ return this._encodeFloat(bytes, value)
80
+ }
81
+ return this._encodeLong(bytes, value)
82
+ }
83
+
84
+ _encodeLong (bytes, value) {
85
+ const isPositive = value >= 0
86
+
87
+ const hi = isPositive ? (value / Math.pow(2, 32)) >> 0 : Math.floor(value / Math.pow(2, 32))
88
+ const lo = value >>> 0
89
+ const flag = isPositive ? 0xcf : 0xd3
90
+
91
+ const buffer = bytes.buffer
92
+ const offset = bytes.length
93
+
94
+ // int 64
95
+ bytes.reserve(9)
96
+ bytes.length += 9
97
+
98
+ buffer[offset] = flag
99
+ buffer[offset + 1] = hi >> 24
100
+ buffer[offset + 2] = hi >> 16
101
+ buffer[offset + 3] = hi >> 8
102
+ buffer[offset + 4] = hi
103
+ buffer[offset + 5] = lo >> 24
104
+ buffer[offset + 6] = lo >> 16
105
+ buffer[offset + 7] = lo >> 8
106
+ buffer[offset + 8] = lo
107
+ }
108
+
109
+ _encodeMapPrefix (bytes, map) {
110
+ const keys = Object.keys(map)
111
+ const buffer = bytes.buffer
112
+ const offset = bytes.length
113
+
114
+ bytes.reserve(5)
115
+ bytes.length += 5
116
+ buffer[offset] = 0xdf
117
+ buffer[offset + 1] = keys.length >> 24
118
+ buffer[offset + 2] = keys.length >> 16
119
+ buffer[offset + 3] = keys.length >> 8
120
+ buffer[offset + 4] = keys.length
121
+ }
122
+
123
+ _encode (bytes, trace) {
124
+ this._eventCount += trace.length
125
+ const events = trace.map(formatSpan)
126
+
127
+ for (const event of events) {
128
+ this._encodeEvent(bytes, event)
129
+ }
130
+ }
131
+
132
+ makePayload () {
133
+ const bytes = this._traceBytes
134
+ const eventsOffset = this._eventsOffset
135
+ const eventsCount = this._eventCount
136
+
137
+ bytes.buffer[eventsOffset] = 0xdd
138
+ bytes.buffer[eventsOffset + 1] = eventsCount >> 24
139
+ bytes.buffer[eventsOffset + 2] = eventsCount >> 16
140
+ bytes.buffer[eventsOffset + 3] = eventsCount >> 8
141
+ bytes.buffer[eventsOffset + 4] = eventsCount
142
+
143
+ const traceSize = bytes.length
144
+ const buffer = Buffer.allocUnsafe(traceSize)
145
+
146
+ bytes.buffer.copy(buffer, 0, 0, bytes.length)
147
+
148
+ this.reset()
149
+
150
+ return buffer
151
+ }
152
+
153
+ _encodePayloadStart (bytes) {
154
+ // encodes the payload up to `events`. `events` will be encoded via _encode
155
+ const payload = {
156
+ version: ENCODING_VERSION,
157
+ metadata: {
158
+ '*': {
159
+ 'language': 'javascript'
160
+ }
161
+ },
162
+ events: []
163
+ }
164
+
165
+ if (this.env) {
166
+ payload.metadata['*'].env = this.env
167
+ }
168
+ if (this.runtimeId) {
169
+ payload.metadata['*']['runtime-id'] = this.runtimeId
170
+ }
171
+
172
+ this._encodeMapPrefix(bytes, payload)
173
+ this._encodeString(bytes, 'version')
174
+ this._encodeNumber(bytes, payload.version)
175
+ this._encodeString(bytes, 'metadata')
176
+ this._encodeMapPrefix(bytes, payload.metadata)
177
+ this._encodeString(bytes, '*')
178
+ this._encodeMap(bytes, payload.metadata['*'])
179
+ this._encodeString(bytes, 'events')
180
+ // Get offset of the events list to update the length of the array when calling `makePayload`
181
+ this._eventsOffset = bytes.length
182
+ bytes.reserve(5)
183
+ bytes.length += 5
184
+ }
185
+
186
+ reset () {
187
+ this._reset()
188
+ this._eventCount = 0
189
+ this._encodePayloadStart(this._traceBytes)
190
+ }
191
+ }
192
+
193
+ module.exports = { AgentlessCiVisibilityEncoder }
@@ -0,0 +1,116 @@
1
+ // From agent truncators: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/agent/truncator.go
2
+
3
+ // Values from: https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/truncate.go#L22-L27
4
+ // MAX_RESOURCE_NAME_LENGTH the maximum length a span resource can have
5
+ const MAX_RESOURCE_NAME_LENGTH = 5000
6
+ // MAX_META_KEY_LENGTH the maximum length of metadata key
7
+ const MAX_META_KEY_LENGTH = 200
8
+ // MAX_META_VALUE_LENGTH the maximum length of metadata value
9
+ const MAX_META_VALUE_LENGTH = 25000
10
+ // MAX_METRIC_KEY_LENGTH the maximum length of a metric name key
11
+ const MAX_METRIC_KEY_LENGTH = MAX_META_KEY_LENGTH
12
+ // MAX_METRIC_VALUE_LENGTH the maximum length of a metric name value
13
+ const MAX_METRIC_VALUE_LENGTH = MAX_META_VALUE_LENGTH
14
+
15
+ // From agent normalizer:
16
+ // https://github.com/DataDog/datadog-agent/blob/main/pkg/trace/traceutil/normalize.go
17
+ // DEFAULT_SPAN_NAME is the default name we assign a span if it's missing and we have no reasonable fallback
18
+ const DEFAULT_SPAN_NAME = 'unnamed_operation'
19
+ // DEFAULT_SERVICE_NAME is the default name we assign a service if it's missing and we have no reasonable fallback
20
+ const DEFAULT_SERVICE_NAME = 'unnamed-service'
21
+ // MAX_NAME_LENGTH the maximum length a name can have
22
+ const MAX_NAME_LENGTH = 100
23
+ // MAX_SERVICE_LENGTH the maximum length a service can have
24
+ const MAX_SERVICE_LENGTH = 100
25
+ // MAX_TYPE_LENGTH the maximum length a span type can have
26
+ const MAX_TYPE_LENGTH = 100
27
+
28
+ const fromEntries = Object.fromEntries || (entries =>
29
+ entries.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {}))
30
+
31
+ function truncateToLength (value, maxLength) {
32
+ if (!value) {
33
+ return value
34
+ }
35
+ if (value.length > maxLength) {
36
+ return `${value.slice(0, maxLength)}...`
37
+ }
38
+ return value
39
+ }
40
+
41
+ function truncateSpan (span) {
42
+ return fromEntries(Object.entries(span).map(([key, value]) => {
43
+ switch (key) {
44
+ case 'resource':
45
+ return ['resource', truncateToLength(value, MAX_RESOURCE_NAME_LENGTH)]
46
+ case 'meta':
47
+ return ['meta', fromEntries(Object.entries(value).map(([metaKey, metaValue]) =>
48
+ [truncateToLength(metaKey, MAX_META_KEY_LENGTH), truncateToLength(metaValue, MAX_META_VALUE_LENGTH)]
49
+ ))]
50
+ case 'metrics':
51
+ return ['metrics', fromEntries(Object.entries(value).map(([metricsKey, metricsValue]) =>
52
+ [truncateToLength(metricsKey, MAX_METRIC_KEY_LENGTH), truncateToLength(metricsValue, MAX_METRIC_VALUE_LENGTH)]
53
+ ))]
54
+ default:
55
+ return [key, value]
56
+ }
57
+ }))
58
+ }
59
+
60
+ function normalizeSpan (span) {
61
+ const normalizedSpan = fromEntries(Object.entries(span).map(([key, value]) => {
62
+ switch (key) {
63
+ case 'service':
64
+ if (!value) {
65
+ return [key, DEFAULT_SERVICE_NAME]
66
+ }
67
+ if (value.length > MAX_SERVICE_LENGTH) {
68
+ return [key, value.slice(0, MAX_SERVICE_LENGTH)]
69
+ }
70
+ break
71
+ case 'name':
72
+ if (!value) {
73
+ return [key, DEFAULT_SPAN_NAME]
74
+ }
75
+ if (value.length > MAX_NAME_LENGTH) {
76
+ return [key, value.slice(0, MAX_NAME_LENGTH)]
77
+ }
78
+ break
79
+ case 'resource':
80
+ if (!value) {
81
+ return [key, span.name || DEFAULT_SPAN_NAME]
82
+ }
83
+ break
84
+ case 'type':
85
+ if (!value) {
86
+ return [key, value]
87
+ }
88
+ if (value.length > MAX_TYPE_LENGTH) {
89
+ return [key, value.slice(0, MAX_TYPE_LENGTH)]
90
+ }
91
+ }
92
+ return [key, value]
93
+ }))
94
+ if (!normalizedSpan.service) {
95
+ normalizedSpan.service = DEFAULT_SERVICE_NAME
96
+ }
97
+ if (!normalizedSpan.name) {
98
+ normalizedSpan.name = DEFAULT_SPAN_NAME
99
+ }
100
+ return normalizedSpan
101
+ }
102
+
103
+ module.exports = {
104
+ truncateSpan,
105
+ normalizeSpan,
106
+ MAX_META_KEY_LENGTH,
107
+ MAX_META_VALUE_LENGTH,
108
+ MAX_METRIC_KEY_LENGTH,
109
+ MAX_METRIC_VALUE_LENGTH,
110
+ MAX_NAME_LENGTH,
111
+ MAX_SERVICE_LENGTH,
112
+ MAX_TYPE_LENGTH,
113
+ MAX_RESOURCE_NAME_LENGTH,
114
+ DEFAULT_SPAN_NAME,
115
+ DEFAULT_SERVICE_NAME
116
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const AgentExporter = require('./exporters/agent')
4
4
  const LogExporter = require('./exporters/log')
5
+ const AgentlessCiVisibilityExporter = require('./ci-visibility/exporters/agentless')
5
6
  const exporters = require('../../../ext/exporters')
6
7
  const fs = require('fs')
7
8
  const constants = require('./constants')
@@ -15,6 +16,8 @@ module.exports = name => {
15
16
  return LogExporter
16
17
  case exporters.AGENT:
17
18
  return AgentExporter
19
+ case exporters.DATADOG:
20
+ return AgentlessCiVisibilityExporter
18
21
  default:
19
22
  return inAWSLambda && !usingLambdaExtension ? LogExporter : AgentExporter
20
23
  }
@@ -3,7 +3,7 @@
3
3
  const URL = require('url').URL
4
4
  const log = require('../../log')
5
5
  const Writer = require('./writer')
6
- const Scheduler = require('./scheduler')
6
+ const Scheduler = require('../scheduler')
7
7
 
8
8
  class AgentExporter {
9
9
  constructor ({ url, hostname, port, flushInterval, lookup, protocolVersion }, prioritySampler) {