Skip to content

Commit

Permalink
feat(sdk): add method to mark the current span as erroneous
Browse files Browse the repository at this point in the history
  • Loading branch information
Bastian Krol committed Jun 15, 2023
1 parent c12ff39 commit 2cfcc7b
Show file tree
Hide file tree
Showing 5 changed files with 497 additions and 10 deletions.
25 changes: 25 additions & 0 deletions packages/collector/test/tracing/api/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,31 @@ app.get('/span/annotate-path-array', (req, res) => {
});
});

app.get('/span/mark-as-erroneous', (req, res) => {
const span = instana.currentSpan();
span.markAsErroneous();
res.json({
span: serialize(span)
});
});

app.get('/span/mark-as-erroneous-custom-message', (req, res) => {
const span = instana.currentSpan();
span.markAsErroneous('custom error message', 'custom.path.error');
res.json({
span: serialize(span)
});
});

app.get('/span/mark-as-non-erroneous', (req, res) => {
const span = instana.currentSpan();
span.markAsErroneous();
span.markAsNonErroneous();
res.json({
span: serialize(span)
});
});

app.get('/span/manuallyended', (req, res) => {
const span = instana.currentSpan();
span.disableAutoEnd();
Expand Down
25 changes: 25 additions & 0 deletions packages/collector/test/tracing/api/app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,31 @@ app.get('/span/annotate-path-array', (req, res) => {
});
});

app.get('/span/mark-as-erroneous', (req, res) => {
const span = instana.currentSpan();
span.markAsErroneous();
res.json({
span: serialize(span)
});
});

app.get('/span/mark-as-erroneous-custom-message', (req, res) => {
const span = instana.currentSpan();
span.markAsErroneous('custom error message', 'custom.path.error');
res.json({
span: serialize(span)
});
});

app.get('/span/mark-as-non-erroneous', (req, res) => {
const span = instana.currentSpan();
span.markAsErroneous();
span.markAsNonErroneous();
res.json({
span: serialize(span)
});
});

app.get('/span/manuallyended', (req, res) => {
const span = instana.currentSpan();
span.disableAutoEnd();
Expand Down
50 changes: 43 additions & 7 deletions packages/collector/test/tracing/api/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ mochaSuiteFn('tracing/api', function () {
const now = Date.now();
return controls
.sendRequest({
method: 'GET',
path: '/span/active'
})
.then(response => {
Expand All @@ -55,7 +54,6 @@ mochaSuiteFn('tracing/api', function () {
it('must annotate a nested value (path given as flat string)', () =>
controls
.sendRequest({
method: 'GET',
path: '/span/annotate-path-flat-string'
})
.then(response => {
Expand All @@ -74,7 +72,6 @@ mochaSuiteFn('tracing/api', function () {
it('must annotate a nested value (path given as array)', () =>
controls
.sendRequest({
method: 'GET',
path: '/span/annotate-path-array'
})
.then(response => {
Expand All @@ -89,11 +86,53 @@ mochaSuiteFn('tracing/api', function () {
expect(span.handleConstructorName).to.equal('SpanHandle');
}));

it('must mark the current span as erroneous', () =>
controls
.sendRequest({
path: '/span/mark-as-erroneous'
})
.then(response => {
const span = response.span;
expect(span).to.exist;
expect(span.name).to.equal('node.http.server');
expect(span.errorCount).to.equal(1);
expect(span.data.http.error).to.equal(
'This call has been marked as erroneous via the Instana Node.js SDK, no error message has been supplied.'
);
}));

it('must mark the current span as erroneous with a custom error message', () =>
controls
.sendRequest({
path: '/span/mark-as-erroneous-custom-message'
})
.then(response => {
const span = response.span;
expect(span).to.exist;
expect(span.name).to.equal('node.http.server');
expect(span.errorCount).to.equal(1);
expect(span.data.http.error).to.not.exist;
expect(span.data.custom.path.error).to.equal('custom error message');
}));

it('must mark the current span as non-erroneous', () =>
controls
.sendRequest({
path: '/span/mark-as-non-erroneous'
})
.then(response => {
const span = response.span;
expect(span).to.exist;
expect(span.name).to.equal('node.http.server');
expect(span.errorCount).to.equal(0);
expect(span.name).to.equal('node.http.server');
expect(span.data.http.error).to.not.exist;
}));

it('must manually end the currently active span', () => {
const now = Date.now();
return controls
.sendRequest({
method: 'GET',
path: '/span/manuallyended'
})
.then(response => {
Expand Down Expand Up @@ -126,7 +165,6 @@ mochaSuiteFn('tracing/api', function () {
it('must provide a noop span handle', () =>
controls
.sendRequest({
method: 'GET',
path: '/span/active'
})
.then(response => {
Expand All @@ -148,7 +186,6 @@ mochaSuiteFn('tracing/api', function () {
it('must do nothing when trying to manually end the currently active span', () =>
controls
.sendRequest({
method: 'GET',
path: '/span/manuallyended'
})
.then(response => {
Expand All @@ -170,7 +207,6 @@ mochaSuiteFn('tracing/api', function () {
it('must do nothing when trying to annotate', () =>
controls
.sendRequest({
method: 'GET',
path: '/span/annotate-path-flat-string'
})
.then(response => {
Expand Down
129 changes: 126 additions & 3 deletions packages/core/src/tracing/spanHandle.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

const constants = require('./constants');

/** @type {import('../logger').GenericLogger} */
let logger;
logger = require('../logger').getLogger('tracing/spanHandle', newLogger => {
logger = newLogger;
});

/**
* Provides very limited access from client code to the current active span.
* @param {import('./cls').InstanaBaseSpan} _span
Expand Down Expand Up @@ -124,8 +130,17 @@ SpanHandle.prototype.setCorrelationType = function setCorrelationType(correlatio
};

/**
* @param {string} path
* @param {*} value
* Adds an annotation (also known as a tag or custom tag) to the span. The path can be provided as a dot-separated
* string or as an array of strings. That is, the following two calls are equivalent:
* - span.annotate('sdk.custom.tags.myTag', 'My Value'), and
* - span.annotate(['sdk', 'custom', 'tags', 'myTag'], 'My Value').
*
* Note that custom tags should always be prefixed by sdk.custom.tags. You can also use this method to override standard
* tags, like the HTTP path template (example: span.annotate('http.path_tpl', '/user/{id}/details')), but it is not
* recommended, unless there are very good reasons to interfere with Instana's auto tracing.
*
* @param {string|Array.<string>} path the path of the annotation in the span object
* @param {*} value the value for the annotation
*/
SpanHandle.prototype.annotate = function annotate(path, value) {
if (path == null) {
Expand Down Expand Up @@ -174,7 +189,7 @@ function _annotateWithString(target, path, value) {

/**
* @param {Object.<string, *>} target
* @param {string} path
* @param {Array.<string>} path
* @param {*} value
*/
function _annotateWithArray(target, path, value) {
Expand All @@ -194,6 +209,104 @@ function _annotateWithArray(target, path, value) {
}
}

/**
* Marks the span as an error (that is, it sets the error count for the span to 1). You can optionally provide an error
* message. If no message is provided, a default error message will be set.
*
* @param {string?} errorMessage the error message to add as an annotation
* @param {string|Array.<string>} errorMessagePath the annotation path where the error message will be written to;
* there is usually no need to provide this argument as this will be handled automatically
*/
SpanHandle.prototype.markAsErroneous = function markAsErroneous(
errorMessage = 'This call has been marked as erroneous via the Instana Node.js SDK, no error message has been ' +
'supplied.',
errorMessagePath
) {
this.span.ec = 1;
this._annotateErrorMessage(errorMessage, errorMessagePath);
};

/**
* Marks the span as being not an error (that is, it sets the error count for the span to 0). This is useful if the span
* has been marked erroneous previously (either by autotracing or via span.markAsErroneous) and that earlier decision
* needs to be reverted.
* @param {string|Array.<string>} errorMessagePath the annotation path where the error message has been written to
* earlier; there is usually no need to provide this argument as this will be handled automatically
*/
SpanHandle.prototype.markAsNonErroneous = function markAsNonErroneous(errorMessagePath) {
this.span.ec = 0;
// reset the error message as well
this._annotateErrorMessage(undefined, errorMessagePath);
};

/**
* @param {string?} errorMessage
* @param {string|Array.<string>} errorMessagePath
*/
SpanHandle.prototype._annotateErrorMessage = function _annotateErrorMessage(errorMessage, errorMessagePath) {
if (errorMessagePath) {
this.annotate(errorMessagePath, errorMessage);
} else {
findAndAnnotateErrorMessage(this.span, errorMessage);
}
};

/**
* @param {import('./cls').InstanaBaseSpan} span
* @param {string|Array.<string>} message
*/
function findAndAnnotateErrorMessage(span, message) {
const data = span.data;
if (!data) {
logger.warn(
'The error message annotation cannot be set in span.markAsErroneous/span.markAsNonErroneous, since the ' +
`${span.n} span has no data object.`
);
return;
}

let potentialSpanTypeSpecificDataKeys = Object.keys(data).filter(
key =>
// Some db instrumentations add a span.data.peer object in addition to their main section.
key !== 'peer' &&
// We are only interested in actual object properties, not string properties like span.data.service etc.
data[key] != null &&
typeof data[key] === 'object' &&
!Array.isArray(data[key])
);

if (potentialSpanTypeSpecificDataKeys.length === 0) {
logger.warn(
'The error message annotation cannot be set in span.markAsErroneous/span.markAsNonErroneous, since the ' +
`data object of the ${span.n} span has no keys. Please provide the path to the error message annotation ` +
'explicitly.'
);
return;
}
if (potentialSpanTypeSpecificDataKeys.length > 1 && potentialSpanTypeSpecificDataKeys.includes('sdk')) {
// Example: span.data.(http|rpc|mysql|whatever) _and_ span.data.sdk.custom.tags can legitimately exist on the same
// span when custom annotations have been added to an autotrace span.
// In that case, we want to add the error message to the autotrace span data.
potentialSpanTypeSpecificDataKeys = potentialSpanTypeSpecificDataKeys.filter(key => key !== 'sdk');
}
if (potentialSpanTypeSpecificDataKeys.length > 1) {
logger.warn(
'The error message annotation cannot be set in span.markAsErroneous/span.markAsNonErroneous, since the ' +
`data object of the ${span.n} span has more than one key: ${potentialSpanTypeSpecificDataKeys.join(
', '
)}. Please provide the path to the error message annotation explicitly.`
);
return;
}

const spanTypeSpecificData = data[potentialSpanTypeSpecificDataKeys[0]];
if (message != null) {
spanTypeSpecificData.error = message;
} else {
delete spanTypeSpecificData.error;
}
}

/**
* Switches the span into manual-end-mode. Calls to span#transmit() as used by automatic tracing instrumentation will be
* ignored. Instead, client code needs to finish the span (and trigger transmission) by calling spanHandle#end();
Expand Down Expand Up @@ -304,6 +417,10 @@ NoopSpanHandle.prototype.setCorrelationType = function setCorrelationType() {};

NoopSpanHandle.prototype.annotate = function annotate() {};

NoopSpanHandle.prototype.markAsErroneous = function markAsErroneous() {};

NoopSpanHandle.prototype.markAsNonErroneous = function markAsNonErroneous() {};

NoopSpanHandle.prototype.disableAutoEnd = function disableAutoEnd() {
// provide dummy operation when automatic tracing is not enabled
};
Expand All @@ -327,3 +444,9 @@ exports.getHandleForCurrentSpan = function getHandleForCurrentSpan(cls) {
return new NoopSpanHandle();
}
};

// Only exported for testing purposese.
exports._SpanHandle = SpanHandle;

// Only exported for testing purposese.
exports._NoopSpanHandle = NoopSpanHandle;
Loading

0 comments on commit 2cfcc7b

Please sign in to comment.