dd-trace 2.4.2 → 2.5.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 (36) 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/package.json +3 -2
  7. package/packages/datadog-instrumentations/src/cypress.js +8 -0
  8. package/packages/datadog-plugin-aws-sdk/src/helpers.js +4 -4
  9. package/packages/datadog-plugin-aws-sdk/src/index.js +1 -1
  10. package/packages/datadog-plugin-cucumber/src/index.js +24 -12
  11. package/packages/datadog-plugin-cypress/src/index.js +10 -5
  12. package/packages/datadog-plugin-cypress/src/plugin.js +13 -1
  13. package/packages/datadog-plugin-mocha/src/index.js +10 -1
  14. package/packages/dd-trace/lib/version.js +1 -1
  15. package/packages/dd-trace/src/appsec/callbacks/ddwaf.js +3 -1
  16. package/packages/dd-trace/src/appsec/recommended.json +15 -5
  17. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +32 -0
  18. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +51 -0
  19. package/packages/dd-trace/src/config.js +8 -1
  20. package/packages/dd-trace/src/encode/0.4.js +0 -1
  21. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +193 -0
  22. package/packages/dd-trace/src/encode/tags-processors.js +116 -0
  23. package/packages/dd-trace/src/exporter.js +3 -0
  24. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  25. package/packages/dd-trace/src/exporters/agent/writer.js +7 -32
  26. package/packages/dd-trace/src/exporters/{agent → common}/docker.js +0 -0
  27. package/packages/dd-trace/src/exporters/common/request.js +83 -0
  28. package/packages/dd-trace/src/exporters/common/writer.js +36 -0
  29. package/packages/dd-trace/src/exporters/{agent/scheduler.js → scheduler.js} +0 -0
  30. package/packages/dd-trace/src/instrumenter.js +3 -0
  31. package/packages/dd-trace/src/pkg.js +11 -6
  32. package/packages/dd-trace/src/plugins/util/test.js +60 -1
  33. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  34. package/packages/dd-trace/src/proxy.js +2 -0
  35. package/packages/dd-trace/src/telemetry.js +187 -0
  36. package/packages/dd-trace/src/exporters/agent/request.js +0 -86
@@ -10,6 +10,7 @@ const metrics = require('./metrics')
10
10
  const log = require('./log')
11
11
  const { isFalse } = require('./util')
12
12
  const { setStartupLogInstrumenter } = require('./startup-log')
13
+ const telemetry = require('./telemetry')
13
14
 
14
15
  const noop = new NoopTracer()
15
16
 
@@ -63,6 +64,7 @@ class Tracer extends BaseTracer {
63
64
  this._instrumenter.enable(config)
64
65
  this._pluginManager.configure(config)
65
66
  setStartupLogInstrumenter(this._instrumenter)
67
+ telemetry.start(config, this._instrumenter, this._pluginManager)
66
68
  }
67
69
  } catch (e) {
68
70
  log.error(e)
@@ -0,0 +1,187 @@
1
+ 'use strict'
2
+
3
+ const tracerVersion = require('../lib/version')
4
+ const pkg = require('./pkg')
5
+ const containerId = require('./exporters/common/docker').id()
6
+ const requirePackageJson = require('./require-package-json')
7
+ const path = require('path')
8
+ const os = require('os')
9
+ const request = require('./exporters/common/request')
10
+
11
+ let config
12
+ let instrumenter
13
+ let pluginManager
14
+
15
+ let seqId = 0
16
+ let application
17
+ let host
18
+ let interval
19
+ const sentIntegrations = new Set()
20
+
21
+ function getIntegrations () {
22
+ const newIntegrations = []
23
+ for (const plugin of instrumenter._instrumented.keys()) {
24
+ if (sentIntegrations.has(plugin.name)) {
25
+ continue
26
+ }
27
+ newIntegrations.push({
28
+ name: plugin.name,
29
+ enabled: true,
30
+ auto_enabled: true
31
+ })
32
+ sentIntegrations.add(plugin.name)
33
+ }
34
+ for (const pluginName in pluginManager._pluginsByName) {
35
+ if (sentIntegrations.has(pluginName)) {
36
+ continue
37
+ }
38
+ newIntegrations.push({
39
+ name: pluginName,
40
+ enabled: pluginManager._pluginsByName[pluginName]._enabled,
41
+ auto_enabled: true
42
+ })
43
+ sentIntegrations.add(pluginName)
44
+ }
45
+ return newIntegrations
46
+ }
47
+
48
+ function getDependencies () {
49
+ const deps = []
50
+ const { dependencies } = pkg
51
+ if (!dependencies) {
52
+ return deps
53
+ }
54
+ const rootDir = pkg.findRoot()
55
+ for (const [name, version] of Object.entries(dependencies)) {
56
+ const dep = { name }
57
+ try {
58
+ dep.version = requirePackageJson(
59
+ path.join(rootDir, 'node_modules', name.replace('/', path.sep))
60
+ ).version
61
+ } catch (e) {
62
+ dep.version = version
63
+ }
64
+ deps.push(dep)
65
+ }
66
+ return deps
67
+ }
68
+
69
+ function flatten (input, result = [], prefix = [], traversedObjects = null) {
70
+ traversedObjects = traversedObjects || new WeakSet()
71
+ if (traversedObjects.has(input)) {
72
+ return
73
+ }
74
+ traversedObjects.add(input)
75
+ for (const [key, value] of Object.entries(input)) {
76
+ if (typeof value === 'object' && value !== null) {
77
+ flatten(value, result, [...prefix, key], traversedObjects)
78
+ } else {
79
+ result.push({ name: [...prefix, key].join('.'), value })
80
+ }
81
+ }
82
+ return result
83
+ }
84
+
85
+ function appStarted () {
86
+ return {
87
+ integrations: getIntegrations(),
88
+ dependencies: getDependencies(),
89
+ configuration: flatten(config),
90
+ additional_payload: []
91
+ }
92
+ }
93
+
94
+ function onBeforeExit () {
95
+ process.removeListener('beforeExit', onBeforeExit)
96
+ sendData('app-closing')
97
+ }
98
+
99
+ function createAppObject () {
100
+ return {
101
+ service_name: config.service,
102
+ env: config.env,
103
+ service_version: config.version,
104
+ tracer_version: tracerVersion,
105
+ language_name: 'nodejs',
106
+ language_version: process.versions.node
107
+ }
108
+ }
109
+
110
+ function createHostObject () {
111
+ return {
112
+ hostname: os.hostname(), // TODO is this enough?
113
+ container_id: containerId
114
+ }
115
+ }
116
+
117
+ function sendData (reqType, payload = {}) {
118
+ const {
119
+ hostname,
120
+ port
121
+ } = config
122
+ const options = {
123
+ hostname,
124
+ port,
125
+ method: 'POST',
126
+ path: '/telemetry/proxy/api/v2/apmtelemetry',
127
+ headers: {
128
+ 'content-type': 'application/json',
129
+ 'dd-telemetry-api-version': 'v1',
130
+ 'dd-telemetry-request-type': reqType
131
+ }
132
+ }
133
+ const data = JSON.stringify({
134
+ api_version: 'v1',
135
+ request_type: reqType,
136
+ tracer_time: Math.floor(Date.now() / 1000),
137
+ runtime_id: config.tags['runtime-id'],
138
+ seq_id: ++seqId,
139
+ payload,
140
+ application,
141
+ host
142
+ })
143
+
144
+ request(data, options, true, () => {
145
+ // ignore errors
146
+ })
147
+ }
148
+
149
+ function start (aConfig, theInstrumenter, thePluginManager) {
150
+ if (!aConfig.telemetryEnabled) {
151
+ return
152
+ }
153
+ config = aConfig
154
+ instrumenter = theInstrumenter
155
+ pluginManager = thePluginManager
156
+ application = createAppObject()
157
+ host = createHostObject()
158
+ sendData('app-started', appStarted())
159
+ interval = setInterval(() => sendData('app-heartbeat'), 60000)
160
+ interval.unref()
161
+ process.on('beforeExit', onBeforeExit)
162
+ }
163
+
164
+ function stop () {
165
+ if (!config) {
166
+ return
167
+ }
168
+ clearInterval(interval)
169
+ process.removeListener('beforeExit', onBeforeExit)
170
+ }
171
+
172
+ function updateIntegrations () {
173
+ if (!config || !config.telemetryEnabled) {
174
+ return
175
+ }
176
+ const integrations = getIntegrations()
177
+ if (integrations.length === 0) {
178
+ return
179
+ }
180
+ sendData('app-integrations-change', { integrations })
181
+ }
182
+
183
+ module.exports = {
184
+ start,
185
+ stop,
186
+ updateIntegrations
187
+ }
@@ -1,86 +0,0 @@
1
- 'use strict'
2
-
3
- const http = require('http')
4
- const https = require('https')
5
- const docker = require('./docker')
6
- const log = require('../../log')
7
- const { storage } = require('../../../../datadog-core')
8
-
9
- const httpAgent = new http.Agent({ keepAlive: true })
10
- const httpsAgent = new https.Agent({ keepAlive: true })
11
- const containerId = docker.id()
12
-
13
- function retriableRequest (options, callback, client, data) {
14
- const store = storage.getStore()
15
-
16
- storage.enterWith({ noop: true })
17
-
18
- const req = client.request(options, res => {
19
- let data = ''
20
-
21
- res.setTimeout(options.timeout)
22
-
23
- res.on('data', chunk => { data += chunk })
24
- res.on('end', () => {
25
- if (res.statusCode >= 200 && res.statusCode <= 299) {
26
- callback(null, data, res.statusCode)
27
- } else {
28
- const error = new Error(`Error from the agent: ${res.statusCode} ${http.STATUS_CODES[res.statusCode]}`)
29
- error.status = res.statusCode
30
-
31
- callback(error, null, res.statusCode)
32
- }
33
- })
34
- })
35
- req.setTimeout(options.timeout, req.abort)
36
- data.forEach(buffer => req.write(buffer))
37
-
38
- storage.enterWith(store)
39
-
40
- return req
41
- }
42
-
43
- function request (options, callback) {
44
- options = Object.assign({
45
- headers: {},
46
- data: [],
47
- timeout: 2000
48
- }, options)
49
-
50
- const data = [].concat(options.data)
51
- const isSecure = options.protocol === 'https:'
52
- const client = isSecure ? https : http
53
- const agent = isSecure ? httpsAgent : httpAgent
54
-
55
- options.agent = agent
56
- options.headers['Content-Length'] = byteLength(data)
57
-
58
- if (containerId) {
59
- options.headers['Datadog-Container-ID'] = containerId
60
- }
61
- const firstRequest = retriableRequest(options, callback, client, data)
62
-
63
- // The first request will be retried if it fails due to a socket connection close
64
- const firstRequestErrorHandler = error => {
65
- if (firstRequest.reusedSocket && (error.code === 'ECONNRESET' || error.code === 'EPIPE')) {
66
- log.debug('Retrying request due to socket connection error')
67
- const retriedReq = retriableRequest(options, callback, client, data)
68
- // The retried request will fail normally
69
- retriedReq.on('error', e => callback(new Error(`Network error trying to reach the agent: ${e.message}`)))
70
- retriedReq.end()
71
- } else {
72
- callback(new Error(`Network error trying to reach the agent: ${error.message}`))
73
- }
74
- }
75
-
76
- firstRequest.on('error', firstRequestErrorHandler)
77
- firstRequest.end()
78
-
79
- return firstRequest
80
- }
81
-
82
- function byteLength (data) {
83
- return data.length > 0 ? data.reduce((prev, next) => prev + next.length, 0) : 0
84
- }
85
-
86
- module.exports = request