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 +21 -0
- package/README.md +416 -0
- package/dist/drizzle.cjs +469 -0
- package/dist/drizzle.cjs.map +1 -0
- package/dist/drizzle.d.cts +194 -0
- package/dist/drizzle.d.ts +194 -0
- package/dist/drizzle.js +466 -0
- package/dist/drizzle.js.map +1 -0
- package/dist/index.cjs +833 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +822 -0
- package/dist/index.js.map +1 -0
- package/dist/mongoose.cjs +376 -0
- package/dist/mongoose.cjs.map +1 -0
- package/dist/mongoose.d.cts +77 -0
- package/dist/mongoose.d.ts +77 -0
- package/dist/mongoose.js +372 -0
- package/dist/mongoose.js.map +1 -0
- package/package.json +105 -0
- package/src/common/constants.ts +17 -0
- package/src/drizzle/index.ts +898 -0
- package/src/index.ts +68 -0
- package/src/mongoose/index.ts +595 -0
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
|