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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel-mongoose",
3
- "version": "10.1.0",
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.0.0"
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`).