Skip to content

Commit 0c32ea2

Browse files
watsonrochdev
authored andcommitted
[Code Origin] Take source maps into account in stack traces (#6070)
Ensure that the stack traces reported via Code Origin for Spans are resolved using source maps, if Node.js is running with the `--enable-source-maps` flag.
1 parent 643aba7 commit 0c32ea2

File tree

9 files changed

+775
-99
lines changed

9 files changed

+775
-99
lines changed

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export default [
4343
'**/versions', // This is effectively a node_modules tree.
4444
'**/acmeair-nodejs', // We don't own this.
4545
'**/vendor', // Generally, we didn't author this code.
46+
'integration-tests/code-origin/typescript.js', // Generated
4647
'integration-tests/debugger/target-app/source-map-support/bundle.js', // Generated
4748
'integration-tests/debugger/target-app/source-map-support/hello/world.js', // Generated
4849
'integration-tests/debugger/target-app/source-map-support/minify.min.js', // Generated

integration-tests/code-origin.spec.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict'
2+
3+
const assert = require('node:assert')
4+
const path = require('node:path')
5+
const Axios = require('axios')
6+
const { FakeAgent, spawnProc, createSandbox } = require('./helpers')
7+
8+
describe('Code Origin for Spans', function () {
9+
let sandbox, cwd, appFile, agent, proc, axios
10+
11+
before(async () => {
12+
sandbox = await createSandbox(['fastify'])
13+
cwd = sandbox.folder
14+
appFile = path.join(cwd, 'code-origin', 'typescript.js')
15+
})
16+
17+
after(async () => {
18+
await sandbox?.remove()
19+
})
20+
21+
beforeEach(async () => {
22+
agent = await new FakeAgent().start()
23+
proc = await spawnProc(appFile, {
24+
cwd,
25+
env: {
26+
NODE_OPTIONS: '--enable-source-maps',
27+
DD_TRACE_AGENT_URL: `http://localhost:${agent.port}`
28+
},
29+
stdio: 'pipe',
30+
})
31+
axios = Axios.create({ baseURL: proc.url })
32+
})
33+
34+
afterEach(async () => {
35+
proc?.kill()
36+
await agent?.stop()
37+
})
38+
39+
describe('source map support', function () {
40+
it('should support source maps', async () => {
41+
await Promise.all([
42+
agent.assertMessageReceived(({ payload }) => {
43+
const [span] = payload.flatMap(p => p.filter(span => span.name === 'fastify.request'))
44+
assert.strictEqual(span.meta['_dd.code_origin.type'], 'entry')
45+
assert.ok(span.meta['_dd.code_origin.frames.0.file'].endsWith(`${cwd}/code-origin/typescript.ts`))
46+
assert.strictEqual(span.meta['_dd.code_origin.frames.0.line'], '10')
47+
assert.strictEqual(span.meta['_dd.code_origin.frames.0.column'], '5')
48+
assert.strictEqual(span.meta['_dd.code_origin.frames.0.method'], '<anonymous>')
49+
assert.strictEqual(span.meta['_dd.code_origin.frames.0.type'], 'Object')
50+
}, 2_500),
51+
await axios.get('/')
52+
])
53+
})
54+
})
55+
})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env sh
2+
3+
npx --package=typescript -- tsc --sourceMap integration-tests/code-origin/typescript.ts

integration-tests/code-origin/typescript.js

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integration-tests/code-origin/typescript.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require('dd-trace/init')
2+
3+
// @ts-ignore - fastify will be available at runtime
4+
import Fastify from 'fastify'
5+
6+
const app = Fastify({
7+
logger: true
8+
})
9+
10+
app.get('/', async function handler () {
11+
return { hello: 'world' }
12+
})
13+
14+
app.listen({ port: process.env.APP_PORT || 0 }, (err) => {
15+
if (err) throw err
16+
process.send?.({ port: app.server.address().port })
17+
})

packages/datadog-code-origin/index.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict'
22

3-
const { getUserLandFrames } = require('../dd-trace/src/plugins/util/stacktrace')
3+
const { parseUserLandFrames } = require('../dd-trace/src/plugins/util/stacktrace')
44

55
const ENTRY_SPAN_STACK_FRAMES_LIMIT = 1
66
const EXIT_SPAN_STACK_FRAMES_LIMIT = Number(process.env._DD_CODE_ORIGIN_FOR_SPANS_EXIT_SPAN_MAX_USER_FRAMES) || 8
@@ -36,20 +36,25 @@ function exitTags (topOfStackFunc) {
3636
* @returns {Record<string, string>}
3737
*/
3838
function tag (type, topOfStackFunc, limit) {
39-
const frames = getUserLandFrames(topOfStackFunc, limit)
39+
// The `Error.prepareStackTrace` API doesn't support resolving source maps.
40+
// Fall back to manually parsing the stack trace.
41+
const dummy = {}
42+
Error.captureStackTrace(dummy, topOfStackFunc)
43+
const frames = parseUserLandFrames(dummy.stack, limit)
44+
4045
const tags = {
4146
'_dd.code_origin.type': type
4247
}
4348
for (let i = 0; i < frames.length; i++) {
4449
const frame = frames[i]
45-
tags[`_dd.code_origin.frames.${i}.file`] = frame.file
46-
tags[`_dd.code_origin.frames.${i}.line`] = String(frame.line)
47-
tags[`_dd.code_origin.frames.${i}.column`] = String(frame.column)
48-
if (frame.method) {
49-
tags[`_dd.code_origin.frames.${i}.method`] = frame.method
50+
tags[`_dd.code_origin.frames.${i}.file`] = frame.fileName
51+
tags[`_dd.code_origin.frames.${i}.line`] = frame.lineNumber
52+
tags[`_dd.code_origin.frames.${i}.column`] = frame.columnNumber
53+
if (frame.methodName || frame.functionName) {
54+
tags[`_dd.code_origin.frames.${i}.method`] = frame.methodName || frame.functionName
5055
}
51-
if (frame.type) {
52-
tags[`_dd.code_origin.frames.${i}.type`] = frame.type
56+
if (frame.typeName) {
57+
tags[`_dd.code_origin.frames.${i}.type`] = frame.typeName
5358
}
5459
}
5560
return tags

0 commit comments

Comments
 (0)