Skip to content

Commit b293353

Browse files
authored
Merge branch 'master' into run-stores
2 parents 9e10b4e + eeda4f8 commit b293353

File tree

95 files changed

+4762
-246
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+4762
-246
lines changed

.github/workflows/release-dev.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
3131
injection-image-publish:
3232
runs-on: ubuntu-latest
33-
needs: ['publish']
33+
needs: ['dev_release']
3434
steps:
3535
- uses: actions/checkout@v3
3636
- uses: actions/setup-node@v3

.gitlab-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ package:
2929
stage: deploy
3030
variables:
3131
PRODUCT_NAME: auto_inject-node
32+
PACKAGE_FILTER: js # product name is "node" but package name ends "js"
3233

3334
deploy_to_reliability_env:
3435
stage: deploy

index.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,6 +1450,20 @@ declare namespace plugins {
14501450
};
14511451
}
14521452

1453+
/**
1454+
* This plugin automatically instruments the
1455+
* [openai](https://platform.openai.com/docs/api-reference?lang=node.js) module.
1456+
*
1457+
* Note that for logs to work you'll need to set the `DD_API_KEY` environment variable.
1458+
* You'll also need to adjust any firewall settings to allow the tracer to communicate
1459+
* with `http-intake.logs.datadoghq.com`.
1460+
*
1461+
* Note that for metrics to work you'll need to enable
1462+
* [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/?tab=hostagent#setup)
1463+
* in the agent.
1464+
*/
1465+
interface openai extends Instrumentation {}
1466+
14531467
/**
14541468
* This plugin automatically instruments the
14551469
* [opensearch](https://github.com/opensearch-project/opensearch-js) module.

integration-tests/helpers.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class FakeAgent extends EventEmitter {
2727
async start () {
2828
const app = express()
2929
app.use(bodyParser.raw({ limit: Infinity, type: 'application/msgpack' }))
30+
app.use(bodyParser.json({ limit: Infinity, type: 'application/json' }))
3031
app.put('/v0.4/traces', (req, res) => {
3132
if (req.body.length === 0) return res.status(200).send()
3233
res.status(200).send({ rate_by_service: { 'service:,env:': 1 } })
@@ -43,6 +44,13 @@ class FakeAgent extends EventEmitter {
4344
files: req.files
4445
})
4546
})
47+
app.post('/telemetry/proxy/api/v2/apmtelemetry', (req, res) => {
48+
res.status(200).send()
49+
this.emit('telemetry', {
50+
headers: req.headers,
51+
payload: req.body
52+
})
53+
})
4654

4755
return new Promise((resolve, reject) => {
4856
const timeoutObj = setTimeout(() => {
@@ -103,6 +111,48 @@ class FakeAgent extends EventEmitter {
103111

104112
return resultPromise
105113
}
114+
115+
assertTelemetryReceived (fn, timeout, requestType, expectedMessageCount = 1) {
116+
timeout = timeout || 5000
117+
let resultResolve
118+
let resultReject
119+
let msgCount = 0
120+
const errors = []
121+
122+
const timeoutObj = setTimeout(() => {
123+
resultReject([...errors, new Error('timeout')])
124+
}, timeout)
125+
126+
const resultPromise = new Promise((resolve, reject) => {
127+
resultResolve = () => {
128+
clearTimeout(timeoutObj)
129+
resolve()
130+
}
131+
resultReject = (e) => {
132+
clearTimeout(timeoutObj)
133+
reject(e)
134+
}
135+
})
136+
137+
const messageHandler = msg => {
138+
if (msg.payload.request_type !== requestType) return
139+
msgCount += 1
140+
try {
141+
fn(msg)
142+
if (msgCount === expectedMessageCount) {
143+
resultResolve()
144+
}
145+
} catch (e) {
146+
errors.push(e)
147+
}
148+
if (msgCount === expectedMessageCount) {
149+
this.removeListener('telemetry', messageHandler)
150+
}
151+
}
152+
this.on('telemetry', messageHandler)
153+
154+
return resultPromise
155+
}
106156
}
107157

108158
function spawnProc (filename, options = {}) {

integration-tests/opentelemetry.spec.js

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ const { join } = require('path')
66
const { assert } = require('chai')
77
const { satisfies } = require('semver')
88

9-
function check (agent, proc, timeout, onMessage = () => { }) {
9+
function check (agent, proc, timeout, onMessage = () => { }, isMetrics) {
10+
const messageReceiver = isMetrics
11+
? agent.assertTelemetryReceived(onMessage, timeout, 'generate-metrics')
12+
: agent.assertMessageReceived(onMessage, timeout)
13+
1014
return Promise.all([
11-
agent.assertMessageReceived(onMessage, timeout),
15+
messageReceiver,
1216
new Promise((resolve, reject) => {
1317
const timer = setTimeout(() => {
1418
reject(new Error('Process timed out'))
@@ -38,6 +42,11 @@ function eachEqual (spans, expected, fn) {
3842
return spans.every((span, i) => fn(span) === expected[i])
3943
}
4044

45+
function nearNow (ts, now = Date.now(), range = 1000) {
46+
const delta = Math.abs(now - ts)
47+
return delta < range && delta >= 0
48+
}
49+
4150
describe('opentelemetry', () => {
4251
let agent
4352
let proc
@@ -84,6 +93,49 @@ describe('opentelemetry', () => {
8493
})
8594
})
8695

96+
it('should capture telemetry', () => {
97+
proc = fork(join(cwd, 'opentelemetry/basic.js'), {
98+
cwd,
99+
env: {
100+
DD_TRACE_AGENT_PORT: agent.port,
101+
DD_TRACE_OTEL_ENABLED: 1,
102+
DD_TELEMETRY_HEARTBEAT_INTERVAL: 1,
103+
TIMEOUT: 1500
104+
}
105+
})
106+
107+
return check(agent, proc, timeout, ({ payload }) => {
108+
assert.strictEqual(payload.request_type, 'generate-metrics')
109+
110+
const metrics = payload.payload
111+
assert.strictEqual(metrics.namespace, 'tracers')
112+
113+
const spanCreated = metrics.series.find(({ metric }) => metric === 'span_created')
114+
const spanFinished = metrics.series.find(({ metric }) => metric === 'span_finished')
115+
116+
// Validate common fields between start and finish
117+
for (const series of [spanCreated, spanFinished]) {
118+
assert.ok(series)
119+
120+
assert.strictEqual(series.points.length, 1)
121+
assert.strictEqual(series.points[0].length, 2)
122+
123+
const [ts, value] = series.points[0]
124+
assert.ok(nearNow(ts, Date.now() / 1e3))
125+
assert.strictEqual(value, 1)
126+
127+
assert.strictEqual(series.type, 'count')
128+
assert.strictEqual(series.common, true)
129+
assert.deepStrictEqual(series.tags, [
130+
'integration_name:otel',
131+
'otel_enabled:true',
132+
'lib_language:nodejs',
133+
`version:${process.version}`
134+
])
135+
}
136+
}, true)
137+
})
138+
87139
it('should work within existing datadog-traced http request', async () => {
88140
proc = fork(join(cwd, 'opentelemetry/server.js'), {
89141
cwd,

integration-tests/opentelemetry/basic.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict'
22

3+
const TIMEOUT = Number(process.env.TIMEOUT || 0)
4+
35
const tracer = require('dd-trace').init()
46

57
const { TracerProvider } = tracer
@@ -16,5 +18,8 @@ const otelTracer = ot.trace.getTracer(
1618
otelTracer.startActiveSpan('otel-sub', otelSpan => {
1719
setImmediate(() => {
1820
otelSpan.end()
21+
22+
// Allow the process to be held open to gather telemetry metrics
23+
setTimeout(() => {}, TIMEOUT)
1924
})
2025
})

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"@datadog/native-iast-rewriter": "2.0.1",
7171
"@datadog/native-iast-taint-tracking": "^1.5.0",
7272
"@datadog/native-metrics": "^2.0.0",
73-
"@datadog/pprof": "2.2.2",
73+
"@datadog/pprof": "2.2.3",
7474
"@datadog/sketches-js": "^2.1.0",
7575
"@opentelemetry/api": "^1.0.0",
7676
"@opentelemetry/core": "^1.14.0",

packages/.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"proxyquire": true,
1212
"withNamingSchema": true,
1313
"withVersions": true,
14-
"withExports": true
14+
"withExports": true,
15+
"withPeerService": true
1516
},
1617
"rules": {
1718
"no-unused-expressions": 0,

packages/datadog-instrumentations/src/helpers/hooks.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ module.exports = {
6969
'net': () => require('../net'),
7070
'next': () => require('../next'),
7171
'oracledb': () => require('../oracledb'),
72+
'openai': () => require('../openai'),
7273
'paperplane': () => require('../paperplane'),
7374
'pg': () => require('../pg'),
7475
'pino': () => require('../pino'),
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict'
2+
3+
const {
4+
channel,
5+
addHook
6+
} = require('./helpers/instrument')
7+
const shimmer = require('../../datadog-shimmer')
8+
9+
const startCh = channel('apm:openai:request:start')
10+
const finishCh = channel('apm:openai:request:finish')
11+
const errorCh = channel('apm:openai:request:error')
12+
13+
addHook({ name: 'openai', file: 'dist/api.js', versions: ['>=3.0.0'] }, exports => {
14+
const methodNames = Object.getOwnPropertyNames(exports.OpenAIApi.prototype)
15+
methodNames.shift() // remove leading 'constructor' method
16+
17+
for (const methodName of methodNames) {
18+
shimmer.wrap(exports.OpenAIApi.prototype, methodName, fn => function () {
19+
if (!startCh.hasSubscribers) {
20+
return fn.apply(this, arguments)
21+
}
22+
23+
startCh.publish({
24+
methodName,
25+
args: arguments,
26+
basePath: this.basePath,
27+
apiKey: this.configuration.apiKey
28+
})
29+
30+
return fn.apply(this, arguments)
31+
.then((response) => {
32+
finishCh.publish({
33+
headers: response.headers,
34+
body: response.data,
35+
path: response.request.path,
36+
method: response.request.method
37+
})
38+
39+
return response
40+
})
41+
.catch((err) => {
42+
errorCh.publish({ err })
43+
44+
throw err
45+
})
46+
})
47+
}
48+
49+
return exports
50+
})

0 commit comments

Comments
 (0)