-
Notifications
You must be signed in to change notification settings - Fork 225
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: set outcome for mongodb spans (#3695)
- Loading branch information
1 parent
a61142f
commit bb65827
Showing
4 changed files
with
103 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
|
||
const semver = require('semver'); | ||
|
||
const { OUTCOME_SUCCESS, OUTCOME_FAILURE } = require('../../constants'); | ||
const { getDBDestination } = require('../context'); | ||
const shimmer = require('../shimmer'); | ||
const kListenersAdded = Symbol('kListenersAdded'); | ||
|
@@ -32,8 +33,8 @@ module.exports = (mongodb, agent, { version, enabled }) => { | |
if (mongodb.instrument) { | ||
const listener = mongodb.instrument(); | ||
listener.on('started', onStart); | ||
listener.on('succeeded', onEnd); | ||
listener.on('failed', onEnd); | ||
listener.on('succeeded', onSuccess); | ||
listener.on('failed', onFailure); | ||
} else if (mongodb.MongoClient) { | ||
// mongodb 4.0+ removed the instrument() method in favor of | ||
// listeners on the instantiated client objects. There are two mechanisms | ||
|
@@ -51,8 +52,8 @@ module.exports = (mongodb, agent, { version, enabled }) => { | |
} | ||
super(...args); | ||
this.on('commandStarted', onStart); | ||
this.on('commandSucceeded', onEnd); | ||
this.on('commandFailed', onEnd); | ||
this.on('commandSucceeded', onSuccess); | ||
this.on('commandFailed', onFailure); | ||
this[kListenersAdded] = true; | ||
} | ||
} | ||
|
@@ -102,8 +103,8 @@ module.exports = (mongodb, agent, { version, enabled }) => { | |
} else { | ||
if (!client[kListenersAdded]) { | ||
client.on('commandStarted', onStart); | ||
client.on('commandSucceeded', onEnd); | ||
client.on('commandFailed', onEnd); | ||
client.on('commandSucceeded', onSuccess); | ||
client.on('commandFailed', onFailure); | ||
client[kListenersAdded] = true; | ||
} | ||
callback(err, client); | ||
|
@@ -115,8 +116,8 @@ module.exports = (mongodb, agent, { version, enabled }) => { | |
p.then((client) => { | ||
if (!client[kListenersAdded]) { | ||
client.on('commandStarted', onStart); | ||
client.on('commandSucceeded', onEnd); | ||
client.on('commandFailed', onEnd); | ||
client.on('commandSucceeded', onSuccess); | ||
client.on('commandFailed', onFailure); | ||
client[kListenersAdded] = true; | ||
} | ||
}); | ||
|
@@ -127,17 +128,15 @@ module.exports = (mongodb, agent, { version, enabled }) => { | |
|
||
function onStart(event) { | ||
// `event` is a `CommandStartedEvent` | ||
// https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.rst#api | ||
// E.g. with mongodb@3.6.3: | ||
// https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.rst#command-started-message | ||
// E.g. with mongodb@6.2.0: | ||
// CommandStartedEvent { | ||
// address: '127.0.0.1:27017', | ||
// connectionId: 1, | ||
// connectionId: '127.0.0.1:27017', | ||
// requestId: 1, | ||
// databaseName: 'test', | ||
// commandName: 'insert', | ||
// command: | ||
// { ... } } | ||
|
||
const name = [ | ||
event.databaseName, | ||
collectionFor(event), | ||
|
@@ -174,10 +173,51 @@ module.exports = (mongodb, agent, { version, enabled }) => { | |
} | ||
} | ||
|
||
function onEnd(event) { | ||
function onSuccess(event) { | ||
// `event` is a `CommandSucceededEvent` | ||
// https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.rst#command-succeeded-message | ||
// E.g. with [email protected]: | ||
// CommandSucceededEvent { | ||
// connectionId: '127.0.0.1:27017', | ||
// requestId: 1, | ||
// duration: 1, | ||
// reply: { ... } | ||
// } | ||
if (!activeSpans.has(event.requestId)) return; | ||
|
||
const span = activeSpans.get(event.requestId); | ||
activeSpans.delete(event.requestId); | ||
|
||
// From [email protected] and up some commands like `deleteOne` may contain | ||
// error data inside the `reply` property. It makes sense to capture it. | ||
const writeErrors = event?.reply?.writeErrors; | ||
|
||
if (writeErrors && writeErrors.length) { | ||
agent.captureError(writeErrors[0].errmsg, { | ||
skipOutcome: true, | ||
parent: span, | ||
}); | ||
} | ||
span.setOutcome(OUTCOME_SUCCESS); | ||
span.end(span._timer.start / 1000 + event.duration); | ||
} | ||
|
||
function onFailure(event) { | ||
// `event` is a `CommandFailedEvent` | ||
// https://github.com/mongodb/specifications/blob/master/source/command-logging-and-monitoring/command-logging-and-monitoring.rst#command-failed-message | ||
// E.g. with [email protected]: | ||
// CommandFailedEvent { | ||
// connectionId: '127.0.0.1:27017', | ||
// requestId: 6, | ||
// commandName: "find", | ||
// duration: 1, | ||
// "failure": { ... } | ||
// } | ||
if (!activeSpans.has(event.requestId)) return; | ||
|
||
const span = activeSpans.get(event.requestId); | ||
activeSpans.delete(event.requestId); | ||
span.setOutcome(OUTCOME_FAILURE); | ||
span.end(span._timer.start / 1000 + event.duration); | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,12 @@ const TEST_DB = 'elasticapm'; | |
const TEST_COLLECTION = 'test'; | ||
const TEST_USE_CALLBACKS = semver.satisfies(MONGODB_VERSION, '<5'); | ||
|
||
// From [email protected] and up CommandSuccessEvents may contain errors | ||
const MONGODB_SUCCESS_WITH_ERRORS = semver.satisfies( | ||
MONGODB_VERSION, | ||
'>=3.6.0', | ||
); | ||
|
||
/** @type {import('../../../_utils').TestFixture[]} */ | ||
const testFixtures = [ | ||
{ | ||
|
@@ -66,6 +72,7 @@ const testFixtures = [ | |
// First the transaction. | ||
t.ok(events[0].transaction, 'got the transaction'); | ||
const tx = events.shift().transaction; | ||
const errors = events.filter((e) => e.error).map((e) => e.error); | ||
|
||
// Compare some common fields across all spans. | ||
// ignore http/external spans | ||
|
@@ -97,6 +104,7 @@ const testFixtures = [ | |
'all spans have sample_rate=1', | ||
); | ||
|
||
const failingSpanId = spans[TEST_USE_CALLBACKS ? 11 : 8].id; // index of `.deleteOne` with bogus "hint" | ||
spans.forEach((s) => { | ||
// Remove variable and common fields to facilitate t.deepEqual below. | ||
delete s.id; | ||
|
@@ -194,6 +202,12 @@ const testFixtures = [ | |
t.deepEqual(spans.shift(), findOneSpan, 'findOne 2nd concurrent call'); | ||
t.deepEqual(spans.shift(), findOneSpan, 'findOne 3rd concurrent call'); | ||
|
||
t.deepEqual( | ||
spans.shift(), | ||
{ ...findOneSpan, outcome: 'failure' }, | ||
'findOne with bogus "hint" produced expected span', | ||
); | ||
|
||
if (TEST_USE_CALLBACKS) { | ||
t.deepEqual( | ||
spans.shift(), | ||
|
@@ -278,6 +292,25 @@ const testFixtures = [ | |
'deleteOne produced expected span', | ||
); | ||
|
||
// Delete command errors are not faling | ||
// - Promise API does not reject | ||
// - callback API does not return an error param | ||
// - CommandSucceededvent is fired (althoug it contains error data) | ||
t.deepEqual( | ||
spans.shift(), | ||
deleteOneSpan, | ||
'deleteOne with bogus "hint" produced expected span', | ||
); | ||
|
||
if (MONGODB_SUCCESS_WITH_ERRORS) { | ||
t.equal(errors.length, 1, 'got 1 error'); | ||
t.equal( | ||
errors[0].parent_id, | ||
failingSpanId, | ||
'error is a child of the failing span from deleteOne with bogus "hint"', | ||
); | ||
} | ||
|
||
if (TEST_USE_CALLBACKS) { | ||
t.deepEqual( | ||
spans.shift(), | ||
|