Skip to content

Commit 257ed74

Browse files
fix: prioritized error.cause.stack for span.stack if available (#2280)
refs https://jsw.ibm.com/browse/INSTA-71590
1 parent cab41fc commit 257ed74

File tree

2 files changed

+89
-3
lines changed

2 files changed

+89
-3
lines changed

packages/core/src/tracing/tracingUtil.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,9 +393,16 @@ exports.setErrorDetails = function setErrorDetails(span, error, technology) {
393393
return;
394394
}
395395

396-
// If the mode is error or all and an error occurred, generate and overwrite the stack trace with the error
397-
if (normalizedError.stack) {
398-
const stackArray = stackTrace.parseStackTraceFromString(normalizedError.stack);
396+
// If stack trace collection is set to 'error' or 'all' and an error occurs,
397+
// generate a stack trace from the error and overwrite any existing stack.
398+
// If the error has a `cause` property and it is an Error instance, prefer
399+
// the cause’s stack trace, as it represents the root cause.
400+
// See: https://nodejs.org/api/errors.html#errorcause
401+
const stackToUse =
402+
(normalizedError?.cause instanceof Error && normalizedError.cause.stack) || normalizedError.stack;
403+
404+
if (stackToUse) {
405+
const stackArray = stackTrace.parseStackTraceFromString(stackToUse);
399406
span.stack = stackArray.length > 0 ? stackArray.slice(0, stackTraceLength) : span.stack || [];
400407
} else {
401408
span.stack = span.stack || [];

packages/core/test/tracing/tracingUtil_test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,85 @@ describe('tracing/tracingUtil', () => {
820820

821821
expect(span.data.test.error).to.equal('Error: The message failed to send');
822822
});
823+
824+
it('should use cause stack when cause is an Error instance with stack', () => {
825+
const span = {
826+
data: { test: {} }
827+
};
828+
829+
const rootCause = new Error('Root cause error');
830+
rootCause.stack =
831+
'Error: Root cause error\n' +
832+
' at rootCauseFunction (/path/to/root.js:10:5)\n' +
833+
' at deepFunction (/path/to/deep.js:20:10)';
834+
835+
const wrappedError = new Error('Wrapped error', { cause: rootCause });
836+
wrappedError.stack =
837+
'Error: Wrapped error\n' +
838+
' at wrapperFunction (/path/to/wrapper.js:5:3)\n' +
839+
' at mainFunction (/path/to/main.js:15:7)';
840+
841+
setErrorDetails(span, wrappedError, 'test');
842+
843+
expect(span.data.test.error).to.equal('Error: Root cause error');
844+
expect(span.stack).to.be.an('array');
845+
expect(span.stack.length).to.be.greaterThan(0);
846+
expect(span.stack[0].c).to.include('root.js');
847+
expect(span.stack[0].m).to.equal('rootCauseFunction');
848+
});
849+
850+
it('should fallback to main error stack when cause has no stack', () => {
851+
const span = {
852+
data: { test: {} }
853+
};
854+
855+
const causeWithoutStack = new Error('Cause without stack');
856+
delete causeWithoutStack.stack;
857+
858+
const wrappedError = new Error('Wrapped error', { cause: causeWithoutStack });
859+
wrappedError.stack =
860+
'Error: Wrapped error\n' +
861+
' at mainErrorFunction (/path/to/main.js:10:5)\n' +
862+
' at callerFunction (/path/to/caller.js:20:10)';
863+
864+
setErrorDetails(span, wrappedError, 'test');
865+
866+
expect(span.data.test.error).to.equal('Error: Cause without stack');
867+
expect(span.stack).to.be.an('array');
868+
expect(span.stack.length).to.be.greaterThan(0);
869+
expect(span.stack[0].c).to.include('main.js');
870+
expect(span.stack[0].m).to.equal('mainErrorFunction');
871+
});
872+
873+
it('should use main error stack when cause is not an Error instance', () => {
874+
const span = {
875+
data: { test: {} }
876+
};
877+
878+
const error = new Error('Main error', { cause: 'string cause' });
879+
880+
setErrorDetails(span, error, 'test');
881+
882+
expect(span.data.test.error).to.equal('Error: Main error');
883+
expect(span.stack).to.be.an('array');
884+
expect(span.stack.length).to.be.greaterThan(0);
885+
});
886+
887+
it('should handle cause with empty stack string', () => {
888+
const span = {
889+
data: { test: {} }
890+
};
891+
892+
const causeWithEmptyStack = new Error('Cause error');
893+
causeWithEmptyStack.stack = '';
894+
const wrappedError = new Error('Wrapped error', { cause: causeWithEmptyStack });
895+
896+
setErrorDetails(span, wrappedError, 'test');
897+
898+
expect(span.data.test.error).to.equal('Error: Cause error');
899+
expect(span.stack).to.be.an('array');
900+
expect(span.stack.length).to.be.greaterThan(0);
901+
});
823902
});
824903

825904
describe('setErrorDetails with stackTraceMode filtering', () => {

0 commit comments

Comments
 (0)