dd-trace 5.0.0-pre-0a30f33 → 5.0.0-pre-244a0ea
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.
- package/README.md +4 -9
- package/index.d.ts +43 -0
- package/package.json +2 -2
- package/packages/datadog-esbuild/index.js +20 -6
- package/packages/datadog-instrumentations/src/jest.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +86 -2
- package/packages/datadog-instrumentations/src/openai.js +1 -1
- package/packages/datadog-plugin-graphql/src/index.js +3 -3
- package/packages/datadog-plugin-next/src/index.js +4 -0
- package/packages/dd-trace/src/dogstatsd.js +65 -5
- package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
- package/packages/dd-trace/src/exporters/common/request.js +13 -4
- package/packages/dd-trace/src/opentracing/span.js +13 -13
- package/packages/dd-trace/src/opentracing/tracer.js +3 -3
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +23 -7
- package/packages/dd-trace/src/proxy.js +23 -2
- /package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +0 -0
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/dd-trace)
|
|
4
4
|
[](https://www.npmjs.com/package/dd-trace/v/latest-node12)
|
|
5
|
-
[](https://www.npmjs.com/package/dd-trace/v/latest-node12)
|
|
6
|
-
[](https://www.npmjs.com/package/dd-trace/v/dev)
|
|
7
5
|
[](https://codecov.io/gh/DataDog/dd-trace-js)
|
|
8
6
|
|
|
9
7
|
<img align="right" src="https://user-images.githubusercontent.com/551402/208212084-1d0c07e2-4135-4c61-b2da-8f2fddbc66ed.png" alt="Bits the dog JavaScript" width="200px"/>
|
|
@@ -28,12 +26,12 @@ Most of the documentation for `dd-trace` is available on these webpages:
|
|
|
28
26
|
| Release Line | Latest Version | Node.js | Status |Initial Release | End of Life |
|
|
29
27
|
| :---: | :---: | :---: | :---: | :---: | :---: |
|
|
30
28
|
| [`v1`](https://github.com/DataDog/dd-trace-js/tree/v1.x) |  | `>= v12` | **End of Life** | 2021-07-13 | 2022-02-25 |
|
|
31
|
-
| [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) |  | `>= v12` | **
|
|
29
|
+
| [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) |  | `>= v12` | **End of Life** | 2022-01-28 | 2023-08-15 |
|
|
32
30
|
| [`v3`](https://github.com/DataDog/dd-trace-js/tree/v3.x) |  | `>= v14` | **Maintenance** | 2022-08-15 | 2024-05-15 |
|
|
33
31
|
| [`v4`](https://github.com/DataDog/dd-trace-js/tree/v4.x) |  | `>= v16` | **Current** | 2023-05-12 | Unknown |
|
|
34
32
|
|
|
35
|
-
We currently maintain
|
|
36
|
-
Features and bug fixes that are merged are released to the `v4` line and, if appropriate, also the `
|
|
33
|
+
We currently maintain two release lines, namely `v3` and `v4`.
|
|
34
|
+
Features and bug fixes that are merged are released to the `v4` line and, if appropriate, also the `v3` line.
|
|
37
35
|
|
|
38
36
|
For any new projects it is recommended to use the `v4` release line:
|
|
39
37
|
|
|
@@ -42,15 +40,12 @@ $ npm install dd-trace
|
|
|
42
40
|
$ yarn add dd-trace
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
However, existing projects that already use the `
|
|
43
|
+
However, existing projects that already use the `v3` release line, or projects that need to support EOL versions of Node.js, may continue to use these release lines.
|
|
46
44
|
This is done by specifying the version when installing the package.
|
|
47
|
-
Note that we also publish to npm using a `latest-node12` and `latest-node14` tag that can also be used for install:
|
|
48
45
|
|
|
49
46
|
```sh
|
|
50
47
|
$ npm install dd-trace@3
|
|
51
48
|
$ yarn add dd-trace@3
|
|
52
|
-
$ npm install dd-trace@latest-node14
|
|
53
|
-
$ yarn add dd-trace@latest-node14
|
|
54
49
|
```
|
|
55
50
|
|
|
56
51
|
Any backwards-breaking functionality that is introduced into the library will result in an increase of the major version of the library and therefore a new release line.
|
package/index.d.ts
CHANGED
|
@@ -121,6 +121,8 @@ export declare interface Tracer extends opentracing.Tracer {
|
|
|
121
121
|
appsec: Appsec;
|
|
122
122
|
|
|
123
123
|
TracerProvider: opentelemetry.TracerProvider;
|
|
124
|
+
|
|
125
|
+
dogstatsd: DogStatsD;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
export declare interface TraceOptions extends Analyzable {
|
|
@@ -642,6 +644,47 @@ export declare interface User {
|
|
|
642
644
|
[key: string]: string | undefined
|
|
643
645
|
}
|
|
644
646
|
|
|
647
|
+
export declare interface DogStatsD {
|
|
648
|
+
/**
|
|
649
|
+
* Increments a metric by the specified value, optionally specifying tags.
|
|
650
|
+
* @param {string} stat The dot-separated metric name.
|
|
651
|
+
* @param {number} value The amount to increment the stat by.
|
|
652
|
+
* @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
|
|
653
|
+
*/
|
|
654
|
+
increment(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Decrements a metric by the specified value, optionally specifying tags.
|
|
658
|
+
* @param {string} stat The dot-separated metric name.
|
|
659
|
+
* @param {number} value The amount to decrement the stat by.
|
|
660
|
+
* @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
|
|
661
|
+
*/
|
|
662
|
+
decrement(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Sets a distribution value, optionally specifying tags.
|
|
666
|
+
* @param {string} stat The dot-separated metric name.
|
|
667
|
+
* @param {number} value The amount to increment the stat by.
|
|
668
|
+
* @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
|
|
669
|
+
*/
|
|
670
|
+
distribution(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Sets a gauge value, optionally specifying tags.
|
|
674
|
+
* @param {string} stat The dot-separated metric name.
|
|
675
|
+
* @param {number} value The amount to increment the stat by.
|
|
676
|
+
* @param {[tag:string]:string|number} tags Tags to pass along, such as `[ 'foo:bar' ]`. Values are combined with config.tags.
|
|
677
|
+
*/
|
|
678
|
+
gauge(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Forces any unsent metrics to be sent
|
|
682
|
+
*
|
|
683
|
+
* @beta This method is experimental and could be removed in future versions.
|
|
684
|
+
*/
|
|
685
|
+
flush(): void
|
|
686
|
+
}
|
|
687
|
+
|
|
645
688
|
export declare interface Appsec {
|
|
646
689
|
/**
|
|
647
690
|
* Links a successful login event to the current trace. Will link the passed user to the current trace with Appsec.setUser() internally.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.0.0-pre-
|
|
3
|
+
"version": "5.0.0-pre-244a0ea",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@datadog/native-iast-rewriter": "2.0.1",
|
|
72
72
|
"@datadog/native-iast-taint-tracking": "1.5.0",
|
|
73
73
|
"@datadog/native-metrics": "^2.0.0",
|
|
74
|
-
"@datadog/pprof": "3.
|
|
74
|
+
"@datadog/pprof": "3.2.0",
|
|
75
75
|
"@datadog/sketches-js": "^2.1.0",
|
|
76
76
|
"@opentelemetry/api": "^1.0.0",
|
|
77
77
|
"@opentelemetry/core": "^1.14.0",
|
|
@@ -53,10 +53,25 @@ module.exports.setup = function (build) {
|
|
|
53
53
|
const externalModules = new Set(build.initialOptions.external || [])
|
|
54
54
|
build.onResolve({ filter: /.*/ }, args => {
|
|
55
55
|
if (externalModules.has(args.path)) {
|
|
56
|
+
// Internal Node.js packages will still be instrumented via require()
|
|
56
57
|
if (DEBUG) console.log(`EXTERNAL: ${args.path}`)
|
|
57
58
|
return
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
// TODO: Should this also check for namespace === 'file'?
|
|
62
|
+
if (args.path.startsWith('.') && !args.importer.includes('node_modules/')) {
|
|
63
|
+
// This is local application code, not an instrumented package
|
|
64
|
+
if (DEBUG) console.log(`LOCAL: ${args.path}`)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// TODO: Should this also check for namespace === 'file'?
|
|
69
|
+
if (args.path.startsWith('@') && !args.importer.includes('node_modules/')) {
|
|
70
|
+
// This is the Next.js convention for loading local files
|
|
71
|
+
if (DEBUG) console.log(`@LOCAL: ${args.path}`)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
let fullPathToModule
|
|
61
76
|
try {
|
|
62
77
|
fullPathToModule = dotFriendlyResolve(args.path, args.resolveDir)
|
|
@@ -65,12 +80,11 @@ module.exports.setup = function (build) {
|
|
|
65
80
|
return
|
|
66
81
|
}
|
|
67
82
|
const extracted = extractPackageAndModulePath(fullPathToModule)
|
|
68
|
-
const packageName = args.path
|
|
69
83
|
|
|
70
84
|
const internal = builtins.has(args.path)
|
|
71
85
|
|
|
72
86
|
if (args.namespace === 'file' && (
|
|
73
|
-
modulesOfInterest.has(
|
|
87
|
+
modulesOfInterest.has(args.path) || modulesOfInterest.has(`${extracted.pkg}/${extracted.path}`))
|
|
74
88
|
) {
|
|
75
89
|
// The file namespace is used when requiring files from disk in userland
|
|
76
90
|
|
|
@@ -90,7 +104,7 @@ module.exports.setup = function (build) {
|
|
|
90
104
|
|
|
91
105
|
const packageJson = require(pathToPackageJson)
|
|
92
106
|
|
|
93
|
-
if (DEBUG) console.log(`RESOLVE: ${
|
|
107
|
+
if (DEBUG) console.log(`RESOLVE: ${args.path}@${packageJson.version}`)
|
|
94
108
|
|
|
95
109
|
// https://esbuild.github.io/plugins/#on-resolve-arguments
|
|
96
110
|
return {
|
|
@@ -101,17 +115,17 @@ module.exports.setup = function (build) {
|
|
|
101
115
|
pkg: extracted.pkg,
|
|
102
116
|
path: extracted.path,
|
|
103
117
|
full: fullPathToModule,
|
|
104
|
-
raw:
|
|
118
|
+
raw: args.path,
|
|
105
119
|
internal
|
|
106
120
|
}
|
|
107
121
|
}
|
|
108
122
|
} else if (args.namespace === NAMESPACE) {
|
|
109
123
|
// The datadog namespace is used when requiring files that are injected during the onLoad stage
|
|
110
124
|
|
|
111
|
-
if (builtins.has(
|
|
125
|
+
if (builtins.has(args.path)) return
|
|
112
126
|
|
|
113
127
|
return {
|
|
114
|
-
path: require.resolve(
|
|
128
|
+
path: require.resolve(args.path, { paths: [ args.resolveDir ] }),
|
|
115
129
|
namespace: 'file'
|
|
116
130
|
}
|
|
117
131
|
}
|
|
@@ -105,7 +105,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
105
105
|
await super.handleTestEvent(event, state)
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
const setNameToParams = (name, params) => { this.nameToParams[name] = params }
|
|
108
|
+
const setNameToParams = (name, params) => { this.nameToParams[name] = [...params] }
|
|
109
109
|
|
|
110
110
|
if (event.name === 'setup') {
|
|
111
111
|
if (this.global.test) {
|
|
@@ -12,6 +12,7 @@ const errorChannel = channel('apm:next:request:error')
|
|
|
12
12
|
const pageLoadChannel = channel('apm:next:page:load')
|
|
13
13
|
|
|
14
14
|
const requests = new WeakSet()
|
|
15
|
+
const requestToNextjsPagePath = new WeakMap()
|
|
15
16
|
|
|
16
17
|
function wrapHandleRequest (handleRequest) {
|
|
17
18
|
return function (req, res, pathname, query) {
|
|
@@ -132,6 +133,57 @@ function instrument (req, res, handler) {
|
|
|
132
133
|
})
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
function wrapSetupServerWorker (setupServerWorker) {
|
|
137
|
+
return function (requestHandler) {
|
|
138
|
+
arguments[0] = shimmer.wrap(requestHandler, wrapRequestHandler(requestHandler))
|
|
139
|
+
return setupServerWorker.apply(this, arguments)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function wrapInitialize (initialize) {
|
|
144
|
+
return async function () {
|
|
145
|
+
const result = await initialize.apply(this, arguments)
|
|
146
|
+
if (Array.isArray(result)) {
|
|
147
|
+
const requestHandler = result[0]
|
|
148
|
+
result[0] = shimmer.wrap(requestHandler, wrapRequestHandler(requestHandler))
|
|
149
|
+
}
|
|
150
|
+
return result
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function wrapRequestHandler (requestHandler) {
|
|
155
|
+
return function (req, res) {
|
|
156
|
+
return instrument(req, res, async () => {
|
|
157
|
+
const result = await requestHandler.apply(this, arguments) // apply here first to get page path association
|
|
158
|
+
|
|
159
|
+
const page = requestToNextjsPagePath.get(req)
|
|
160
|
+
if (page && pageLoadChannel.hasSubscribers) pageLoadChannel.publish({ page })
|
|
161
|
+
|
|
162
|
+
return result
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// these two functions make sure we get path groups for routes in standalone,
|
|
168
|
+
// as it doesn't route through `next-server`/`base-server`
|
|
169
|
+
function wrapGetResolveRoutes (getResolveRoutes) {
|
|
170
|
+
return function () {
|
|
171
|
+
const result = getResolveRoutes.apply(this, arguments)
|
|
172
|
+
return shimmer.wrap(result, wrapResolveRoutes(result))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function wrapResolveRoutes (resolveRoutes) {
|
|
177
|
+
return async function (req) {
|
|
178
|
+
const result = await resolveRoutes.apply(this, arguments)
|
|
179
|
+
if (result && result.matchedOutput) {
|
|
180
|
+
const path = result.matchedOutput.itemPath
|
|
181
|
+
requestToNextjsPagePath.set(req, path)
|
|
182
|
+
}
|
|
183
|
+
return result
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
135
187
|
function finish (ctx, result, err) {
|
|
136
188
|
if (err) {
|
|
137
189
|
ctx.error = err
|
|
@@ -147,11 +199,27 @@ function finish (ctx, result, err) {
|
|
|
147
199
|
return result
|
|
148
200
|
}
|
|
149
201
|
|
|
202
|
+
addHook({
|
|
203
|
+
name: 'next',
|
|
204
|
+
versions: ['>=13.4.13'],
|
|
205
|
+
file: 'dist/server/lib/router-utils/resolve-routes.js'
|
|
206
|
+
}, resolveRoutesModule => shimmer.wrap(resolveRoutesModule, 'getResolveRoutes', wrapGetResolveRoutes))
|
|
207
|
+
|
|
208
|
+
addHook({
|
|
209
|
+
name: 'next',
|
|
210
|
+
versions: ['13.4.13'],
|
|
211
|
+
file: 'dist/server/lib/setup-server-worker.js'
|
|
212
|
+
}, setupServerWorker => shimmer.wrap(setupServerWorker, 'initializeServerWorker', wrapSetupServerWorker))
|
|
213
|
+
|
|
214
|
+
addHook({
|
|
215
|
+
name: 'next',
|
|
216
|
+
versions: ['>=13.4.15'],
|
|
217
|
+
file: 'dist/server/lib/router-server.js'
|
|
218
|
+
}, routerServer => shimmer.wrap(routerServer, 'initialize', wrapInitialize))
|
|
219
|
+
|
|
150
220
|
addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js' }, nextServer => {
|
|
151
221
|
const Server = nextServer.default
|
|
152
222
|
|
|
153
|
-
shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
|
|
154
|
-
shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
|
|
155
223
|
shimmer.wrap(Server.prototype, 'renderToResponse', wrapRenderToResponse)
|
|
156
224
|
shimmer.wrap(Server.prototype, 'renderErrorToResponse', wrapRenderErrorToResponse)
|
|
157
225
|
shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
|
|
@@ -159,6 +227,22 @@ addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js'
|
|
|
159
227
|
return nextServer
|
|
160
228
|
})
|
|
161
229
|
|
|
230
|
+
// these functions wrapped in all versions above 13.2 except:
|
|
231
|
+
// 13.4.13 due to tests failing when these functions are wrapped
|
|
232
|
+
// 13.4.14 due to it not being in the NPM registry/officially released
|
|
233
|
+
addHook({
|
|
234
|
+
name: 'next',
|
|
235
|
+
versions: ['>=13.2 <13.4.13', '>=13.4.15'],
|
|
236
|
+
file: 'dist/server/next-server.js'
|
|
237
|
+
}, nextServer => {
|
|
238
|
+
const Server = nextServer.default
|
|
239
|
+
|
|
240
|
+
shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
|
|
241
|
+
shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
|
|
242
|
+
|
|
243
|
+
return nextServer
|
|
244
|
+
})
|
|
245
|
+
|
|
162
246
|
addHook({ name: 'next', versions: ['>=11.1 <13.2'], file: 'dist/server/next-server.js' }, nextServer => {
|
|
163
247
|
const Server = nextServer.default
|
|
164
248
|
|
|
@@ -10,7 +10,7 @@ const startCh = channel('apm:openai:request:start')
|
|
|
10
10
|
const finishCh = channel('apm:openai:request:finish')
|
|
11
11
|
const errorCh = channel('apm:openai:request:error')
|
|
12
12
|
|
|
13
|
-
addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0'] }, exports => {
|
|
13
|
+
addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0 <4'] }, exports => {
|
|
14
14
|
const methodNames = Object.getOwnPropertyNames(exports.OpenAIApi.prototype)
|
|
15
15
|
methodNames.shift() // remove leading 'constructor' method
|
|
16
16
|
|
|
@@ -56,9 +56,9 @@ function getVariablesFilter (config) {
|
|
|
56
56
|
|
|
57
57
|
function getHooks (config) {
|
|
58
58
|
const noop = () => { }
|
|
59
|
-
const execute =
|
|
60
|
-
const parse =
|
|
61
|
-
const validate =
|
|
59
|
+
const execute = config.hooks?.execute || noop
|
|
60
|
+
const parse = config.hooks?.parse || noop
|
|
61
|
+
const validate = config.hooks?.validate || noop
|
|
62
62
|
|
|
63
63
|
return { execute, parse, validate }
|
|
64
64
|
}
|
|
@@ -4,6 +4,7 @@ const ServerPlugin = require('../../dd-trace/src/plugins/server')
|
|
|
4
4
|
const { storage } = require('../../datadog-core')
|
|
5
5
|
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
6
6
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
7
|
+
const web = require('../../dd-trace/src/plugins/util/web')
|
|
7
8
|
|
|
8
9
|
class NextPlugin extends ServerPlugin {
|
|
9
10
|
static get id () {
|
|
@@ -49,6 +50,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
49
50
|
|
|
50
51
|
const span = store.span
|
|
51
52
|
const error = span.context()._tags['error']
|
|
53
|
+
const page = span.context()._tags['next.page']
|
|
52
54
|
|
|
53
55
|
if (!this.config.validateStatus(res.statusCode) && !error) {
|
|
54
56
|
span.setTag('error', true)
|
|
@@ -58,6 +60,8 @@ class NextPlugin extends ServerPlugin {
|
|
|
58
60
|
'http.status_code': res.statusCode
|
|
59
61
|
})
|
|
60
62
|
|
|
63
|
+
if (page) web.setRoute(req, page)
|
|
64
|
+
|
|
61
65
|
this.config.hooks.request(span, req, res)
|
|
62
66
|
|
|
63
67
|
span.finish()
|
|
@@ -35,14 +35,14 @@ class DogStatsDClient {
|
|
|
35
35
|
this._udp6 = this._socket('udp6')
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
gauge (stat, value, tags) {
|
|
39
|
-
this._add(stat, value, TYPE_GAUGE, tags)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
38
|
increment (stat, value, tags) {
|
|
43
39
|
this._add(stat, value, TYPE_COUNTER, tags)
|
|
44
40
|
}
|
|
45
41
|
|
|
42
|
+
gauge (stat, value, tags) {
|
|
43
|
+
this._add(stat, value, TYPE_GAUGE, tags)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
46
|
distribution (stat, value, tags) {
|
|
47
47
|
this._add(stat, value, TYPE_DISTRIBUTION, tags)
|
|
48
48
|
}
|
|
@@ -153,7 +153,67 @@ class NoopDogStatsDClient {
|
|
|
153
153
|
flush () { }
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// This is a simplified user-facing proxy to the underlying DogStatsDClient instance
|
|
157
|
+
class CustomMetrics {
|
|
158
|
+
constructor (options) {
|
|
159
|
+
this.dogstatsd = new DogStatsDClient(options)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
increment (stat, value = 1, tags) {
|
|
163
|
+
return this.dogstatsd.increment(
|
|
164
|
+
stat,
|
|
165
|
+
value,
|
|
166
|
+
CustomMetrics.tagTranslator(tags)
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
decrement (stat, value = 1, tags) {
|
|
171
|
+
return this.dogstatsd.increment(
|
|
172
|
+
stat,
|
|
173
|
+
value * -1,
|
|
174
|
+
CustomMetrics.tagTranslator(tags)
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
gauge (stat, value, tags) {
|
|
179
|
+
return this.dogstatsd.gauge(
|
|
180
|
+
stat,
|
|
181
|
+
value,
|
|
182
|
+
CustomMetrics.tagTranslator(tags)
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
distribution (stat, value, tags) {
|
|
187
|
+
return this.dogstatsd.distribution(
|
|
188
|
+
stat,
|
|
189
|
+
value,
|
|
190
|
+
CustomMetrics.tagTranslator(tags)
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
flush () {
|
|
195
|
+
return this.dogstatsd.flush()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Exposing { tagName: 'tagValue' } to the end user
|
|
200
|
+
* These are translated into [ 'tagName:tagValue' ] for internal use
|
|
201
|
+
*/
|
|
202
|
+
static tagTranslator (objTags) {
|
|
203
|
+
const arrTags = []
|
|
204
|
+
|
|
205
|
+
if (!objTags) return arrTags
|
|
206
|
+
|
|
207
|
+
for (const [key, value] of Object.entries(objTags)) {
|
|
208
|
+
arrTags.push(`${key}:${value}`)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return arrTags
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
156
215
|
module.exports = {
|
|
157
216
|
DogStatsDClient,
|
|
158
|
-
NoopDogStatsDClient
|
|
217
|
+
NoopDogStatsDClient,
|
|
218
|
+
CustomMetrics
|
|
159
219
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const request = require('../common/request')
|
|
4
4
|
const { startupLog } = require('../../startup-log')
|
|
5
|
-
const
|
|
5
|
+
const runtimeMetrics = require('../../runtime_metrics')
|
|
6
6
|
const log = require('../../log')
|
|
7
7
|
const tracerVersion = require('../../../../../package.json').version
|
|
8
8
|
const BaseWriter = require('../common/writer')
|
|
@@ -22,19 +22,19 @@ class Writer extends BaseWriter {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
_sendPayload (data, count, done) {
|
|
25
|
-
|
|
25
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.requests`, true)
|
|
26
26
|
|
|
27
27
|
const { _headers, _lookup, _protocolVersion, _url } = this
|
|
28
28
|
makeRequest(_protocolVersion, data, count, _url, _headers, _lookup, true, (err, res, status) => {
|
|
29
29
|
if (status) {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.responses`, true)
|
|
31
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
|
|
32
32
|
} else if (err) {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.errors`, true)
|
|
34
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${err.name}`, true)
|
|
35
35
|
|
|
36
36
|
if (err.code) {
|
|
37
|
-
|
|
37
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.code`, `code:${err.code}`, true)
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -53,8 +53,8 @@ class Writer extends BaseWriter {
|
|
|
53
53
|
} catch (e) {
|
|
54
54
|
log.error(e)
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.errors`, true)
|
|
57
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${e.name}`, true)
|
|
58
58
|
}
|
|
59
59
|
done()
|
|
60
60
|
})
|
|
@@ -42,10 +42,19 @@ function urlToOptions (url) {
|
|
|
42
42
|
return options
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function fromUrlString (
|
|
46
|
-
|
|
47
|
-
? urlToOptions(new URL(
|
|
48
|
-
: urlParse(
|
|
45
|
+
function fromUrlString (urlString) {
|
|
46
|
+
const url = typeof urlToHttpOptions === 'function'
|
|
47
|
+
? urlToOptions(new URL(urlString))
|
|
48
|
+
: urlParse(urlString)
|
|
49
|
+
|
|
50
|
+
// Add the 'hostname' back if we're using named pipes
|
|
51
|
+
if (url.protocol === 'unix:' && url.host === '.') {
|
|
52
|
+
const udsPath = urlString.replace(/^unix:/, '')
|
|
53
|
+
url.path = udsPath
|
|
54
|
+
url.pathname = udsPath
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return url
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
function request (data, options, callback) {
|
|
@@ -8,7 +8,7 @@ const semver = require('semver')
|
|
|
8
8
|
const SpanContext = require('./span_context')
|
|
9
9
|
const id = require('../id')
|
|
10
10
|
const tagger = require('../tagger')
|
|
11
|
-
const
|
|
11
|
+
const runtimeMetrics = require('../runtime_metrics')
|
|
12
12
|
const log = require('../log')
|
|
13
13
|
const { storage } = require('../../../datadog-core')
|
|
14
14
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
@@ -79,11 +79,11 @@ class DatadogSpan {
|
|
|
79
79
|
this._startTime = fields.startTime || this._getTime()
|
|
80
80
|
|
|
81
81
|
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
runtimeMetrics.increment('runtime.node.spans.unfinished')
|
|
83
|
+
runtimeMetrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
runtimeMetrics.increment('runtime.node.spans.open') // unfinished for real
|
|
86
|
+
runtimeMetrics.increment('runtime.node.spans.open.by.name', `span_name:${operationName}`)
|
|
87
87
|
|
|
88
88
|
unfinishedRegistry.register(this, operationName, this)
|
|
89
89
|
}
|
|
@@ -159,13 +159,13 @@ class DatadogSpan {
|
|
|
159
159
|
getIntegrationCounter('span_finished', this._integrationName).inc()
|
|
160
160
|
|
|
161
161
|
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
runtimeMetrics.decrement('runtime.node.spans.unfinished')
|
|
163
|
+
runtimeMetrics.decrement('runtime.node.spans.unfinished.by.name', `span_name:${this._name}`)
|
|
164
|
+
runtimeMetrics.increment('runtime.node.spans.finished')
|
|
165
|
+
runtimeMetrics.increment('runtime.node.spans.finished.by.name', `span_name:${this._name}`)
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
runtimeMetrics.decrement('runtime.node.spans.open') // unfinished for real
|
|
168
|
+
runtimeMetrics.decrement('runtime.node.spans.open.by.name', `span_name:${this._name}`)
|
|
169
169
|
|
|
170
170
|
unfinishedRegistry.unregister(this)
|
|
171
171
|
finishedRegistry.register(this, this._name)
|
|
@@ -243,8 +243,8 @@ function createRegistry (type) {
|
|
|
243
243
|
if (!semver.satisfies(process.version, '>=14.6')) return
|
|
244
244
|
|
|
245
245
|
return new global.FinalizationRegistry(name => {
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
runtimeMetrics.decrement(`runtime.node.spans.${type}`)
|
|
247
|
+
runtimeMetrics.decrement(`runtime.node.spans.${type}.by.name`, [`span_name:${name}`])
|
|
248
248
|
})
|
|
249
249
|
}
|
|
250
250
|
|
|
@@ -11,7 +11,7 @@ const LogPropagator = require('./propagation/log')
|
|
|
11
11
|
const formats = require('../../../../ext/formats')
|
|
12
12
|
|
|
13
13
|
const log = require('../log')
|
|
14
|
-
const
|
|
14
|
+
const runtimeMetrics = require('../runtime_metrics')
|
|
15
15
|
const getExporter = require('../exporter')
|
|
16
16
|
const SpanContext = require('./span_context')
|
|
17
17
|
|
|
@@ -82,7 +82,7 @@ class DatadogTracer {
|
|
|
82
82
|
this._propagators[format].inject(spanContext, carrier)
|
|
83
83
|
} catch (e) {
|
|
84
84
|
log.error(e)
|
|
85
|
-
|
|
85
|
+
runtimeMetrics.increment('datadog.tracer.node.inject.errors', true)
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -91,7 +91,7 @@ class DatadogTracer {
|
|
|
91
91
|
return this._propagators[format].extract(carrier)
|
|
92
92
|
} catch (e) {
|
|
93
93
|
log.error(e)
|
|
94
|
-
|
|
94
|
+
runtimeMetrics.increment('datadog.tracer.node.extract.errors', true)
|
|
95
95
|
return null
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -31,6 +31,7 @@ class Config {
|
|
|
31
31
|
DD_PROFILING_UPLOAD_PERIOD,
|
|
32
32
|
DD_PROFILING_PPROF_PREFIX,
|
|
33
33
|
DD_PROFILING_HEAP_ENABLED,
|
|
34
|
+
DD_PROFILING_V8_PROFILER_BUG_WORKAROUND,
|
|
34
35
|
DD_PROFILING_WALLTIME_ENABLED,
|
|
35
36
|
DD_PROFILING_EXPERIMENTAL_OOM_MONITORING_ENABLED,
|
|
36
37
|
DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
|
|
@@ -76,7 +77,8 @@ class Config {
|
|
|
76
77
|
this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
|
|
77
78
|
this.endpointCollectionEnabled = endpointCollectionEnabled
|
|
78
79
|
this.pprofPrefix = pprofPrefix
|
|
79
|
-
|
|
80
|
+
this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
|
|
81
|
+
DD_PROFILING_V8_PROFILER_BUG_WORKAROUND, true))
|
|
80
82
|
const hostname = coalesce(options.hostname, DD_AGENT_HOST) || 'localhost'
|
|
81
83
|
const port = coalesce(options.port, DD_TRACE_AGENT_PORT) || 8126
|
|
82
84
|
this.url = new URL(coalesce(options.url, DD_TRACE_AGENT_URL, format({
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
const { storage } = require('../../../../datadog-core')
|
|
4
4
|
|
|
5
5
|
const dc = require('../../../../diagnostics_channel')
|
|
6
|
+
const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
|
|
7
|
+
const { WEB } = require('../../../../../ext/types')
|
|
8
|
+
const runtimeMetrics = require('../../runtime_metrics')
|
|
6
9
|
|
|
7
10
|
const beforeCh = dc.channel('dd-trace:storage:before')
|
|
8
11
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
@@ -41,13 +44,13 @@ function getSpanContextTags (span) {
|
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
function isWebServerSpan (tags) {
|
|
44
|
-
return tags[
|
|
47
|
+
return tags[SPAN_TYPE] === WEB
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
function endpointNameFromTags (tags) {
|
|
48
|
-
return tags[
|
|
49
|
-
tags[
|
|
50
|
-
tags[
|
|
51
|
+
return tags[RESOURCE_NAME] || [
|
|
52
|
+
tags[HTTP_METHOD],
|
|
53
|
+
tags[HTTP_ROUTE]
|
|
51
54
|
].filter(v => v).join(' ')
|
|
52
55
|
}
|
|
53
56
|
|
|
@@ -81,6 +84,7 @@ class NativeWallProfiler {
|
|
|
81
84
|
this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
|
|
82
85
|
this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
|
|
83
86
|
this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
|
|
87
|
+
this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
|
|
84
88
|
this._mapper = undefined
|
|
85
89
|
this._pprof = undefined
|
|
86
90
|
|
|
@@ -99,7 +103,7 @@ class NativeWallProfiler {
|
|
|
99
103
|
|
|
100
104
|
if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
|
|
101
105
|
this._logger.debug(
|
|
102
|
-
`Wall profiler: Enable
|
|
106
|
+
`Wall profiler: Enable trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
|
|
103
107
|
this._emittedFFMessage = true
|
|
104
108
|
}
|
|
105
109
|
|
|
@@ -120,7 +124,8 @@ class NativeWallProfiler {
|
|
|
120
124
|
durationMillis: this._flushIntervalMillis,
|
|
121
125
|
sourceMapper: this._mapper,
|
|
122
126
|
withContexts: this._codeHotspotsEnabled,
|
|
123
|
-
lineNumbers: false
|
|
127
|
+
lineNumbers: false,
|
|
128
|
+
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
|
|
124
129
|
})
|
|
125
130
|
|
|
126
131
|
if (this._codeHotspotsEnabled) {
|
|
@@ -170,7 +175,18 @@ class NativeWallProfiler {
|
|
|
170
175
|
this._enter()
|
|
171
176
|
this._lastSampleCount = 0
|
|
172
177
|
}
|
|
173
|
-
|
|
178
|
+
const profile = this._pprof.time.stop(restart, this._codeHotspotsEnabled ? generateLabels : undefined)
|
|
179
|
+
if (restart) {
|
|
180
|
+
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
181
|
+
if (v8BugDetected === 1) {
|
|
182
|
+
this._logger?.warn('Wall profiler: possible v8 profiler stuck event loop detected.')
|
|
183
|
+
runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_maybe_stuck_event_loop', undefined, true)
|
|
184
|
+
} else if (v8BugDetected === 2) {
|
|
185
|
+
this._logger?.warn('Wall profiler: v8 profiler stuck event loop detected.')
|
|
186
|
+
runtimeMetrics.increment('runtime.node.profiler.v8_cpu_profiler_stuck_event_loop', undefined, true)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return profile
|
|
174
190
|
}
|
|
175
191
|
|
|
176
192
|
profile () {
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
const NoopProxy = require('./noop/proxy')
|
|
3
3
|
const DatadogTracer = require('./tracer')
|
|
4
4
|
const Config = require('./config')
|
|
5
|
-
const
|
|
5
|
+
const runtimeMetrics = require('./runtime_metrics')
|
|
6
6
|
const log = require('./log')
|
|
7
7
|
const { setStartupLogPluginManager } = require('./startup-log')
|
|
8
8
|
const telemetry = require('./telemetry')
|
|
9
9
|
const PluginManager = require('./plugin_manager')
|
|
10
10
|
const remoteConfig = require('./appsec/remote_config')
|
|
11
11
|
const AppsecSdk = require('./appsec/sdk')
|
|
12
|
+
const dogstatsd = require('./dogstatsd')
|
|
12
13
|
|
|
13
14
|
class Tracer extends NoopProxy {
|
|
14
15
|
constructor () {
|
|
@@ -16,6 +17,7 @@ class Tracer extends NoopProxy {
|
|
|
16
17
|
|
|
17
18
|
this._initialized = false
|
|
18
19
|
this._pluginManager = new PluginManager(this)
|
|
20
|
+
this.dogstatsd = new dogstatsd.NoopDogStatsDClient()
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
init (options) {
|
|
@@ -26,6 +28,25 @@ class Tracer extends NoopProxy {
|
|
|
26
28
|
try {
|
|
27
29
|
const config = new Config(options) // TODO: support dynamic code config
|
|
28
30
|
|
|
31
|
+
if (config.dogstatsd) {
|
|
32
|
+
// Custom Metrics
|
|
33
|
+
this.dogstatsd = new dogstatsd.CustomMetrics({
|
|
34
|
+
host: config.dogstatsd.hostname,
|
|
35
|
+
port: config.dogstatsd.port,
|
|
36
|
+
tags: [
|
|
37
|
+
// these are the Runtime Metrics default tags
|
|
38
|
+
// Python also uses these as default Custom Metrics tags
|
|
39
|
+
`service:${config.tags.service}`,
|
|
40
|
+
`env:${config.tags.env}`,
|
|
41
|
+
`version:${config.tags.version}`
|
|
42
|
+
]
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
setInterval(() => {
|
|
46
|
+
this.dogstatsd.flush()
|
|
47
|
+
}, 10 * 1000).unref()
|
|
48
|
+
}
|
|
49
|
+
|
|
29
50
|
if (config.remoteConfig.enabled && !config.isCiVisibility) {
|
|
30
51
|
const rc = remoteConfig.enable(config)
|
|
31
52
|
|
|
@@ -58,7 +79,7 @@ class Tracer extends NoopProxy {
|
|
|
58
79
|
}
|
|
59
80
|
|
|
60
81
|
if (config.runtimeMetrics) {
|
|
61
|
-
|
|
82
|
+
runtimeMetrics.start(config)
|
|
62
83
|
}
|
|
63
84
|
|
|
64
85
|
if (config.tracing) {
|
|
File without changes
|