Skip to content

Commit 9436b01

Browse files
benglwatson
authored andcommitted
Instrument dd-trace-api (#5145)
* initial poc * get the inject test working * support object mapping for callback args * start adding telemetry * parity on sending and receiving sides * initial dd-trace-api test * rest of dd-trace-api testing * quick test refactor * support span context methods * lint * hoist counter tag * add plugin test * replace -> replaceAll
1 parent 25f089a commit 9436b01

File tree

5 files changed

+421
-0
lines changed

5 files changed

+421
-0
lines changed

.github/workflows/plugins.yml

+8
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,14 @@ jobs:
317317
suffix: plugins-${{ github.job }}
318318
- uses: codecov/codecov-action@v5
319319

320+
dd-trace-api:
321+
runs-on: ubuntu-latest
322+
env:
323+
PLUGINS: dd-trace-api
324+
steps:
325+
- uses: actions/checkout@v4
326+
- uses: ./.github/actions/plugins/test
327+
320328
dns:
321329
runs-on: ubuntu-latest
322330
env:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
'use strict'
2+
3+
const Plugin = require('../../dd-trace/src/plugins/plugin')
4+
const telemetryMetrics = require('../../dd-trace/src/telemetry/metrics')
5+
const apiMetrics = telemetryMetrics.manager.namespace('tracers')
6+
7+
// api ==> here
8+
const objectMap = new WeakMap()
9+
10+
const injectionEnabledTag =
11+
`injection_enabled:${process.env.DD_INJECTION_ENABLED ? 'yes' : 'no'}`
12+
13+
module.exports = class DdTraceApiPlugin extends Plugin {
14+
static get id () {
15+
return 'dd-trace-api'
16+
}
17+
18+
constructor (...args) {
19+
super(...args)
20+
21+
const tracer = this._tracer
22+
23+
this.addSub('datadog-api:v1:tracerinit', ({ proxy }) => {
24+
const proxyVal = proxy()
25+
objectMap.set(proxyVal, tracer)
26+
objectMap.set(proxyVal.appsec, tracer.appsec)
27+
objectMap.set(proxyVal.dogstatsd, tracer.dogstatsd)
28+
})
29+
30+
const handleEvent = (name) => {
31+
const counter = apiMetrics.count('dd_trace_api.called', [
32+
`name:${name.replaceAll(':', '.')}`,
33+
'api_version:v1',
34+
injectionEnabledTag
35+
])
36+
37+
// For v1, APIs are 1:1 with their internal equivalents, so we can just
38+
// call the internal method directly. That's what we do here unless we
39+
// want to override. As the API evolves, this may change.
40+
this.addSub(`datadog-api:v1:${name}`, ({ self, args, ret, proxy, revProxy }) => {
41+
counter.inc()
42+
43+
if (name.includes(':')) {
44+
name = name.split(':').pop()
45+
}
46+
47+
if (objectMap.has(self)) {
48+
self = objectMap.get(self)
49+
}
50+
51+
for (let i = 0; i < args.length; i++) {
52+
if (objectMap.has(args[i])) {
53+
args[i] = objectMap.get(args[i])
54+
}
55+
if (typeof args[i] === 'function') {
56+
const orig = args[i]
57+
args[i] = (...fnArgs) => {
58+
for (let j = 0; j < fnArgs.length; j++) {
59+
if (revProxy && revProxy[j]) {
60+
const proxyVal = revProxy[j]()
61+
objectMap.set(proxyVal, fnArgs[j])
62+
fnArgs[j] = proxyVal
63+
}
64+
}
65+
// TODO do we need to apply(this, ...) here?
66+
return orig(...fnArgs)
67+
}
68+
}
69+
}
70+
71+
try {
72+
ret.value = self[name](...args)
73+
if (proxy) {
74+
const proxyVal = proxy()
75+
objectMap.set(proxyVal, ret.value)
76+
ret.value = proxyVal
77+
} else if (ret.value && typeof ret.value === 'object') {
78+
throw new TypeError(`Objects need proxies when returned via API (${name})`)
79+
}
80+
} catch (e) {
81+
ret.error = e
82+
}
83+
})
84+
}
85+
86+
// handleEvent('configure')
87+
handleEvent('startSpan')
88+
handleEvent('wrap')
89+
handleEvent('trace')
90+
handleEvent('inject')
91+
handleEvent('extract')
92+
handleEvent('getRumData')
93+
handleEvent('profilerStarted')
94+
handleEvent('context:toTraceId')
95+
handleEvent('context:toSpanId')
96+
handleEvent('context:toTraceparent')
97+
handleEvent('span:context')
98+
handleEvent('span:setTag')
99+
handleEvent('span:addTags')
100+
handleEvent('span:finish')
101+
handleEvent('span:addLink')
102+
handleEvent('scope')
103+
handleEvent('scope:activate')
104+
handleEvent('scope:active')
105+
handleEvent('scope:bind')
106+
handleEvent('appsec:blockRequest')
107+
handleEvent('appsec:isUserBlocked')
108+
handleEvent('appsec:setUser')
109+
handleEvent('appsec:trackCustomEvent')
110+
handleEvent('appsec:trackUserLoginFailureEvent')
111+
handleEvent('appsec:trackUserLoginSuccessEvent')
112+
handleEvent('dogstatsd:decrement')
113+
handleEvent('dogstatsd:distribution')
114+
handleEvent('dogstatsd:flush')
115+
handleEvent('dogstatsd:gauge')
116+
handleEvent('dogstatsd:histogram')
117+
handleEvent('dogstatsd:increment')
118+
handleEvent('use')
119+
}
120+
}

0 commit comments

Comments
 (0)