autotel-plugins 0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Jag Reehal 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,416 @@
1
+ # Autotel Plugins
2
+
3
+ OpenTelemetry instrumentation for libraries **without official support** OR where the official support is fundamentally broken.
4
+
5
+ ## Philosophy
6
+
7
+ **autotel-plugins only includes instrumentation that:**
8
+
9
+ 1. **Has NO official OpenTelemetry package** (e.g., Drizzle ORM)
10
+ 2. **Has BROKEN official instrumentation** (e.g., Mongoose in ESM+tsx)
11
+ 3. **Adds significant value** beyond official packages
12
+
13
+ **We do NOT include:**
14
+
15
+ - Re-exports of official packages
16
+ - Wrappers that add no value
17
+ - Duplicates of working official packages
18
+
19
+ ## Why This Approach?
20
+
21
+ With the `--import` pattern (Node.js 18.19+), using official OpenTelemetry packages **when they work** is simple:
22
+
23
+ ```javascript
24
+ // instrumentation.mjs
25
+ import { init } from 'autotel';
26
+ import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
27
+
28
+ init({
29
+ service: 'my-app',
30
+ instrumentations: [new PgInstrumentation()],
31
+ });
32
+ ```
33
+
34
+ ```bash
35
+ # Run with --import flag
36
+ tsx --import ./instrumentation.mjs src/index.ts
37
+ ```
38
+
39
+ **Benefits of official packages (when they work):**
40
+
41
+ - ✅ Always up-to-date (maintained by OpenTelemetry)
42
+ - ✅ Complete feature coverage
43
+ - ✅ Battle-tested in production
44
+ - ✅ Zero maintenance burden
45
+ - ✅ More discoverable and trustworthy
46
+
47
+ ## When to Use Official Packages
48
+
49
+ For databases/ORMs with **working** official instrumentation, **use those directly**:
50
+
51
+ - **MongoDB** → [`@opentelemetry/instrumentation-mongodb`](https://www.npmjs.com/package/@opentelemetry/instrumentation-mongodb)
52
+ - **PostgreSQL** → [`@opentelemetry/instrumentation-pg`](https://www.npmjs.com/package/@opentelemetry/instrumentation-pg)
53
+ - **MySQL** → [`@opentelemetry/instrumentation-mysql2`](https://www.npmjs.com/package/@opentelemetry/instrumentation-mysql2)
54
+ - **Redis** → [`@opentelemetry/instrumentation-redis`](https://www.npmjs.com/package/@opentelemetry/instrumentation-redis)
55
+ - **Express** → [`@opentelemetry/instrumentation-express`](https://www.npmjs.com/package/@opentelemetry/instrumentation-express)
56
+ - **Fastify** → [`@opentelemetry/instrumentation-fastify`](https://www.npmjs.com/package/@opentelemetry/instrumentation-fastify)
57
+
58
+ [Browse all official instrumentations →](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node)
59
+
60
+ ### ⚠️ Mongoose ESM Exception
61
+
62
+ **Note:** [`@opentelemetry/instrumentation-mongoose`](https://www.npmjs.com/package/@opentelemetry/instrumentation-mongoose) is fundamentally broken in ESM+tsx environments due to module loading hook issues. It works in CommonJS, but if you're using ESM with tsx/ts-node, use our custom plugin below.
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ npm install autotel-plugins
68
+ ```
69
+
70
+ ## Currently Supported
71
+
72
+ ### Mongoose
73
+
74
+ Instrument Mongoose database operations with OpenTelemetry tracing using runtime patching. Works in ESM+tsx unlike the official package. **✨ NEW: Automatic hook instrumentation - no manual trace() calls needed!**
75
+
76
+ **Why we provide this:**
77
+
78
+ The official [`@opentelemetry/instrumentation-mongoose`](https://www.npmjs.com/package/@opentelemetry/instrumentation-mongoose) package is fundamentally broken in ESM+tsx environments:
79
+
80
+ - Uses module loading hooks (`import-in-the-middle`) that fail with ESM import hoisting
81
+ - Mongoose package lacks proper dual-mode exports (CJS only)
82
+ - Works in CommonJS, but fails in modern ESM projects
83
+ - No timeline for ESM support
84
+
85
+ Our implementation uses **runtime patching** instead of module loading hooks, so it works everywhere.
86
+
87
+ #### Basic Usage (with Automatic Hook Tracing)
88
+
89
+ ```typescript
90
+ import mongoose from 'mongoose';
91
+ import { init } from 'autotel';
92
+ import { instrumentMongoose } from 'autotel-plugins/mongoose';
93
+
94
+ // Initialize Autotel
95
+ init({ service: 'my-app' });
96
+
97
+ // IMPORTANT: Instrument BEFORE defining schemas to enable automatic hook tracing
98
+ instrumentMongoose(mongoose, {
99
+ dbName: 'myapp',
100
+ peerName: 'localhost',
101
+ peerPort: 27017,
102
+ });
103
+
104
+ // NOW define schemas - hooks are automatically traced!
105
+ const userSchema = new mongoose.Schema({ name: String, email: String });
106
+
107
+ userSchema.pre('save', async function () {
108
+ // ✨ This hook is AUTOMATICALLY traced - no manual trace() needed!
109
+ this.email = this.email.toLowerCase();
110
+ });
111
+
112
+ const User = mongoose.model('User', userSchema);
113
+
114
+ // Connect to MongoDB
115
+ await mongoose.connect('mongodb://localhost:27017/myapp');
116
+
117
+ // All operations AND hooks are automatically traced
118
+ await User.create({ name: 'Alice', email: 'ALICE@EXAMPLE.COM' });
119
+ // Creates spans: mongoose.users.create + mongoose.users.pre.save
120
+ ```
121
+
122
+ #### What Gets Automatically Traced
123
+
124
+ **1. Model Operations** (all automatic):
125
+
126
+ - `create`, `insertMany`, `find`, `findOne`, `findById`
127
+ - `findOneAndUpdate`, `findByIdAndUpdate`, `updateOne`, `updateMany`
128
+ - `deleteOne`, `deleteMany`, `countDocuments`, `aggregate`
129
+ - Instance methods: `save`, `remove`, `deleteOne`
130
+
131
+ **2. Schema Hooks** (automatic - no manual code needed!):
132
+
133
+ - Pre hooks: `pre('save')`, `pre('findOneAndUpdate')`, etc.
134
+ - Post hooks: `post('save')`, `post('remove')`, etc.
135
+ - Built-in hooks: `post('init')` (document hydration)
136
+
137
+ #### Hook Instrumentation Setup
138
+
139
+ For automatic hook tracing, call `instrumentMongoose()` **before** defining schemas. ESM import hoisting means you need a separate init file:
140
+
141
+ **Pattern for ESM+tsx projects:**
142
+
143
+ Create `init-mongoose.ts`:
144
+
145
+ ```typescript
146
+ import mongoose from 'mongoose';
147
+ import { instrumentMongoose } from 'autotel-plugins/mongoose';
148
+
149
+ instrumentMongoose(mongoose, { dbName: 'myapp' });
150
+ ```
151
+
152
+ Import before schemas in `index.ts`:
153
+
154
+ ```typescript
155
+ import './init-mongoose'; // Import first!
156
+ import { User, Post } from './schema'; // Hooks auto-instrumented
157
+ ```
158
+
159
+ #### Configuration
160
+
161
+ ```typescript
162
+ {
163
+ dbName?: string // Database name
164
+ captureCollectionName?: boolean // Include collection in spans (default: true)
165
+ peerName?: string // MongoDB host
166
+ peerPort?: number // MongoDB port (default: 27017)
167
+ tracerName?: string // Custom tracer name
168
+ }
169
+ ```
170
+
171
+ #### Span Attributes
172
+
173
+ **Operation Spans (SpanKind.CLIENT):**
174
+
175
+ - `db.system` - "mongoose"
176
+ - `db.operation` - create, find, update, etc.
177
+ - `db.mongodb.collection` - Collection name
178
+ - `db.name` - Database name
179
+ - `net.peer.name` / `net.peer.port` - MongoDB server
180
+
181
+ **Hook Spans (SpanKind.INTERNAL):**
182
+
183
+ - `hook.type` - "pre" or "post"
184
+ - `hook.operation` - save, findOneAndUpdate, etc.
185
+ - `hook.model` - Model name (User, Post, etc.)
186
+ - `db.mongodb.collection` - Collection name
187
+ - `db.system` - "mongoose"
188
+ - `db.name` - Database name
189
+
190
+ #### Before vs After (70% Less Code!)
191
+
192
+ **Before (Manual instrumentation):**
193
+
194
+ ```typescript
195
+ import { trace } from 'autotel';
196
+
197
+ userSchema.pre('save', async function () {
198
+ await trace((ctx) => async () => {
199
+ ctx.setAttribute('hook.type', 'pre');
200
+ ctx.setAttribute('hook.operation', 'save');
201
+ // ... lots of boilerplate
202
+ this.email = this.email.toLowerCase();
203
+ })();
204
+ });
205
+ ```
206
+
207
+ **After (Automatic instrumentation):**
208
+
209
+ ```typescript
210
+ // NO trace() imports needed!
211
+ userSchema.pre('save', async function () {
212
+ // Automatically traced with all attributes!
213
+ this.email = this.email.toLowerCase();
214
+ });
215
+ ```
216
+
217
+ ### Drizzle ORM
218
+
219
+ Instrument Drizzle database operations with OpenTelemetry tracing. Drizzle doesn't have official instrumentation, so we provide it here.
220
+
221
+ ```typescript
222
+ import { drizzle } from 'drizzle-orm/postgres-js';
223
+ import postgres from 'postgres';
224
+ import { instrumentDrizzleClient } from 'autotel-plugins/drizzle';
225
+
226
+ const queryClient = postgres(process.env.DATABASE_URL!);
227
+ const db = drizzle({ client: queryClient });
228
+
229
+ // Instrument the database instance
230
+ instrumentDrizzleClient(db, {
231
+ dbSystem: 'postgresql',
232
+ dbName: 'myapp',
233
+ peerName: 'db.example.com',
234
+ peerPort: 5432,
235
+ captureQueryText: true,
236
+ });
237
+
238
+ // All queries are now traced
239
+ await db.select().from(users).where(eq(users.id, 123));
240
+ ```
241
+
242
+ **Supported databases:**
243
+
244
+ - PostgreSQL (node-postgres, postgres.js)
245
+ - MySQL (mysql2)
246
+ - SQLite (better-sqlite3, LibSQL/Turso)
247
+
248
+ **Functions:**
249
+
250
+ - `instrumentDrizzle(client, config)` - Instrument a database client/pool
251
+ - `instrumentDrizzleClient(db, config)` - Instrument a Drizzle database instance
252
+
253
+ **Configuration:**
254
+
255
+ ```typescript
256
+ {
257
+ dbSystem?: string // Database type (postgresql, mysql, sqlite)
258
+ dbName?: string // Database name
259
+ captureQueryText?: boolean // Capture SQL in spans (default: true)
260
+ maxQueryTextLength?: number // Max SQL length (default: 1000)
261
+ peerName?: string // Database host
262
+ peerPort?: number // Database port
263
+ }
264
+ ```
265
+
266
+ **Span Attributes:**
267
+
268
+ - `db.system` - Database type (postgresql, mysql, sqlite)
269
+ - `db.operation` - Operation name (SELECT, INSERT, UPDATE, DELETE)
270
+ - `db.name` - Database name
271
+ - `db.statement` - SQL query text (if `captureQueryText: true`)
272
+ - `net.peer.name` - Database host
273
+ - `net.peer.port` - Database port
274
+
275
+ ## Usage with Autotel
276
+
277
+ Drizzle instrumentation works seamlessly with [Autotel](../autotel):
278
+
279
+ ```typescript
280
+ import { init } from 'autotel';
281
+ import { instrumentDrizzleClient } from 'autotel-plugins/drizzle';
282
+ import { drizzle } from 'drizzle-orm/postgres-js';
283
+ import postgres from 'postgres';
284
+
285
+ // Initialize Autotel
286
+ init({
287
+ service: 'my-service',
288
+ endpoint: 'http://localhost:4318',
289
+ });
290
+
291
+ // Instrument your database
292
+ const client = postgres(process.env.DATABASE_URL!);
293
+ const db = drizzle({ client });
294
+ instrumentDrizzleClient(db, { dbSystem: 'postgresql' });
295
+
296
+ // Traces will be sent to your OTLP endpoint
297
+ await db.select().from(users);
298
+ ```
299
+
300
+ ## Combining with Official Packages
301
+
302
+ Mix autotel-plugins with official OpenTelemetry instrumentations:
303
+
304
+ ```typescript
305
+ import { init } from 'autotel';
306
+ import { instrumentDrizzleClient } from 'autotel-plugins/drizzle';
307
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
308
+ import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
309
+ import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
310
+
311
+ init({
312
+ service: 'my-service',
313
+ instrumentations: [
314
+ new HttpInstrumentation(),
315
+ new ExpressInstrumentation(),
316
+ new PgInstrumentation(), // Official packages
317
+ ],
318
+ });
319
+
320
+ // Drizzle (no official package available)
321
+ const db = drizzle({ client: postgres(process.env.DATABASE_URL!) });
322
+ instrumentDrizzleClient(db, { dbSystem: 'postgresql' });
323
+ ```
324
+
325
+ ## Security Considerations
326
+
327
+ ### Query Text Capture
328
+
329
+ By default, Drizzle instrumentation captures SQL text which may contain sensitive data:
330
+
331
+ ```typescript
332
+ // Disable SQL capture to prevent PII leakage
333
+ instrumentDrizzleClient(db, {
334
+ captureQueryText: false,
335
+ });
336
+ ```
337
+
338
+ ## Examples
339
+
340
+ See the [example-drizzle](../../apps/example-drizzle) directory for a complete working example.
341
+
342
+ ## TypeScript
343
+
344
+ Full type safety with TypeScript:
345
+
346
+ ```typescript
347
+ import type { InstrumentDrizzleConfig } from 'autotel-plugins';
348
+
349
+ const config: InstrumentDrizzleConfig = {
350
+ dbSystem: 'postgresql',
351
+ captureQueryText: true,
352
+ maxQueryTextLength: 1000,
353
+ };
354
+ ```
355
+
356
+ ## Future
357
+
358
+ When official OpenTelemetry instrumentation becomes available for Drizzle ORM, we will announce deprecation and provide a migration guide.
359
+
360
+ ## Creating Your Own Instrumentation
361
+
362
+ Don't see your library here? Autotel makes it easy to create custom instrumentation for any library using simple, well-tested utilities.
363
+
364
+ ### Quick Example
365
+
366
+ ```typescript
367
+ import { trace, SpanKind } from '@opentelemetry/api';
368
+ import { runWithSpan, finalizeSpan } from 'autotel/trace-helpers';
369
+
370
+ const INSTRUMENTED_FLAG = Symbol('instrumented');
371
+
372
+ export function instrumentMyLibrary(client) {
373
+ if (client[INSTRUMENTED_FLAG]) return client;
374
+
375
+ const tracer = trace.getTracer('my-library');
376
+ const originalMethod = client.someMethod.bind(client);
377
+
378
+ client.someMethod = async function (...args) {
379
+ const span = tracer.startSpan('operation', { kind: SpanKind.CLIENT });
380
+ span.setAttribute('operation.param', args[0]);
381
+
382
+ try {
383
+ const result = await runWithSpan(span, () => originalMethod(...args));
384
+ finalizeSpan(span);
385
+ return result;
386
+ } catch (error) {
387
+ finalizeSpan(span, error);
388
+ throw error;
389
+ }
390
+ };
391
+
392
+ client[INSTRUMENTED_FLAG] = true;
393
+ return client;
394
+ }
395
+ ```
396
+
397
+ ### Full Guide
398
+
399
+ For a comprehensive guide including:
400
+
401
+ - Step-by-step tutorial with real examples
402
+ - Best practices for security and idempotency
403
+ - Complete utilities reference
404
+ - Ready-to-use template code
405
+
406
+ **See: [Creating Custom Instrumentation](../autotel/README.md#creating-custom-instrumentation)** in the main autotel docs.
407
+
408
+ You can also check [`INSTRUMENTATION_TEMPLATE.ts`](../autotel/INSTRUMENTATION_TEMPLATE.ts) for a fully commented, copy-paste-ready template.
409
+
410
+ ## Contributing
411
+
412
+ Found a database/ORM without official OpenTelemetry instrumentation? Please [open an issue](https://github.com/jagreehal/autotel/issues) to discuss adding it.
413
+
414
+ ## License
415
+
416
+ MIT