diff --git a/packages/datadog-plugin-fetch/test/index.spec.js b/packages/datadog-plugin-fetch/test/index.spec.js index 6871c0b60ab..20e51db8da2 100644 --- a/packages/datadog-plugin-fetch/test/index.spec.js +++ b/packages/datadog-plugin-fetch/test/index.spec.js @@ -1,5 +1,7 @@ 'use strict' +const util = require('node:util') + const { expect } = require('chai') const { describe, it, beforeEach, afterEach } = require('mocha') @@ -226,7 +228,7 @@ describe('Plugin', function () { .assertSomeTraces(traces => { expect(traces[0][0].meta).to.have.property(ERROR_TYPE, error.name) expect(traces[0][0].meta).to.have.property(ERROR_MESSAGE, error.message || error.code) - expect(traces[0][0].meta).to.have.property(ERROR_STACK, error.stack) + expect(traces[0][0].meta).to.have.property(ERROR_STACK, util.inspect(error, { depth: 0 })) expect(traces[0][0].meta).to.have.property('component', 'fetch') }) .then(done) diff --git a/packages/datadog-plugin-http/src/client.js b/packages/datadog-plugin-http/src/client.js index 40f68adb2b4..4a5989e4e18 100644 --- a/packages/datadog-plugin-http/src/client.js +++ b/packages/datadog-plugin-http/src/client.js @@ -1,5 +1,7 @@ 'use strict' +const { URL } = require('url') + const ClientPlugin = require('../../dd-trace/src/plugins/client') const { storage } = require('../../datadog-core') const tags = require('../../../ext/tags') @@ -8,8 +10,8 @@ const formats = require('../../../ext/formats') const HTTP_HEADERS = formats.HTTP_HEADERS const urlFilter = require('../../dd-trace/src/plugins/util/urlfilter') const log = require('../../dd-trace/src/log') -const { CLIENT_PORT_KEY, COMPONENT, ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') -const { URL } = require('url') +const { CLIENT_PORT_KEY, COMPONENT } = require('../../dd-trace/src/constants') +const { addErrorTagsToSpan } = require('../../dd-trace/src/util') const HTTP_STATUS_CODE = tags.HTTP_STATUS_CODE const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS @@ -116,11 +118,7 @@ class HttpClientPlugin extends ClientPlugin { error ({ span, error, args, customRequestTimeout }) { if (!span) return if (error) { - span.addTags({ - [ERROR_TYPE]: error.name, - [ERROR_MESSAGE]: error.message || error.code, - [ERROR_STACK]: error.stack - }) + addErrorTagsToSpan(span, error) } else { // conditions for no error: // 1. not using a custom agent instance with custom timeout specified diff --git a/packages/datadog-plugin-http/test/client.spec.js b/packages/datadog-plugin-http/test/client.spec.js index 5a0be10760d..554ad3650a1 100644 --- a/packages/datadog-plugin-http/test/client.spec.js +++ b/packages/datadog-plugin-http/test/client.spec.js @@ -1,17 +1,20 @@ 'use strict' +const fs = require('node:fs') +const path = require('node:path') +const { inspect } = require('node:util') + +const { expect } = require('chai') +const { satisfies } = require('semver') + const { withNamingSchema, withPeerService } = require('../../dd-trace/test/setup/mocha') const agent = require('../../dd-trace/test/plugins/agent') -const fs = require('fs') -const path = require('path') const tags = require('../../../ext/tags') -const { expect } = require('chai') const { storage } = require('../../datadog-core') const key = fs.readFileSync(path.join(__dirname, './ssl/test.key')) const cert = fs.readFileSync(path.join(__dirname, './ssl/test.crt')) const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') const { rawExpectedSchema } = require('./naming') -const { satisfies } = require('semver') const HTTP_REQUEST_HEADERS = tags.HTTP_REQUEST_HEADERS const HTTP_RESPONSE_HEADERS = tags.HTTP_RESPONSE_HEADERS @@ -552,7 +555,7 @@ describe('Plugin', () => { .assertSomeTraces(traces => { expect(traces[0][0].meta).to.have.property(ERROR_TYPE, error.name) expect(traces[0][0].meta).to.have.property(ERROR_MESSAGE, error.message || error.code) - expect(traces[0][0].meta).to.have.property(ERROR_STACK, error.stack) + expect(traces[0][0].meta).to.have.property(ERROR_STACK, inspect(error, { depth: 0 })) expect(traces[0][0].meta).to.have.property('component', 'http') }) .then(done) @@ -625,7 +628,7 @@ describe('Plugin', () => { expect(traces[0][0]).to.have.property('error', 1) expect(traces[0][0].meta).to.have.property(ERROR_MESSAGE, error.message) expect(traces[0][0].meta).to.have.property(ERROR_TYPE, error.name) - expect(traces[0][0].meta).to.have.property(ERROR_STACK, error.stack) + expect(traces[0][0].meta).to.have.property(ERROR_STACK, inspect(error, { depth: 0 })) expect(traces[0][0].meta).to.not.have.property('http.status_code') expect(traces[0][0].meta).to.have.property('component', 'http') }) diff --git a/packages/datadog-plugin-undici/test/index.spec.js b/packages/datadog-plugin-undici/test/index.spec.js index e2db4be862e..e2f146a27a5 100644 --- a/packages/datadog-plugin-undici/test/index.spec.js +++ b/packages/datadog-plugin-undici/test/index.spec.js @@ -1,9 +1,12 @@ 'use strict' +const { inspect } = require('node:util') + +const { expect } = require('chai') + const { withNamingSchema, withVersions } = require('../../dd-trace/test/setup/mocha') const agent = require('../../dd-trace/test/plugins/agent') const tags = require('../../../ext/tags') -const { expect } = require('chai') const { rawExpectedSchema } = require('./naming') const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') const { NODE_MAJOR } = require('../../../version') @@ -204,7 +207,7 @@ describe('Plugin', () => { .assertSomeTraces(traces => { expect(traces[0][0].meta).to.have.property(ERROR_TYPE, error.name) expect(traces[0][0].meta).to.have.property(ERROR_MESSAGE, error.message || error.code) - expect(traces[0][0].meta).to.have.property(ERROR_STACK, error.stack) + expect(traces[0][0].meta).to.have.property(ERROR_STACK, inspect(error, { depth: 0 })) expect(traces[0][0].meta).to.have.property('component', 'undici') }) .then(done) diff --git a/packages/dd-trace/src/format.js b/packages/dd-trace/src/format.js index 3009ab571da..541567e030b 100644 --- a/packages/dd-trace/src/format.js +++ b/packages/dd-trace/src/format.js @@ -212,9 +212,16 @@ function extractError (formattedSpan, error) { // AggregateError only has a code and no message. // TODO(BridgeAR)[31.03.2025]: An AggregateError can have a message. Should // the code just generally be added, if available? - addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_MESSAGE, error.message || error.code) - addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_TYPE, error.name) - addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_STACK, error.stack) + const message = error.message || error.code + if (message != null) { + formattedSpan.meta[ERROR_MESSAGE] = message + } + if (error.name != null) { + formattedSpan.meta[ERROR_TYPE] = error.name + } + if (error.stack != null) { + formattedSpan.meta[ERROR_STACK] = error.stack + } } } diff --git a/packages/dd-trace/src/lambda/runtime/errors.js b/packages/dd-trace/src/lambda/runtime/errors.js index 71f63e97079..b5728946e19 100644 --- a/packages/dd-trace/src/lambda/runtime/errors.js +++ b/packages/dd-trace/src/lambda/runtime/errors.js @@ -7,7 +7,10 @@ class ExtendedError extends Error { constructor (reason) { + const { stackTraceLimit } = Error + Error.stackTraceLimit = 0 super(reason) + Error.stackTraceLimit = stackTraceLimit Object.setPrototypeOf(this, new.target.prototype) } } diff --git a/packages/dd-trace/src/llmobs/span_processor.js b/packages/dd-trace/src/llmobs/span_processor.js index 2492aa11449..e70bdff8332 100644 --- a/packages/dd-trace/src/llmobs/span_processor.js +++ b/packages/dd-trace/src/llmobs/span_processor.js @@ -1,5 +1,7 @@ 'use strict' +const { inspect } = require('node:util') + const { SPAN_KIND, MODEL_NAME, @@ -140,7 +142,7 @@ class LLMObsSpanProcessor { if (error) { meta[ERROR_MESSAGE] = spanTags[ERROR_MESSAGE] || error.message || error.code meta[ERROR_TYPE] = spanTags[ERROR_TYPE] || error.name - meta[ERROR_STACK] = spanTags[ERROR_STACK] || error.stack + meta[ERROR_STACK] = spanTags[ERROR_STACK] || (error.stack ? inspect(error, { depth: 0 }) : error.stack) } const metrics = mlObsTags[METRICS] || {} diff --git a/packages/dd-trace/src/opentelemetry/span.js b/packages/dd-trace/src/opentelemetry/span.js index 2cf4b20cb42..6fab9c95b57 100644 --- a/packages/dd-trace/src/opentelemetry/span.js +++ b/packages/dd-trace/src/opentelemetry/span.js @@ -9,10 +9,12 @@ const { timeInputToHrTime } = require('@opentelemetry/core') const tracer = require('../../') const DatadogSpan = require('../opentracing/span') -const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK, IGNORE_OTEL_ERROR } = require('../constants') +const { ERROR_MESSAGE, IGNORE_OTEL_ERROR } = require('../constants') const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags') const kinds = require('../../../../ext/kinds') +const { addErrorTagsToSpan } = require('../util') + const SpanContext = require('./span_context') const id = require('../id') @@ -290,12 +292,8 @@ class Span { } recordException (exception, timeInput) { - this._ddSpan.addTags({ - [ERROR_TYPE]: exception.name, - [ERROR_MESSAGE]: exception.message, - [ERROR_STACK]: exception.stack, - [IGNORE_OTEL_ERROR]: this._ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true - }) + addErrorTagsToSpan(this._ddSpan, exception) + this._ddSpan.setTag(IGNORE_OTEL_ERROR, this._ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true) const attributes = {} if (exception.message) attributes['exception.message'] = exception.message if (exception.type) attributes['exception.type'] = exception.type diff --git a/packages/dd-trace/src/plugins/util/web.js b/packages/dd-trace/src/plugins/util/web.js index 48fddbbeb40..0e31543ee89 100644 --- a/packages/dd-trace/src/plugins/util/web.js +++ b/packages/dd-trace/src/plugins/util/web.js @@ -8,9 +8,10 @@ const tags = require('../../../../../ext/tags') const types = require('../../../../../ext/types') const kinds = require('../../../../../ext/kinds') const urlFilter = require('./urlfilter') -const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../constants') +const { ERROR_MESSAGE } = require('../../constants') const { createInferredProxySpan, finishInferredProxySpan } = require('./inferred_proxy') const TracingPlugin = require('../tracing') +const { addErrorTagsToSpan } = require('../../util') let extractIp @@ -216,13 +217,7 @@ const web = { const span = context.middleware.pop() if (span) { - if (error) { - span.addTags({ - [ERROR_TYPE]: error.name, - [ERROR_MESSAGE]: error.message, - [ERROR_STACK]: error.stack - }) - } + addErrorTagsToSpan(span, error) span.finish() } diff --git a/packages/dd-trace/src/tracer.js b/packages/dd-trace/src/tracer.js index af73b4bcecf..af5c7ce516d 100644 --- a/packages/dd-trace/src/tracer.js +++ b/packages/dd-trace/src/tracer.js @@ -3,12 +3,11 @@ const Tracer = require('./opentracing/tracer') const tags = require('../../../ext/tags') const Scope = require('./scope') -const { isError } = require('./util') const { setStartupLogConfig } = require('./startup-log') -const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../../dd-trace/src/constants') const { DataStreamsCheckpointer, DataStreamsManager, DataStreamsProcessor } = require('./datastreams') const { flushStartupLogs } = require('../../datadog-instrumentations/src/helpers/check-require-cache') const log = require('./log/writer') +const { addErrorTagsToSpan } = require('./util') const SPAN_TYPE = tags.SPAN_TYPE const RESOURCE_NAME = tags.RESOURCE_NAME @@ -66,7 +65,7 @@ class DatadogTracer extends Tracer { try { if (fn.length > 1) { return this.scope().activate(span, () => fn(span, err => { - addError(span, err) + addErrorTagsToSpan(span, err) span.finish() })) } @@ -80,7 +79,7 @@ class DatadogTracer extends Tracer { return value }, err => { - addError(span, err) + addErrorTagsToSpan(span, err) span.finish() throw err } @@ -90,7 +89,7 @@ class DatadogTracer extends Tracer { return result } catch (e) { - addError(span, e) + addErrorTagsToSpan(span, e) span.finish() throw e } @@ -145,16 +144,6 @@ class DatadogTracer extends Tracer { } } -function addError (span, error) { - if (isError(error)) { - span.addTags({ - [ERROR_TYPE]: error.name, - [ERROR_MESSAGE]: error.message, - [ERROR_STACK]: error.stack - }) - } -} - function addTags (span, options) { const tags = {} diff --git a/packages/dd-trace/src/util.js b/packages/dd-trace/src/util.js index cc2ab8b2c97..844301c857b 100644 --- a/packages/dd-trace/src/util.js +++ b/packages/dd-trace/src/util.js @@ -1,7 +1,10 @@ 'use strict' +const { inspect } = require('util') const path = require('path') +const { ERROR_STACK, ERROR_MESSAGE, ERROR_TYPE } = require('./constants') + function isTrue (str) { str = String(str).toLowerCase() return str === 'true' || str === '1' @@ -83,7 +86,18 @@ function normalizePluginEnvName (envPluginName, makeLowercase = false) { return makeLowercase ? envPluginName.toLowerCase() : envPluginName } +function addErrorTagsToSpan (span, error) { + if (isError(error)) { + span.addTags({ + [ERROR_TYPE]: error.name, + [ERROR_MESSAGE]: error.message || error.code, + [ERROR_STACK]: error.stack ? inspect(error, { depth: 0 }) : error.stack + }) + } +} + module.exports = { + addErrorTagsToSpan, isTrue, isFalse, isError, diff --git a/packages/dd-trace/test/llmobs/plugins/openai/openaiv3.spec.js b/packages/dd-trace/test/llmobs/plugins/openai/openaiv3.spec.js index a083790b87a..f6d0e552b32 100644 --- a/packages/dd-trace/test/llmobs/plugins/openai/openaiv3.spec.js +++ b/packages/dd-trace/test/llmobs/plugins/openai/openaiv3.spec.js @@ -1,5 +1,7 @@ 'use strict' +const { inspect } = require('node:util') + const chai = require('chai') const { describe, it, beforeEach } = require('mocha') const semifies = require('semifies') @@ -239,7 +241,7 @@ describe('integrations', () => { error, errorType: error.type || error.name, errorMessage: error.message, - errorStack: error.stack + errorStack: inspect(error, { depth: 0 }) }) expect(llmobsSpans[0]).to.deepEqualWithMockValues(expected) @@ -292,7 +294,7 @@ describe('integrations', () => { error, errorType: error.type || error.name, errorMessage: error.message, - errorStack: error.stack + errorStack: inspect(error, { depth: 0 }) }) expect(llmobsSpans[0]).to.deepEqualWithMockValues(expected) diff --git a/packages/dd-trace/test/llmobs/plugins/openai/openaiv4.spec.js b/packages/dd-trace/test/llmobs/plugins/openai/openaiv4.spec.js index 3d8911bee82..7bc61dd9fd6 100644 --- a/packages/dd-trace/test/llmobs/plugins/openai/openaiv4.spec.js +++ b/packages/dd-trace/test/llmobs/plugins/openai/openaiv4.spec.js @@ -1,5 +1,7 @@ 'use strict' +const { inspect } = require('node:util') + const chai = require('chai') const { describe, it, beforeEach } = require('mocha') const semifies = require('semifies') @@ -407,7 +409,7 @@ describe('integrations', () => { error, errorType: 'Error', errorMessage: error.message, - errorStack: error.stack + errorStack: inspect(error, { depth: 0 }) }) expect(llmobsSpans[0]).to.deepEqualWithMockValues(expected) @@ -456,7 +458,7 @@ describe('integrations', () => { error, errorType: 'Error', errorMessage: error.message, - errorStack: error.stack + errorStack: inspect(error, { depth: 0 }) }) expect(llmobsSpans[0]).to.deepEqualWithMockValues(expected) diff --git a/packages/dd-trace/test/opentelemetry/span.spec.js b/packages/dd-trace/test/opentelemetry/span.spec.js index 020ae132479..f2ec897bd8b 100644 --- a/packages/dd-trace/test/opentelemetry/span.spec.js +++ b/packages/dd-trace/test/opentelemetry/span.spec.js @@ -1,26 +1,27 @@ 'use strict' +const { inspect } = require('util') +const { performance } = require('perf_hooks') + const { expect } = require('chai') const { describe, it } = require('tap').mocha const sinon = require('sinon') -const { performance } = require('perf_hooks') -const { timeOrigin } = performance const { timeInputToHrTime } = require('@opentelemetry/core') +const api = require('@opentelemetry/api') require('../setup/core') const tracer = require('../../').init() - -const api = require('@opentelemetry/api') const TracerProvider = require('../../src/opentelemetry/tracer_provider') const SpanContext = require('../../src/opentelemetry/span_context') const { NoopSpanProcessor } = require('../../src/opentelemetry/span_processor') - const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE, IGNORE_OTEL_ERROR } = require('../../src/constants') const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags') const kinds = require('../../../../ext/kinds') const format = require('../../src/format') +const { timeOrigin } = performance + const spanKindNames = { [api.SpanKind.INTERNAL]: kinds.INTERNAL, [api.SpanKind.SERVER]: kinds.SERVER, @@ -385,7 +386,7 @@ describe('OTel Span', () => { const { _tags } = span._ddSpan.context() expect(_tags).to.have.property(ERROR_TYPE, error.name) expect(_tags).to.have.property(ERROR_MESSAGE, error.message) - expect(_tags).to.have.property(ERROR_STACK, error.stack) + expect(_tags).to.have.property(ERROR_STACK, inspect(error)) expect(_tags).to.have.property(IGNORE_OTEL_ERROR, true) const events = span._ddSpan._events @@ -423,21 +424,25 @@ describe('OTel Span', () => { const span = makeSpan('name') class TestError extends Error { - constructor () { - super('test message') + abc = 123 + + deep = { + invisible: 'invisible' } } const time = timeInputToHrTime(60000 + timeOrigin) const timeInMilliseconds = time[0] * 1e3 + time[1] / 1e6 - const error = new TestError() + const cause = new TypeError('cause') + const error = new TestError('test message', { cause }) span.recordException(error) const { _tags } = span._ddSpan.context() expect(_tags).to.have.property(ERROR_TYPE, error.name) expect(_tags).to.have.property(ERROR_MESSAGE, error.message) - expect(_tags).to.have.property(ERROR_STACK, error.stack) + expect(_tags).to.not.have.property(ERROR_STACK, inspect(error, { depth: 1 })) + expect(_tags).to.have.property(ERROR_STACK, inspect(error, { depth: 0 })) const events = span._ddSpan._events expect(events).to.have.lengthOf(1)