diff --git a/packages/collector/test/tracing/databases/couchbase/test.js b/packages/collector/test/tracing/databases/couchbase/test.js index 24e580b78e..60e99bf384 100644 --- a/packages/collector/test/tracing/databases/couchbase/test.js +++ b/packages/collector/test/tracing/databases/couchbase/test.js @@ -45,7 +45,7 @@ const verifyCouchbaseSpan = (controls, entrySpan, options = {}) => [ span => expect(span.data.couchbase.sql).to.contain(options.sql || 'GET'), span => options.error - ? expect(span.data.couchbase.error).to.equal(options.error) + ? expect(span.data.couchbase.error).to.contain(options.error) : expect(span.data.couchbase.error).to.not.exist ]; diff --git a/packages/collector/test/tracing/databases/mysql/app.js b/packages/collector/test/tracing/databases/mysql/app.js index 05d8ce333a..265ef82ab9 100644 --- a/packages/collector/test/tracing/databases/mysql/app.js +++ b/packages/collector/test/tracing/databases/mysql/app.js @@ -200,6 +200,14 @@ app.post('/valuesAndCall', (req, res) => { } }); +app.post('/error', (req, res) => { + if (driver === 'mysql2/promise') { + triggerErrorWithPromises(req, res); + } else { + triggerError(req, res); + } +}); + app.listen(port, () => { log( `Listening on port: ${process.env.APP_PORT} (driver: ${driver}, access: ${accessFunction}, cluster: ${useCluster})` @@ -305,6 +313,49 @@ function insertValuesWithPromisesAndCall(req, res) { }); } +function triggerError(req, res) { + pool.getConnection((err, connection) => { + if (err) { + log('Failed to get connection', err); + res.sendStatus(500); + return; + } + + connection[accessFunction]('SELECT * FROM non_existent_table', queryError => { + connection.release(); + + if (queryError) { + log('Expected error occurred', queryError); + res.sendStatus(500); + return; + } + + res.sendStatus(200); + }); + }); +} + +function triggerErrorWithPromises(req, res) { + pool + .getConnection() + .then(connection => { + wrapAccess(connection, 'SELECT * FROM non_existent_table', null, queryError => { + connection.release(); + + if (queryError) { + log('Expected error occurred', queryError); + res.sendStatus(500); + } else { + res.sendStatus(200); + } + }); + }) + .catch(err => { + log('Failed to get connection', err); + res.sendStatus(500); + }); +} + function log() { const args = Array.prototype.slice.call(arguments); args[0] = logPrefix + args[0]; diff --git a/packages/collector/test/tracing/databases/mysql/test.js b/packages/collector/test/tracing/databases/mysql/test.js index 0b0c4f2f69..6d80793414 100644 --- a/packages/collector/test/tracing/databases/mysql/test.js +++ b/packages/collector/test/tracing/databases/mysql/test.js @@ -320,4 +320,36 @@ function test(env, agentControls) { }) ); })); + + it('must replace stack trace with error stack when query fails', () => + controls + .sendRequest({ + method: 'POST', + path: '/error' + }) + .then(() => + testUtils.retry(() => + agentControls.getSpans().then(spans => { + expect(spans.length).to.equal(2); + const entrySpan = testUtils.expectAtLeastOneMatching(spans, [ + span => expect(span.n).to.equal('node.http.server'), + span => expect(span.f.e).to.equal(String(controls.getPid())), + span => expect(span.f.h).to.equal('agent-stub-uuid') + ]); + + const mysqlSpan = testUtils.expectAtLeastOneMatching(spans, [ + span => expect(span.t).to.equal(entrySpan.t), + span => expect(span.p).to.equal(entrySpan.s), + span => expect(span.n).to.equal('mysql'), + span => expect(span.k).to.equal(constants.EXIT), + span => expect(span.f.e).to.equal(String(controls.getPid())), + span => expect(span.f.h).to.equal('agent-stub-uuid'), + span => expect(span.ec).to.equal(1), + span => expect(span.data.mysql.error).to.exist + ]); + + expect(mysqlSpan.stack).to.exist; + }) + ) + )); } diff --git a/packages/collector/test/tracing/databases/pg_native/test.js b/packages/collector/test/tracing/databases/pg_native/test.js index b3cf30b6ae..61cdb6cd97 100644 --- a/packages/collector/test/tracing/databases/pg_native/test.js +++ b/packages/collector/test/tracing/databases/pg_native/test.js @@ -290,6 +290,7 @@ mochaSuiteFn('tracing/pg-native', function () { return expectAtLeastOneMatching(spans, span => { verifyPgExitBase(span, parent, statement); expect(span.error).to.not.exist; + expect(span.stack).to.exist; expect(span.ec).to.equal(1); expect(span.data.pg.error).to.contain(errorMessage); }); diff --git a/packages/collector/test/tracing/messaging/node-rdkafka/test_definition.js b/packages/collector/test/tracing/messaging/node-rdkafka/test_definition.js index 5fef745288..529928a867 100644 --- a/packages/collector/test/tracing/messaging/node-rdkafka/test_definition.js +++ b/packages/collector/test/tracing/messaging/node-rdkafka/test_definition.js @@ -272,10 +272,10 @@ module.exports.run = function ({ span => expect(span.ec).to.equal(!withError ? 0 : 1), span => (!withError ? expect(span.data.kafka.error).to.not.exist : ''), span => - withError === 'deliveryErrorSender' ? expect(span.data.kafka.error).to.equal('delivery fake error') : '', + withError === 'deliveryErrorSender' ? expect(span.data.kafka.error).to.contain('delivery fake error') : '', span => withError === 'bufferErrorSender' - ? expect(span.data.kafka.error).to.equal('Message must be a buffer or null') + ? expect(span.data.kafka.error).to.contain('Message must be a buffer or null') : '' ]); } diff --git a/packages/collector/test/tracing/misc/stack_trace/test.js b/packages/collector/test/tracing/misc/stack_trace/test.js index 26ca4db039..d8dc6454c3 100644 --- a/packages/collector/test/tracing/misc/stack_trace/test.js +++ b/packages/collector/test/tracing/misc/stack_trace/test.js @@ -130,6 +130,7 @@ const mochaSuiteFn = supportedVersion(process.versions.node) ? describe : descri testUtils.expectAtLeastOneMatching(spans, [ span => expect(span.n).to.equal('node.http.client'), span => expect(span.k).to.equal(constants.EXIT), + span => expect(span.data.http.status).to.equal(201), span => expect(span.stack[2].m).to.equal('fetch'), span => expect(span.stack[2].c).to.contains('node-fetch') ]); diff --git a/packages/collector/test/tracing/protocols/http/client/test.js b/packages/collector/test/tracing/protocols/http/client/test.js index 7bd5ad978b..1ba90a4a47 100644 --- a/packages/collector/test/tracing/protocols/http/client/test.js +++ b/packages/collector/test/tracing/protocols/http/client/test.js @@ -633,7 +633,8 @@ function registerTests(appUsesHttps) { expect(span.data.http.error).to.match(/Invalid URL/); }, span => expect(span.t).to.equal(entrySpan.t), - span => expect(span.p).to.equal(entrySpan.s) + span => expect(span.p).to.equal(entrySpan.s), + span => expect(span.stack).to.be.a('string') ]); expectExactlyOneMatching(spans, span => { expect(span.n).to.equal('node.http.client'); diff --git a/packages/collector/test/tracing/protocols/http/native_fetch/test.js b/packages/collector/test/tracing/protocols/http/native_fetch/test.js index a6b4748098..78d395efa8 100644 --- a/packages/collector/test/tracing/protocols/http/native_fetch/test.js +++ b/packages/collector/test/tracing/protocols/http/native_fetch/test.js @@ -853,12 +853,11 @@ function verifyHttpExit({ expectedClientError = `Failed to parse URL from http:127.0.0.1:${serverControls.port}malformed-url`; } expectations.push(span => expect(span.data.http.status).to.not.exist); - expectations.push(span => expect(span.data.http.error).to.equal(expectedClientError)); + expectations.push(span => expect(span.data.http.error).to.contain(expectedClientError)); } else if (withTimeout) { expectations.push(span => expect(span.data.http.status).to.not.exist); // Early v18.x Node.js versions had "The operation was aborted", the message later changed to - // "The operation was aborted due to timeout". - expectations.push(span => expect(span.data.http.error).to.match(/^The operation was aborted(?: due to timeout)?/)); + expectations.push(span => expect(span.data.http.error).to.contain('The operation was aborted due to timeout')); } else { expectations.push(span => expect(span.data.http.status).to.equal(status)); expectations.push(span => expect(span.data.http.error).to.not.exist); diff --git a/packages/core/src/tracing/instrumentation/cloud/aws-sdk/v2/sqs.js b/packages/core/src/tracing/instrumentation/cloud/aws-sdk/v2/sqs.js index 0590f65c54..d9aa07007f 100644 --- a/packages/core/src/tracing/instrumentation/cloud/aws-sdk/v2/sqs.js +++ b/packages/core/src/tracing/instrumentation/cloud/aws-sdk/v2/sqs.js @@ -392,7 +392,7 @@ function finishSpan(err, data, span) { function addErrorToSpan(err, span) { if (err) { span.ec = 1; - span.data.sqs.error = err.message || err.code || JSON.stringify(err); + tracingUtil.setErrorDetails(span, err, 'sqs'); } } diff --git a/packages/core/src/tracing/instrumentation/cloud/aws-sdk/v3/sqs-consumer.js b/packages/core/src/tracing/instrumentation/cloud/aws-sdk/v3/sqs-consumer.js index 28c3ce826d..cd29f1aba6 100644 --- a/packages/core/src/tracing/instrumentation/cloud/aws-sdk/v3/sqs-consumer.js +++ b/packages/core/src/tracing/instrumentation/cloud/aws-sdk/v3/sqs-consumer.js @@ -7,6 +7,7 @@ const shimmer = require('../../../../shimmer'); const hook = require('../../../../../util/hook'); const cls = require('../../../../cls'); +const tracingUtil = require('../../../../tracingUtil'); function init() { hook.onModuleLoad('sqs-consumer', instrument); @@ -30,7 +31,7 @@ function instrument(SQSConsumer) { }) .catch(err => { span.ec = 1; - span.data.sqs.error = err.message || err.code || JSON.stringify(err); + tracingUtil.setErrorDetails(span, err, 'sqs'); span.d = Date.now() - span.ts; span.transmitManual(); }); @@ -62,7 +63,7 @@ function instrument(SQSConsumer) { }) .catch(err => { span.ec = 1; - span.data.sqs.error = err.message || err.code || JSON.stringify(err); + tracingUtil.setErrorDetails(span, err, 'sqs'); span.d = Date.now() - span.ts; span.transmitManual(); }); diff --git a/packages/core/src/tracing/instrumentation/cloud/azure/blob.js b/packages/core/src/tracing/instrumentation/cloud/azure/blob.js index 2ecd9f97ab..d2b076cf13 100644 --- a/packages/core/src/tracing/instrumentation/cloud/azure/blob.js +++ b/packages/core/src/tracing/instrumentation/cloud/azure/blob.js @@ -105,7 +105,7 @@ function instrumentingOperation({ function finishSpan(error, span) { if (error) { span.ec = 1; - span.data.azstorage.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'azstorage'); } span.d = Date.now() - span.ts; span.transmit(); diff --git a/packages/core/src/tracing/instrumentation/cloud/gcp/pubsub.js b/packages/core/src/tracing/instrumentation/cloud/gcp/pubsub.js index 1e4a4f030f..a2adfec562 100644 --- a/packages/core/src/tracing/instrumentation/cloud/gcp/pubsub.js +++ b/packages/core/src/tracing/instrumentation/cloud/gcp/pubsub.js @@ -254,11 +254,7 @@ function finishSpan(err, messageId, span) { function addErrorToSpan(err, span) { if (err) { span.ec = 1; - if (err.message) { - span.data.gcps.error = err.message; - } else if (typeof err === 'string') { - span.data.gcps.error = err; - } + tracingUtil.setErrorDetails(span, err, 'gcps'); } } diff --git a/packages/core/src/tracing/instrumentation/cloud/gcp/storage.js b/packages/core/src/tracing/instrumentation/cloud/gcp/storage.js index 733f15b2c5..c1d4fbb43d 100644 --- a/packages/core/src/tracing/instrumentation/cloud/gcp/storage.js +++ b/packages/core/src/tracing/instrumentation/cloud/gcp/storage.js @@ -458,7 +458,7 @@ function instrumentedCreateStream(operation, bindEvent, finalEvent, ctx, origina function finishSpan(error, result, span, extractorPost) { if (error) { span.ec = 1; - span.data.gcs.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'gcs'); } if (extractorPost) { diff --git a/packages/core/src/tracing/instrumentation/databases/couchbase.js b/packages/core/src/tracing/instrumentation/databases/couchbase.js index cb10cd29ff..cd800234b0 100644 --- a/packages/core/src/tracing/instrumentation/databases/couchbase.js +++ b/packages/core/src/tracing/instrumentation/databases/couchbase.js @@ -430,7 +430,7 @@ function instrumentTransactions(cluster, connectionStr) { result .catch(err => { span.ec = 1; - span.data.couchbase.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'couchbase'); }) .finally(() => { span.d = Date.now() - span.ts; @@ -497,7 +497,7 @@ function instrumentOperation({ connectionStr, bucketName, getBucketTypeFn, sql, }) .catch(err => { span.ec = 1; - span.data.couchbase.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'couchbase'); }) .finally(() => { span.d = Date.now() - span.ts; @@ -510,7 +510,7 @@ function instrumentOperation({ connectionStr, bucketName, getBucketTypeFn, sql, originalArgs[callbackIndex] = cls.ns.bind(function instanaCallback(err, result) { if (err) { span.ec = 1; - span.data.couchbase.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'couchbase'); } if (resultHandler) { diff --git a/packages/core/src/tracing/instrumentation/databases/db2.js b/packages/core/src/tracing/instrumentation/databases/db2.js index 2b163095bc..c6819d219c 100644 --- a/packages/core/src/tracing/instrumentation/databases/db2.js +++ b/packages/core/src/tracing/instrumentation/databases/db2.js @@ -222,7 +222,7 @@ function captureFetchError(result, span) { argsFetch[fetchIndex] = function instanaFetchCb(fetchCbErr) { if (fetchCbErr) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(fetchCbErr); + tracingUtil.setErrorDetails(span, fetchCbErr, 'db2'); } return fetchCb.apply(this, arguments); @@ -232,7 +232,7 @@ function captureFetchError(result, span) { return originalFn.apply(this, arguments); } catch (caughtErr) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(caughtErr); + tracingUtil.setErrorDetails(span, caughtErr, 'db2'); throw caughtErr; } }; @@ -249,12 +249,12 @@ function captureFetchError(result, span) { if (res instanceof Error) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(res); + tracingUtil.setErrorDetails(span, res, 'db2'); } return res; } catch (err) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'db2'); return err; } @@ -278,14 +278,14 @@ function instrumentQueryHelper(ctx, originalArgs, originalFunction, stmt, isAsyn if (result instanceof Error) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(result); + tracingUtil.setErrorDetails(span, result, 'db2'); } finishSpan(ctx, result, span); return result; } catch (e) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(e); + tracingUtil.setErrorDetails(span, e, 'db2'); finishSpan(ctx, null, span); throw e; } @@ -304,7 +304,7 @@ function instrumentQueryHelper(ctx, originalArgs, originalFunction, stmt, isAsyn originalArgs[customerCallbackIndex] = function instanaCallback(err) { if (err) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'db2'); } finishSpan(ctx, null, span); @@ -324,7 +324,7 @@ function instrumentQueryHelper(ctx, originalArgs, originalFunction, stmt, isAsyn }) .catch(err => { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'db2'); finishSpan(ctx, null, span); return err; }); @@ -394,7 +394,7 @@ function instrumentExecuteHelper(ctx, originalArgs, stmtObject, prepareCallParen return result; } catch (err) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'db2'); finishSpan(ctx, null, span); return err; } @@ -441,7 +441,7 @@ function instrumentExecuteHelper(ctx, originalArgs, stmtObject, prepareCallParen args[origCallbackIndex] = function instanaExecuteCallback(executeErr, result) { if (executeErr) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(executeErr); + tracingUtil.setErrorDetails(span, executeErr, 'db2'); finishSpan(ctx, null, span); return origCallback.apply(this, arguments); } @@ -470,7 +470,7 @@ function instrumentQueryResultHelper(ctx, originalArgs, originalFunction, stmt, return result; } catch (err) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'db2'); finishSpan(ctx, null, span); return err; } @@ -489,7 +489,7 @@ function instrumentQueryResultHelper(ctx, originalArgs, originalFunction, stmt, originalArgs[customerCallbackIndex] = function instanaCallback(err) { if (err) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'db2'); } const result = customerCallback.apply(this, arguments); @@ -551,6 +551,11 @@ function finishSpan(ctx, result, span) { closeSyncCalled = true; span.ec = 1; span.data.db2.error = `'result.closeSync' was not called within ${CLOSE_TIMEOUT_IN_MS}ms.`; + tracingUtil.setErrorDetails( + span, + new Error(`'result.closeSync' was not called within ${CLOSE_TIMEOUT_IN_MS}ms.`), + 'db2' + ); span.d = Date.now() - span.ts; span.transmit(); } @@ -578,7 +583,7 @@ function handleTransaction(ctx, span) { arguments[1] = function instanaOnEndOverride(onEndErr) { if (onEndErr) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(onEndErr) || 'Error not available.'; + tracingUtil.setErrorDetails(span, onEndErr, 'db2'); } span.d = Date.now() - span.ts; @@ -597,7 +602,7 @@ function handleTransaction(ctx, span) { return result; } catch (err) { span.ec = 1; - span.data.db2.error = tracingUtil.getErrorDetails(err) || 'Error not available.'; + tracingUtil.setErrorDetails(span, err, 'db2'); span.transmit(); throw err; } diff --git a/packages/core/src/tracing/instrumentation/databases/elasticsearch.js b/packages/core/src/tracing/instrumentation/databases/elasticsearch.js index 5e04f941fd..4877c53604 100644 --- a/packages/core/src/tracing/instrumentation/databases/elasticsearch.js +++ b/packages/core/src/tracing/instrumentation/databases/elasticsearch.js @@ -200,7 +200,7 @@ function onError(span, error) { span.d = Date.now() - span.ts; span.ec = 1; if (error) { - span.data.elasticsearch.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'elasticsearch'); } if (error.meta && error.meta.meta) { getConnectionDetailsFromResultMeta(span, error.meta); diff --git a/packages/core/src/tracing/instrumentation/databases/ioredis.js b/packages/core/src/tracing/instrumentation/databases/ioredis.js index 8bbb02dea8..e88a1c69e3 100644 --- a/packages/core/src/tracing/instrumentation/databases/ioredis.js +++ b/packages/core/src/tracing/instrumentation/databases/ioredis.js @@ -115,7 +115,7 @@ function instrumentSendCommand(original) { if (error) { span.ec = 1; - span.data.redis.error = error.message; + tracingUtil.setErrorDetails(span, error, 'redis'); } span.transmit(); @@ -221,7 +221,7 @@ function multiCommandEndCallback(clsContextForMultiOrPipeline, span, error) { if (error) { span.ec = commandCount; - span.data.redis.error = error.message; + tracingUtil.setErrorDetails(span, error, 'redis'); } span.transmit(); @@ -241,7 +241,7 @@ function pipelineCommandEndCallback(clsContextForMultiOrPipeline, span, error, r if (error) { // ioredis docs mention that this should never be possible, but better be safe than sorry span.ec = commandCount; - span.data.redis.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'redis'); } else { let numberOfErrors = 0; let sampledError; @@ -257,7 +257,7 @@ function pipelineCommandEndCallback(clsContextForMultiOrPipeline, span, error, r if (numberOfErrors > 0) { span.ec = numberOfErrors; - span.data.redis.error = tracingUtil.getErrorDetails(sampledError); + tracingUtil.setErrorDetails(span, sampledError, 'redis'); } } diff --git a/packages/core/src/tracing/instrumentation/databases/mongodb.js b/packages/core/src/tracing/instrumentation/databases/mongodb.js index 15ca92270c..7582e1917c 100644 --- a/packages/core/src/tracing/instrumentation/databases/mongodb.js +++ b/packages/core/src/tracing/instrumentation/databases/mongodb.js @@ -438,7 +438,7 @@ function createWrappedCallback(span, originalCallback) { return cls.ns.bind(function (error) { if (error) { span.ec = 1; - span.data.mongo.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'mongo'); } span.d = Date.now() - span.ts; @@ -466,7 +466,7 @@ function handleCallbackOrPromise(ctx, originalArgs, originalFunction, span) { }) .catch(err => { span.ec = 1; - span.data.mongo.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'mongo'); span.d = Date.now() - span.ts; span.transmit(); return err; diff --git a/packages/core/src/tracing/instrumentation/databases/mssql.js b/packages/core/src/tracing/instrumentation/databases/mssql.js index 9fca976a40..d1cf837774 100644 --- a/packages/core/src/tracing/instrumentation/databases/mssql.js +++ b/packages/core/src/tracing/instrumentation/databases/mssql.js @@ -161,7 +161,7 @@ function shimBeginTransaction(originalFunction) { function finishSpan(error, span) { if (error) { span.ec = 1; - span.data.mssql.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'mssql'); } span.d = Date.now() - span.ts; diff --git a/packages/core/src/tracing/instrumentation/databases/mysql.js b/packages/core/src/tracing/instrumentation/databases/mysql.js index afee7877bc..a33688fa86 100644 --- a/packages/core/src/tracing/instrumentation/databases/mysql.js +++ b/packages/core/src/tracing/instrumentation/databases/mysql.js @@ -190,7 +190,7 @@ function instrumentedAccessFunction( }) .catch(error => { span.ec = 1; - span.data.mysql.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, exports.spanName); span.d = Date.now() - span.ts; span.transmit(); @@ -205,7 +205,7 @@ function instrumentedAccessFunction( function onResult(error) { if (error) { span.ec = 1; - span.data.mysql.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, exports.spanName); } span.d = Date.now() - span.ts; diff --git a/packages/core/src/tracing/instrumentation/databases/pg.js b/packages/core/src/tracing/instrumentation/databases/pg.js index d0979c58da..eed210ad70 100644 --- a/packages/core/src/tracing/instrumentation/databases/pg.js +++ b/packages/core/src/tracing/instrumentation/databases/pg.js @@ -103,7 +103,7 @@ function instrumentedQuery(ctx, originalQuery, argsForOriginalQuery) { function finishSpan(error, span) { if (error) { span.ec = 1; - span.data.pg.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'pg'); } span.d = Date.now() - span.ts; diff --git a/packages/core/src/tracing/instrumentation/databases/pgNative.js b/packages/core/src/tracing/instrumentation/databases/pgNative.js index ec75bde81e..8407f5b5a9 100644 --- a/packages/core/src/tracing/instrumentation/databases/pgNative.js +++ b/packages/core/src/tracing/instrumentation/databases/pgNative.js @@ -197,7 +197,7 @@ function startSpanBeforeSync(ctx, originalFn, originalArgs, statement, stackTrac function finishSpan(error, span) { if (error) { span.ec = 1; - span.data.pg.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'pg'); } span.d = Date.now() - span.ts; diff --git a/packages/core/src/tracing/instrumentation/databases/prisma.js b/packages/core/src/tracing/instrumentation/databases/prisma.js index 781ba7f30d..dbd1d670af 100644 --- a/packages/core/src/tracing/instrumentation/databases/prisma.js +++ b/packages/core/src/tracing/instrumentation/databases/prisma.js @@ -6,7 +6,7 @@ const shimmer = require('../../shimmer'); const hook = require('../../../util/hook'); -const { getErrorDetails, getStackTrace } = require('../../tracingUtil'); +const tracingUtil = require('../../tracingUtil'); const { EXIT } = require('../../constants'); const cls = require('../../cls'); @@ -140,7 +140,7 @@ function instrumentedRequest(ctx, originalRequest, argsForOriginalRequest) { spanName: 'prisma', kind: EXIT }); - span.stack = getStackTrace(instrumentedRequest, 1); + span.stack = tracingUtil.getStackTrace(instrumentedRequest, 1); const params = argsForOriginalRequest[0] || {}; const providerAndDataSourceUri = ctx._engine ? providerAndDataSourceUriMap.get(ctx._engine) || {} : {}; @@ -221,7 +221,7 @@ function redactPasswordFromMsSQLUrl(url) { function finishSpan(error, span) { if (error) { span.ec = 1; - span.data.prisma.error = getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'prisma'); } span.d = Date.now() - span.ts; diff --git a/packages/core/src/tracing/instrumentation/databases/redis.js b/packages/core/src/tracing/instrumentation/databases/redis.js index ddacf5ad57..8e41ad763f 100644 --- a/packages/core/src/tracing/instrumentation/databases/redis.js +++ b/packages/core/src/tracing/instrumentation/databases/redis.js @@ -365,7 +365,7 @@ function instrumentCommand(original, command, address, cbStyle) { if (error) { span.ec = 1; - span.data.redis.error = tracingUtil.getErrorDetails(error); + tracingUtil.setErrorDetails(span, error, 'redis'); } span.transmit(); @@ -512,6 +512,7 @@ function instrumentMultiExec(origCtx, origArgs, original, address, isAtomic, cbS // v3 = provides sub errors if (err.errors && err.errors.length) { + // TODO: Not updating now as special case span.data.redis.error = err.errors.map(subErr => subErr.message).join('\n'); } } @@ -531,10 +532,7 @@ function buildSubCommandCallback(span, userProvidedCallback) { return function subCommandCallback(err) { if (err) { span.ec++; - - if (!span.data.redis.error) { - span.data.redis.error = tracingUtil.getErrorDetails(err); - } + tracingUtil.setErrorDetails(span, err, 'redis'); } if (typeof userProvidedCallback === 'function') { diff --git a/packages/core/src/tracing/instrumentation/frameworks/express.js b/packages/core/src/tracing/instrumentation/frameworks/express.js index 208daa0885..2f4a89ff17 100644 --- a/packages/core/src/tracing/instrumentation/frameworks/express.js +++ b/packages/core/src/tracing/instrumentation/frameworks/express.js @@ -116,7 +116,7 @@ function annotateHttpEntrySpanWithError(err) { return; } - span.data.http.error = tracingUtil.getErrorDetails(err); + tracingUtil.setErrorDetails(span, err, 'http'); } function shimHandlerRegistration(original) { diff --git a/packages/core/src/tracing/instrumentation/messaging/bull.js b/packages/core/src/tracing/instrumentation/messaging/bull.js index 21115672a6..d0bb2951d3 100644 --- a/packages/core/src/tracing/instrumentation/messaging/bull.js +++ b/packages/core/src/tracing/instrumentation/messaging/bull.js @@ -325,11 +325,8 @@ function finishSpan(err, data, span) { function addErrorToSpan(err, span) { if (err) { span.ec = 1; - if (err.code) { - span.data.bull.error = err.code; - } else if (typeof err === 'string') { - span.data.bull.error = err; - } + + tracingUtil.setErrorDetails(span, err, 'bull'); } } diff --git a/packages/core/src/tracing/instrumentation/messaging/kafkaJs.js b/packages/core/src/tracing/instrumentation/messaging/kafkaJs.js index 16f44c27e3..f887225f13 100644 --- a/packages/core/src/tracing/instrumentation/messaging/kafkaJs.js +++ b/packages/core/src/tracing/instrumentation/messaging/kafkaJs.js @@ -105,7 +105,7 @@ function instrumentedSend(ctx, originalSend, originalArgs, topic, messages) { }) .catch(error => { span.ec = 1; - span.data.kafka.error = error.message; + tracingUtil.setErrorDetails(span, error, 'kafka'); span.d = Date.now() - span.ts; span.transmit(); throw error; @@ -184,7 +184,7 @@ function instrumentedSendBatch(ctx, originalSendBatch, originalArgs, topicMessag }) .catch(error => { span.ec = 1; - span.data.kafka.error = error.message; + tracingUtil.setErrorDetails(span, error, 'kafka'); span.d = Date.now() - span.ts; span.transmit(); throw error; diff --git a/packages/core/src/tracing/instrumentation/messaging/kafkaNode.js b/packages/core/src/tracing/instrumentation/messaging/kafkaNode.js index a243cc3806..0721f7ce35 100644 --- a/packages/core/src/tracing/instrumentation/messaging/kafkaNode.js +++ b/packages/core/src/tracing/instrumentation/messaging/kafkaNode.js @@ -81,7 +81,7 @@ function instrumentedSend(ctx, originalSend, produceRequests, cb) { cls.ns.bind(function onSendCompleted(err) { if (err) { span.ec = 1; - span.data.kafka.error = err.message; + tracingUtil.setErrorDetails(span, err, 'kafka'); } span.d = Date.now() - span.ts; diff --git a/packages/core/src/tracing/instrumentation/messaging/nats.js b/packages/core/src/tracing/instrumentation/messaging/nats.js index 2f9a3e0465..0e10caa626 100644 --- a/packages/core/src/tracing/instrumentation/messaging/nats.js +++ b/packages/core/src/tracing/instrumentation/messaging/nats.js @@ -397,6 +397,7 @@ function addErrorToSpan(err, span) { if (err) { span.ec = 1; + // TODO: Special logic of appending error let errMsg = null; if (err.message) { errMsg = err.message; diff --git a/packages/core/src/tracing/instrumentation/messaging/natsStreaming.js b/packages/core/src/tracing/instrumentation/messaging/natsStreaming.js index cc72ff8777..b844b11b62 100644 --- a/packages/core/src/tracing/instrumentation/messaging/natsStreaming.js +++ b/packages/core/src/tracing/instrumentation/messaging/natsStreaming.js @@ -194,6 +194,7 @@ function addErrorToSpan(err, span) { if (err) { span.ec = 1; + // TODO: special logic of appending error let errMsg = null; if (err.message) { errMsg = err.message; diff --git a/packages/core/src/tracing/instrumentation/messaging/rdkafka.js b/packages/core/src/tracing/instrumentation/messaging/rdkafka.js index d756c6f4f1..14b245457c 100644 --- a/packages/core/src/tracing/instrumentation/messaging/rdkafka.js +++ b/packages/core/src/tracing/instrumentation/messaging/rdkafka.js @@ -167,7 +167,7 @@ function instrumentedProduce(ctx, originalProduce, originalArgs) { if (err) { span.ec = 1; - span.data.kafka.error = err.message; + tracingUtil.setErrorDetails(span, err, 'kafka'); } span.transmit(); @@ -187,7 +187,7 @@ function instrumentedProduce(ctx, originalProduce, originalArgs) { // e.g. cannot send message because format is byte // "Message must be a buffer or null" span.ec = 1; - span.data.kafka.error = error.message; + tracingUtil.setErrorDetails(span, error, 'kafka'); if (!deliveryCb) { span.d = Date.now() - span.ts; @@ -305,7 +305,7 @@ function instrumentedConsumerEmit(ctx, originalEmit, originalArgs) { delete messageData.headers; span.ec = 1; - span.data.kafka.error = messageData.message; + tracingUtil.setErrorDetails(span, messageData, 'kafka'); } setImmediate(() => { diff --git a/packages/core/src/tracing/instrumentation/protocols/grpcJs.js b/packages/core/src/tracing/instrumentation/protocols/grpcJs.js index 63c720a821..6967156fe8 100644 --- a/packages/core/src/tracing/instrumentation/protocols/grpcJs.js +++ b/packages/core/src/tracing/instrumentation/protocols/grpcJs.js @@ -115,9 +115,11 @@ function modifyArgs(name, originalArgs, span) { // No-op, we do not want to mark cancelled calls as erroneous. } else { span.ec = 1; + // TODO: special case of error message if (errorMessage) { span.data.rpc.error = errorMessage; } + tracingUtil.setErrorDetails(span, err, 'rpc'); } } @@ -347,6 +349,7 @@ function createInstrumentedServerHandler(name, type, originalHandler) { if (err.message || err.details) { span.data.rpc.error = err.message || err.details; } + tracingUtil.setErrorDetails(span, err, 'rpc'); } span.d = Date.now() - span.ts; span.transmit(); @@ -372,6 +375,7 @@ function createInstrumentedServerHandler(name, type, originalHandler) { if (err.message || err.details) { span.data.rpc.error = err.message || err.details; } + tracingUtil.setErrorDetails(span, err, 'rpc'); }); call.on('cancelled', () => { @@ -434,6 +438,7 @@ function instrumentedClientMethod( if (errorMessage) { span.data.rpc.error = errorMessage; } + tracingUtil.setErrorDetails(span, err, 'rpc'); } span.transmit(); }); diff --git a/packages/core/src/tracing/instrumentation/protocols/httpClient.js b/packages/core/src/tracing/instrumentation/protocols/httpClient.js index 28909342a9..7b94593db9 100644 --- a/packages/core/src/tracing/instrumentation/protocols/httpClient.js +++ b/packages/core/src/tracing/instrumentation/protocols/httpClient.js @@ -242,6 +242,7 @@ function instrument(coreModule, forceHttps) { span.d = Date.now() - span.ts; span.ec = res.statusCode >= 500 ? 1 : 0; + span.transmit(); if (callback) { @@ -264,7 +265,7 @@ function instrument(coreModule, forceHttps) { // example is a case that triggers a synchronous exception. span.data.http = {}; span.data.http.url = completeCallUrl; - span.data.http.error = e ? e.message : ''; + tracingUtil.setErrorDetails(span, e, 'http'); span.d = Date.now() - span.ts; span.ec = 1; span.transmit(); @@ -312,9 +313,12 @@ function instrument(coreModule, forceHttps) { method: clientRequest.method, url: completeCallUrl }; + // TODO: special-case, we remove this in separate PR span.data.http.error = errorMessage; span.d = Date.now() - span.ts; span.ec = 1; + tracingUtil.setErrorDetails(span, err, 'http'); + span.transmit(); }); }); diff --git a/packages/core/src/tracing/instrumentation/protocols/nativeFetch.js b/packages/core/src/tracing/instrumentation/protocols/nativeFetch.js index 342c217921..a576ba00a7 100644 --- a/packages/core/src/tracing/instrumentation/protocols/nativeFetch.js +++ b/packages/core/src/tracing/instrumentation/protocols/nativeFetch.js @@ -184,7 +184,7 @@ function instrument() { }) .catch(err => { span.ec = 1; - span.data.http.error = err.message; + tracingUtil.setErrorDetails(span, err, 'http'); }) .finally(() => { span.d = Date.now() - span.ts; diff --git a/packages/core/src/tracing/sdk/sdk.js b/packages/core/src/tracing/sdk/sdk.js index 4ba6b6ec75..cc072053ed 100644 --- a/packages/core/src/tracing/sdk/sdk.js +++ b/packages/core/src/tracing/sdk/sdk.js @@ -330,6 +330,8 @@ exports.generate = function (isCallbackApi) { span.data.sdk.custom.tags = {}; } if (span.data.sdk.custom.tags.message == null) { + // TODO: fix sdk error capture and handling + // This fn used getErrorDetails for now and will need to change span.data.sdk.custom.tags.message = tracingUtil.getErrorDetails(error); } } diff --git a/packages/core/src/tracing/tracingUtil.js b/packages/core/src/tracing/tracingUtil.js index 9e0768130b..323ea0cf8d 100644 --- a/packages/core/src/tracing/tracingUtil.js +++ b/packages/core/src/tracing/tracingUtil.js @@ -276,3 +276,32 @@ exports.findCallback = (/** @type {string | any[]} */ originalArgs) => { callbackIndex }; }; + +/** + * @param {import('../core').InstanaBaseSpan} span - The span to update + * @param {Error} error + * @param {string} technology - The technology name (e.g., 'mysql', 'pg', 'http') + */ +// @ts-ignore +exports.setErrorDetails = function setErrorDetails(span, error, technology) { + if (!error) { + return; + } + + if (technology && span.data?.[technology]) { + // This extra check allows instrumentations to override the error property (not recommended) + if (!span.data[technology].error) { + const combinedMessage = error?.message + ? `${error.name || 'Error'}: ${error.message}` + : // @ts-ignore + error?.code || 'No error message found.'; + + span.data[technology].error = combinedMessage.substring(0, 200); + } + } + + // TODO: Change substring usage to frame capturing - see util/stackTrace.js + if (error.stack) { + span.stack = String(error.stack).substring(500); + } +};