autotel-mongoose 10.1.0 → 11.0.0
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 +4 -3
- package/skills/autotel-mongoose/SKILL.md +256 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-mongoose",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"description": "OpenTelemetry instrumentation for Mongoose with db.query.text capture and automatic redaction",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
18
|
"src",
|
|
19
|
-
"README.md"
|
|
19
|
+
"README.md",
|
|
20
|
+
"skills"
|
|
20
21
|
],
|
|
21
22
|
"keywords": [
|
|
22
23
|
"opentelemetry",
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"tsdown": "^0.22.2",
|
|
46
47
|
"typescript": "^6.0.3",
|
|
47
48
|
"vitest": "^4.1.8",
|
|
48
|
-
"autotel": "4.
|
|
49
|
+
"autotel": "4.1.0"
|
|
49
50
|
},
|
|
50
51
|
"repository": {
|
|
51
52
|
"type": "git",
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: autotel-mongoose
|
|
3
|
+
description: >
|
|
4
|
+
Use this skill when adding OpenTelemetry tracing to a Mongoose 8+ application — covers instrumentMongoose(), query text capture, automatic PII redaction, and Schema hook instrumentation.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# autotel-mongoose
|
|
8
|
+
|
|
9
|
+
OpenTelemetry instrumentation for Mongoose 8+ with automatic `db.query.text` capture and built-in PII redaction. This package exists because the official `@opentelemetry/instrumentation-mongodb` has broken ESM+tsx support; use this instead when running Mongoose in an ESM environment.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install autotel-mongoose
|
|
15
|
+
# mongoose and autotel are peer dependencies
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// src/instrumentation.mts
|
|
20
|
+
import 'autotel/register';
|
|
21
|
+
import { init } from 'autotel';
|
|
22
|
+
import mongoose from 'mongoose';
|
|
23
|
+
import { instrumentMongoose } from 'autotel-mongoose';
|
|
24
|
+
|
|
25
|
+
init({ service: 'my-app' });
|
|
26
|
+
|
|
27
|
+
// IMPORTANT: call BEFORE defining schemas or models
|
|
28
|
+
instrumentMongoose(mongoose, {
|
|
29
|
+
dbName: 'myapp',
|
|
30
|
+
peerName: 'mongo.example.com',
|
|
31
|
+
peerPort: 27017,
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node --import ./src/instrumentation.mts dist/index.js
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Core Patterns
|
|
40
|
+
|
|
41
|
+
### Basic instrumentation
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import mongoose from 'mongoose';
|
|
45
|
+
import { instrumentMongoose } from 'autotel-mongoose';
|
|
46
|
+
|
|
47
|
+
// Minimal — defaults apply
|
|
48
|
+
instrumentMongoose(mongoose);
|
|
49
|
+
|
|
50
|
+
// With connection metadata
|
|
51
|
+
instrumentMongoose(mongoose, {
|
|
52
|
+
dbName: 'myapp',
|
|
53
|
+
peerName: 'localhost',
|
|
54
|
+
peerPort: 27017,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Configuration reference (`InstrumentMongooseConfig`)
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
interface InstrumentMongooseConfig {
|
|
62
|
+
dbName?: string; // Sets db.namespace on spans
|
|
63
|
+
peerName?: string; // Sets server.address on spans
|
|
64
|
+
peerPort?: number; // Sets server.port (default: 27017)
|
|
65
|
+
tracerName?: string; // Default: 'autotel-mongoose'
|
|
66
|
+
captureCollectionName?: boolean; // Include db.collection.name (default: true)
|
|
67
|
+
instrumentHooks?: boolean; // Instrument Schema pre/post hooks (default: false)
|
|
68
|
+
|
|
69
|
+
// Controls db.query.text serialization
|
|
70
|
+
dbStatementSerializer?:
|
|
71
|
+
| ((operation: string, payload: SerializerPayload) => string | undefined)
|
|
72
|
+
| false; // false = disable statement capture entirely
|
|
73
|
+
|
|
74
|
+
// Controls redaction applied to serialized db.query.text
|
|
75
|
+
statementRedactor?:
|
|
76
|
+
| AttributeRedactorPreset // 'default' | other preset names
|
|
77
|
+
| AttributeRedactorConfig // custom redactor config
|
|
78
|
+
| false; // false = no redaction
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Span attributes
|
|
83
|
+
|
|
84
|
+
| Attribute | Condition |
|
|
85
|
+
| -------------------- | --------------------------------------------------------------- |
|
|
86
|
+
| `db.system.name` | Always (`mongodb`) |
|
|
87
|
+
| `db.operation.name` | Always (e.g., `find`, `insertMany`) |
|
|
88
|
+
| `db.collection.name` | When `captureCollectionName: true` (default) |
|
|
89
|
+
| `db.namespace` | When `dbName` is set |
|
|
90
|
+
| `db.query.text` | When statement serialization is enabled (default: JSON payload) |
|
|
91
|
+
| `server.address` | When `peerName` is set |
|
|
92
|
+
| `server.port` | When `peerPort` is set |
|
|
93
|
+
|
|
94
|
+
Span names follow `<operation> <collectionName>` (e.g., `find users`) or fall back to `mongoose.<operation>`.
|
|
95
|
+
|
|
96
|
+
### What is instrumented
|
|
97
|
+
|
|
98
|
+
**Query-returning Model methods** (spans created; `exec()` wrapped for finalization):
|
|
99
|
+
`find`, `findOne`, `findById`, `findOneAndUpdate`, `findOneAndDelete`, `findOneAndReplace`, `deleteOne`, `deleteMany`, `updateOne`, `updateMany`, `countDocuments`, `estimatedDocumentCount`
|
|
100
|
+
|
|
101
|
+
**Model static methods** (spans created; promise finalization):
|
|
102
|
+
`create`, `insertMany`, `aggregate`, `bulkWrite`
|
|
103
|
+
|
|
104
|
+
**Model instance methods** (spans created; promise finalization):
|
|
105
|
+
`save`, `deleteOne` (on prototype)
|
|
106
|
+
|
|
107
|
+
**Chainable Query methods** (context propagation only, no span):
|
|
108
|
+
`populate`, `select`, `lean`, `where`, `sort`, `limit`, `skip`
|
|
109
|
+
|
|
110
|
+
**Schema hooks** (opt-in via `instrumentHooks: true`):
|
|
111
|
+
User-defined `pre` and `post` hooks are wrapped. Internal Mongoose hooks are skipped automatically.
|
|
112
|
+
|
|
113
|
+
### Statement capture and redaction
|
|
114
|
+
|
|
115
|
+
By default, query payloads are serialized to JSON and set as `db.query.text`, with the `'default'` redactor applied (strips emails, phone numbers, SSNs, credit cards).
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Custom serializer — return undefined to suppress db.query.text for this operation
|
|
119
|
+
instrumentMongoose(mongoose, {
|
|
120
|
+
dbStatementSerializer: (operation, payload) => {
|
|
121
|
+
if (operation === 'find') {
|
|
122
|
+
return JSON.stringify({ filter: payload.condition });
|
|
123
|
+
}
|
|
124
|
+
return undefined; // suppress for other operations
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Disable statement capture entirely
|
|
129
|
+
instrumentMongoose(mongoose, {
|
|
130
|
+
dbStatementSerializer: false,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Keep capture but disable redaction (dangerous — only for trusted environments)
|
|
134
|
+
instrumentMongoose(mongoose, {
|
|
135
|
+
statementRedactor: false,
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### SerializerPayload shape
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
interface SerializerPayload {
|
|
143
|
+
condition?: Record<string, unknown>; // Query filter (find*, delete*, update*, count*)
|
|
144
|
+
updates?: Record<string, unknown>; // Update document (findOneAndUpdate, updateOne, etc.)
|
|
145
|
+
options?: Record<string, unknown>; // Query options
|
|
146
|
+
fields?: Record<string, unknown>; // Projection fields
|
|
147
|
+
aggregatePipeline?: unknown[]; // aggregate()
|
|
148
|
+
document?: unknown; // create(), save() — single document
|
|
149
|
+
documents?: unknown[]; // insertMany()
|
|
150
|
+
operations?: unknown[]; // bulkWrite()
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Enabling Schema hook instrumentation
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
instrumentMongoose(mongoose, {
|
|
158
|
+
instrumentHooks: true, // default: false
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Hooks defined AFTER instrumentation are automatically wrapped
|
|
162
|
+
const userSchema = new mongoose.Schema({ name: String });
|
|
163
|
+
|
|
164
|
+
userSchema.pre('save', async function () {
|
|
165
|
+
// This hook gets its own span: mongoose.users.pre.save
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Hook spans use `SpanKind.INTERNAL` and carry `hook.type`, `hook.operation`, `hook.model`, `db.system.name`, and optionally `db.collection.name`.
|
|
170
|
+
|
|
171
|
+
Internal Mongoose hooks (those starting with `_` or `$`, or containing known internal patterns like `this.$__`) are automatically skipped.
|
|
172
|
+
|
|
173
|
+
### Idempotency
|
|
174
|
+
|
|
175
|
+
`instrumentMongoose` is idempotent. Multiple calls on the same `mongoose` instance are safe; the `__autotelMongooseInstrumented` flag prevents double-patching.
|
|
176
|
+
|
|
177
|
+
## Common Mistakes
|
|
178
|
+
|
|
179
|
+
### HIGH — Calling `instrumentMongoose` after defining schemas
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Wrong: hooks on existing schemas are not retroactively wrapped
|
|
183
|
+
const User = mongoose.model('User', new mongoose.Schema({ name: String }));
|
|
184
|
+
instrumentMongoose(mongoose);
|
|
185
|
+
|
|
186
|
+
// Correct: instrument BEFORE defining any schema or model
|
|
187
|
+
instrumentMongoose(mongoose, { dbName: 'myapp' });
|
|
188
|
+
const User = mongoose.model('User', new mongoose.Schema({ name: String }));
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Hook wrapping (`instrumentHooks: true`) only applies to `pre`/`post` calls made after `instrumentMongoose` runs. Model method patching affects `mongoose.Model` globally and works regardless, but hook instrumentation is order-sensitive.
|
|
192
|
+
|
|
193
|
+
### HIGH — Disabling redaction when query text contains PII
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Dangerous: raw query payloads may include emails, IDs, or sensitive fields
|
|
197
|
+
instrumentMongoose(mongoose, {
|
|
198
|
+
statementRedactor: false,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Safer: use a custom serializer that only captures safe fields
|
|
202
|
+
instrumentMongoose(mongoose, {
|
|
203
|
+
dbStatementSerializer: (operation, payload) => {
|
|
204
|
+
// Only capture the operation name, not the full payload
|
|
205
|
+
return JSON.stringify({ op: operation });
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The default `'default'` redactor covers common PII patterns (email, phone, SSN, credit card) but is not exhaustive. Review your query payloads before disabling redaction.
|
|
211
|
+
|
|
212
|
+
### MEDIUM — Using `instrumentHooks: false` (default) and expecting hook spans
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Wrong assumption: hooks are NOT traced by default
|
|
216
|
+
instrumentMongoose(mongoose);
|
|
217
|
+
userSchema.pre('save', async function () {
|
|
218
|
+
// No span created for this hook
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Correct: opt in explicitly
|
|
222
|
+
instrumentMongoose(mongoose, { instrumentHooks: true });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Hook instrumentation is off by default to avoid unexpected performance overhead and to avoid wrapping Mongoose internals by accident.
|
|
226
|
+
|
|
227
|
+
### MEDIUM — Using deprecated semconv attribute names
|
|
228
|
+
|
|
229
|
+
autotel-mongoose uses stable OTel semconv:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// Deprecated (do not use in custom code alongside this package)
|
|
233
|
+
'db.statement'; // use db.query.text
|
|
234
|
+
'db.system'; // use db.system.name
|
|
235
|
+
'net.peer.name'; // use server.address
|
|
236
|
+
'net.peer.port'; // use server.port
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The package exports stable constants from `autotel-mongoose/constants` if needed for custom attribute access.
|
|
240
|
+
|
|
241
|
+
### MEDIUM — Expecting spans for `.find()` without calling `.exec()`
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Wrong: span is created but never finalized — it stays open
|
|
245
|
+
const query = User.find({ active: true });
|
|
246
|
+
const users = await query; // Mongoose auto-calls exec(), but the span wrapper may miss it
|
|
247
|
+
|
|
248
|
+
// Correct: always call .exec() explicitly when you need a guaranteed finalized span
|
|
249
|
+
const users = await User.find({ active: true }).exec();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The instrumentation wraps `exec()` to finalize the span. Implicit execution (Mongoose auto-calling `exec()` when you `await` a Query) should also work, but explicit `.exec()` is more reliable and clearer.
|
|
253
|
+
|
|
254
|
+
## Version
|
|
255
|
+
|
|
256
|
+
Targets autotel-mongoose v0.0.2 with mongoose >= 8.0.0 (peer dep) and autotel (peer dep). Uses stable OTel semconv only (`db.query.text`, `db.operation.name`, `db.system.name`, `db.collection.name`, `db.namespace`, `server.address`, `server.port`).
|