@toa.io/storages.mongodb 1.0.0-alpha.243 → 1.0.0-alpha.246
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/readme.md +16 -0
- package/src/client.js +5 -1
- package/src/monitoring.js +87 -0
- package/test/monitoring.test.js +99 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/storages.mongodb",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.246",
|
|
4
4
|
"description": "Toa MongoDB Storage Connector",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@toa.io/conveyor": "1.0.0-alpha.225",
|
|
23
|
-
"@toa.io/core": "1.0.0-alpha.
|
|
23
|
+
"@toa.io/core": "1.0.0-alpha.246",
|
|
24
24
|
"@toa.io/generic": "1.0.0-alpha.225",
|
|
25
|
-
"@toa.io/pointer": "1.0.0-alpha.
|
|
25
|
+
"@toa.io/pointer": "1.0.0-alpha.246",
|
|
26
26
|
"mongodb": "7.2.0",
|
|
27
|
-
"openspan": "1.0.0-alpha.
|
|
27
|
+
"openspan": "1.0.0-alpha.246",
|
|
28
28
|
"saslprep": "1.0.3"
|
|
29
29
|
},
|
|
30
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "d6ee2a7246c64a44c50c28fadcfd1e27aaf137cb"
|
|
31
31
|
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# MongoDB Storage
|
|
2
|
+
|
|
3
|
+
## Tracing
|
|
4
|
+
|
|
5
|
+
Commands are recorded as `client` spans within the trace of the current invocation,
|
|
6
|
+
using the [driver command monitoring events](https://www.mongodb.com/docs/drivers/node/current/monitoring/command-monitoring/).
|
|
7
|
+
|
|
8
|
+
Spans are named `{command} {collection}` and carry `db.*` attributes following the
|
|
9
|
+
[OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/database/mongodb/):
|
|
10
|
+
`db.system`, `db.namespace`, `db.collection.name`, `db.operation.name`.
|
|
11
|
+
|
|
12
|
+
Commands executed outside of a sampled trace context (e.g. index management on startup)
|
|
13
|
+
and internal driver commands (`hello`, `ping`, authentication) are not recorded.
|
|
14
|
+
|
|
15
|
+
Monitoring is client-side only and does not affect the MongoDB server. Span recording
|
|
16
|
+
adds no waiting to the query path: exporting is buffered and happens in the background.
|
package/src/client.js
CHANGED
|
@@ -11,6 +11,7 @@ const { Connector } = require('@toa.io/core')
|
|
|
11
11
|
const { resolve } = require('@toa.io/pointer')
|
|
12
12
|
const { ID } = require('./deployment')
|
|
13
13
|
const { MongoClient } = require('mongodb')
|
|
14
|
+
const { monitor } = require('./monitoring')
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* @type {Record<string, Promise<Instance>>}
|
|
@@ -112,6 +113,8 @@ class Client extends Connector {
|
|
|
112
113
|
const client = new MongoClient(urls.join(','), OPTIONS)
|
|
113
114
|
const hosts = urls.map((str) => new URL(str).host)
|
|
114
115
|
|
|
116
|
+
monitor(client)
|
|
117
|
+
|
|
115
118
|
console.info('Connecting to MongoDB', { address: hosts.join(', ') })
|
|
116
119
|
|
|
117
120
|
await client.connect()
|
|
@@ -156,7 +159,8 @@ function getKey (db, urls) {
|
|
|
156
159
|
}
|
|
157
160
|
|
|
158
161
|
const OPTIONS = {
|
|
159
|
-
ignoreUndefined: true
|
|
162
|
+
ignoreUndefined: true,
|
|
163
|
+
monitorCommands: true
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
const ALREADY_EXISTS = 48
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { create, current, record } = require('openspan')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Records MongoDB commands as client spans using the driver command monitoring events.
|
|
7
|
+
* Requires the client to be created with `monitorCommands: true`.
|
|
8
|
+
*
|
|
9
|
+
* Commands executed outside of a sampled trace context are not recorded.
|
|
10
|
+
*
|
|
11
|
+
* @param {import('mongodb').MongoClient} client
|
|
12
|
+
*/
|
|
13
|
+
function monitor (client) {
|
|
14
|
+
/** @type {Map<number, { context: object, time: number, attributes: object }>} */
|
|
15
|
+
const pending = new Map()
|
|
16
|
+
|
|
17
|
+
client.on('commandStarted', (event) => {
|
|
18
|
+
if (IGNORE.has(event.commandName))
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
const parent = current()
|
|
22
|
+
|
|
23
|
+
if (parent === undefined || !parent.sampled)
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
if (pending.size >= CAPACITY) // should never happen
|
|
27
|
+
pending.delete(pending.keys().next().value)
|
|
28
|
+
|
|
29
|
+
// https://opentelemetry.io/docs/specs/semconv/database/mongodb/
|
|
30
|
+
const attributes = {
|
|
31
|
+
'db.system': 'mongodb',
|
|
32
|
+
'db.namespace': event.databaseName,
|
|
33
|
+
'db.operation.name': event.commandName
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const collection = event.command?.[event.commandName]
|
|
37
|
+
|
|
38
|
+
if (typeof collection === 'string')
|
|
39
|
+
attributes['db.collection.name'] = collection
|
|
40
|
+
|
|
41
|
+
pending.set(event.requestId, {
|
|
42
|
+
context: create(parent),
|
|
43
|
+
time: Date.now(),
|
|
44
|
+
attributes
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
client.on('commandSucceeded', (event) => complete(event))
|
|
49
|
+
client.on('commandFailed', (event) => complete(event, 'error'))
|
|
50
|
+
|
|
51
|
+
function complete (event, status) {
|
|
52
|
+
const started = pending.get(event.requestId)
|
|
53
|
+
|
|
54
|
+
if (started === undefined)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
pending.delete(event.requestId)
|
|
58
|
+
|
|
59
|
+
const collection = started.attributes['db.collection.name']
|
|
60
|
+
|
|
61
|
+
const span = {
|
|
62
|
+
name: collection === undefined
|
|
63
|
+
? event.commandName
|
|
64
|
+
: `${event.commandName} ${collection}`,
|
|
65
|
+
traceId: started.context.traceId,
|
|
66
|
+
spanId: started.context.spanId,
|
|
67
|
+
parentId: started.context.parentId,
|
|
68
|
+
kind: 'client',
|
|
69
|
+
time: started.time,
|
|
70
|
+
duration: event.duration,
|
|
71
|
+
attributes: started.attributes,
|
|
72
|
+
service: started.context.service
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (status !== undefined)
|
|
76
|
+
span.status = status
|
|
77
|
+
|
|
78
|
+
record(span)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// internal commands, including authentication and connection management
|
|
83
|
+
const IGNORE = new Set(['hello', 'ismaster', 'ping', 'endSessions', 'saslStart', 'saslContinue'])
|
|
84
|
+
|
|
85
|
+
const CAPACITY = 1024
|
|
86
|
+
|
|
87
|
+
exports.monitor = monitor
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('node:events')
|
|
4
|
+
const { create, exporting, run, traces } = require('openspan')
|
|
5
|
+
const { monitor } = require('../src/monitoring')
|
|
6
|
+
|
|
7
|
+
let client
|
|
8
|
+
let spans
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
client = new EventEmitter()
|
|
12
|
+
spans = []
|
|
13
|
+
|
|
14
|
+
exporting([{ export: (span) => spans.push(span) }])
|
|
15
|
+
monitor(client)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
traces()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const started = {
|
|
23
|
+
requestId: 1,
|
|
24
|
+
commandName: 'find',
|
|
25
|
+
databaseName: 'toa-dev',
|
|
26
|
+
command: { find: 'pots', filter: {} }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const succeeded = { requestId: 1, commandName: 'find', duration: 1.5 }
|
|
30
|
+
const failed = { requestId: 1, commandName: 'find', duration: 0.5, failure: 'boom' }
|
|
31
|
+
|
|
32
|
+
it('should record a command as a client span', () => {
|
|
33
|
+
const context = create()
|
|
34
|
+
|
|
35
|
+
run(context, () => client.emit('commandStarted', started))
|
|
36
|
+
client.emit('commandSucceeded', succeeded)
|
|
37
|
+
|
|
38
|
+
expect(spans).toHaveLength(1)
|
|
39
|
+
|
|
40
|
+
expect(spans[0]).toMatchObject({
|
|
41
|
+
name: 'find pots',
|
|
42
|
+
traceId: context.traceId,
|
|
43
|
+
parentId: context.spanId,
|
|
44
|
+
kind: 'client',
|
|
45
|
+
duration: 1.5,
|
|
46
|
+
attributes: {
|
|
47
|
+
'db.system': 'mongodb',
|
|
48
|
+
'db.namespace': 'toa-dev',
|
|
49
|
+
'db.operation.name': 'find',
|
|
50
|
+
'db.collection.name': 'pots'
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(spans[0].status).toBeUndefined()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should record failed commands with error status', () => {
|
|
58
|
+
run(create(), () => client.emit('commandStarted', started))
|
|
59
|
+
client.emit('commandFailed', failed)
|
|
60
|
+
|
|
61
|
+
expect(spans).toHaveLength(1)
|
|
62
|
+
expect(spans[0].status).toBe('error')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should not record commands outside of a trace context', () => {
|
|
66
|
+
client.emit('commandStarted', started)
|
|
67
|
+
client.emit('commandSucceeded', succeeded)
|
|
68
|
+
|
|
69
|
+
expect(spans).toHaveLength(0)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should not record commands of unsampled traces', () => {
|
|
73
|
+
const context = { ...create(), sampled: false }
|
|
74
|
+
|
|
75
|
+
run(context, () => client.emit('commandStarted', started))
|
|
76
|
+
client.emit('commandSucceeded', succeeded)
|
|
77
|
+
|
|
78
|
+
expect(spans).toHaveLength(0)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should ignore internal commands', () => {
|
|
82
|
+
run(create(), () => {
|
|
83
|
+
client.emit('commandStarted', { ...started, commandName: 'hello', command: { hello: 1 } })
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
client.emit('commandSucceeded', { ...succeeded, commandName: 'hello' })
|
|
87
|
+
|
|
88
|
+
expect(spans).toHaveLength(0)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should name spans without a collection by the command', () => {
|
|
92
|
+
const start = { ...started, commandName: 'listIndexes', command: { listIndexes: 1 } }
|
|
93
|
+
|
|
94
|
+
run(create(), () => client.emit('commandStarted', start))
|
|
95
|
+
client.emit('commandSucceeded', { ...succeeded, commandName: 'listIndexes' })
|
|
96
|
+
|
|
97
|
+
expect(spans).toHaveLength(1)
|
|
98
|
+
expect(spans[0].name).toBe('listIndexes')
|
|
99
|
+
})
|