dd-trace 2.4.1 → 2.6.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 (57) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  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 -1
  7. package/package.json +5 -4
  8. package/packages/datadog-instrumentations/index.js +1 -0
  9. package/packages/datadog-instrumentations/src/amqplib.js +1 -1
  10. package/packages/datadog-instrumentations/src/cypress.js +8 -0
  11. package/packages/datadog-instrumentations/src/http/client.js +10 -10
  12. package/packages/datadog-instrumentations/src/jest.js +170 -0
  13. package/packages/datadog-plugin-aws-sdk/src/helpers.js +4 -4
  14. package/packages/datadog-plugin-aws-sdk/src/index.js +1 -1
  15. package/packages/datadog-plugin-cucumber/src/index.js +16 -16
  16. package/packages/datadog-plugin-cypress/src/index.js +10 -5
  17. package/packages/datadog-plugin-cypress/src/plugin.js +18 -17
  18. package/packages/datadog-plugin-elasticsearch/src/index.js +4 -2
  19. package/packages/datadog-plugin-fs/src/index.js +2 -0
  20. package/packages/datadog-plugin-http/src/client.js +4 -1
  21. package/packages/datadog-plugin-http/src/server.js +7 -10
  22. package/packages/datadog-plugin-jest/src/index.js +101 -3
  23. package/packages/datadog-plugin-jest/src/util.js +1 -29
  24. package/packages/datadog-plugin-mocha/src/index.js +14 -15
  25. package/packages/dd-trace/lib/version.js +1 -1
  26. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +29 -12
  27. package/packages/dd-trace/src/appsec/index.js +7 -3
  28. package/packages/dd-trace/src/appsec/recommended.json +119 -210
  29. package/packages/dd-trace/src/appsec/reporter.js +29 -3
  30. package/packages/dd-trace/src/appsec/rule_manager.js +2 -2
  31. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +32 -0
  32. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +51 -0
  33. package/packages/dd-trace/src/config.js +33 -4
  34. package/packages/dd-trace/src/encode/0.4.js +0 -1
  35. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +193 -0
  36. package/packages/dd-trace/src/encode/tags-processors.js +116 -0
  37. package/packages/dd-trace/src/exporter.js +3 -0
  38. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  39. package/packages/dd-trace/src/exporters/agent/writer.js +7 -32
  40. package/packages/dd-trace/src/exporters/{agent → common}/docker.js +0 -0
  41. package/packages/dd-trace/src/exporters/common/request.js +83 -0
  42. package/packages/dd-trace/src/exporters/common/writer.js +36 -0
  43. package/packages/dd-trace/src/exporters/{agent/scheduler.js → scheduler.js} +0 -0
  44. package/packages/dd-trace/src/format.js +9 -5
  45. package/packages/dd-trace/src/instrumenter.js +3 -0
  46. package/packages/dd-trace/src/pkg.js +11 -6
  47. package/packages/dd-trace/src/plugins/util/test.js +79 -1
  48. package/packages/dd-trace/src/plugins/util/web.js +11 -10
  49. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  50. package/packages/dd-trace/src/profiling/profilers/cpu.js +1 -1
  51. package/packages/dd-trace/src/proxy.js +2 -0
  52. package/packages/dd-trace/src/span_processor.js +4 -1
  53. package/packages/dd-trace/src/telemetry.js +187 -0
  54. package/scripts/install_plugin_modules.js +1 -0
  55. package/packages/datadog-plugin-jest/src/jest-environment.js +0 -272
  56. package/packages/datadog-plugin-jest/src/jest-jasmine2.js +0 -185
  57. package/packages/dd-trace/src/exporters/agent/request.js +0 -86
@@ -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,20 @@ 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.rulesVersion) {
97
+ topSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
98
+ }
99
+ }
100
+
85
101
  function reportAttack (attackData, store) {
86
102
  const req = store && store.get('req')
87
103
  const topSpan = web.root(req)
@@ -129,9 +145,17 @@ function reportAttack (attackData, store) {
129
145
  topSpan.addTags(newTags)
130
146
  }
131
147
 
132
- function finishAttacks (req, context) {
148
+ function finishRequest (req, context) {
133
149
  const topSpan = web.root(req)
134
- if (!topSpan || !context) return false
150
+ if (!topSpan) return false
151
+
152
+ if (metricsQueue.size) {
153
+ topSpan.addTags(Object.fromEntries(metricsQueue))
154
+
155
+ metricsQueue.clear()
156
+ }
157
+
158
+ if (!context || !topSpan.context()._tags['appsec.event']) return false
135
159
 
136
160
  const resolvedResponse = resolveHTTPResponse(context)
137
161
 
@@ -149,11 +173,13 @@ function setRateLimit (rateLimit) {
149
173
  }
150
174
 
151
175
  module.exports = {
176
+ metricsQueue,
152
177
  resolveHTTPRequest,
153
178
  resolveHTTPResponse,
154
179
  filterHeaders,
155
180
  formatHeaderName,
181
+ reportMetrics,
156
182
  reportAttack,
157
- finishAttacks,
183
+ finishRequest,
158
184
  setRateLimit
159
185
  }
@@ -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) {
@@ -1,28 +1,23 @@
1
1
  'use strict'
2
2
 
3
- const request = require('./request')
3
+ const request = require('../common/request')
4
4
  const { startupLog } = require('../../startup-log')
5
5
  const metrics = require('../../metrics')
6
6
  const log = require('../../log')
7
7
  const tracerVersion = require('../../../lib/version')
8
+ const BaseWriter = require('../common/writer')
8
9
 
9
10
  const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'
10
11
 
11
- class Writer {
12
- constructor ({ url, prioritySampler, lookup, protocolVersion }) {
12
+ class Writer extends BaseWriter {
13
+ constructor ({ prioritySampler, lookup, protocolVersion }) {
14
+ super(...arguments)
13
15
  const AgentEncoder = getEncoder(protocolVersion)
14
16
 
15
- this._url = url
16
17
  this._prioritySampler = prioritySampler
17
18
  this._lookup = lookup
18
19
  this._protocolVersion = protocolVersion
19
- this._encoderForVersion = new AgentEncoder(this)
20
- }
21
-
22
- append (spans) {
23
- log.debug(() => `Encoding trace: ${JSON.stringify(spans)}`)
24
-
25
- this._encode(spans)
20
+ this._encoder = new AgentEncoder(this)
26
21
  }
27
22
 
28
23
  _sendPayload (data, count, done) {
@@ -62,26 +57,6 @@ class Writer {
62
57
  done()
63
58
  })
64
59
  }
65
-
66
- setUrl (url) {
67
- this._url = url
68
- }
69
-
70
- _encode (trace) {
71
- this._encoderForVersion.encode(trace)
72
- }
73
-
74
- flush (done = () => {}) {
75
- const count = this._encoderForVersion.count()
76
-
77
- if (count > 0) {
78
- const payload = this._encoderForVersion.makePayload()
79
-
80
- this._sendPayload(payload, count, done)
81
- } else {
82
- done()
83
- }
84
- }
85
60
  }
86
61
 
87
62
  function setHeader (headers, key, value) {
@@ -124,7 +99,7 @@ function makeRequest (version, data, count, url, lookup, needsStartupLog, cb) {
124
99
 
125
100
  log.debug(() => `Request to the agent: ${JSON.stringify(options)}`)
126
101
 
127
- request(Object.assign({ data }, options), (err, res, status) => {
102
+ request(data, options, true, (err, res, status) => {
128
103
  if (needsStartupLog) {
129
104
  // Note that logging will only happen once, regardless of how many times this is called.
130
105
  startupLog({