autotel-mongoose 0.0.1

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/dist/index.cjs ADDED
@@ -0,0 +1,596 @@
1
+ 'use strict';
2
+
3
+ var autotel = require('autotel');
4
+ var traceHelpers = require('autotel/trace-helpers');
5
+
6
+ // src/instrumentation.ts
7
+
8
+ // src/constants.ts
9
+ var ATTR_DB_QUERY_TEXT = "db.query.text";
10
+ var ATTR_DB_OPERATION_NAME = "db.operation.name";
11
+ var ATTR_DB_SYSTEM_NAME = "db.system.name";
12
+ var ATTR_DB_COLLECTION_NAME = "db.collection.name";
13
+ var ATTR_DB_NAMESPACE = "db.namespace";
14
+ var ATTR_SERVER_ADDRESS = "server.address";
15
+ var ATTR_SERVER_PORT = "server.port";
16
+ var DB_SYSTEM_NAME_VALUE_MONGODB = "mongodb";
17
+
18
+ // src/types.ts
19
+ var DEFAULT_TRACER_NAME = "autotel-mongoose";
20
+ function defaultSerializer(_operation, payload) {
21
+ return JSON.stringify(payload);
22
+ }
23
+ function createStatementCapture(config) {
24
+ if (config.dbStatementSerializer === false) {
25
+ return () => {
26
+ return;
27
+ };
28
+ }
29
+ const serializer = typeof config.dbStatementSerializer === "function" ? config.dbStatementSerializer : defaultSerializer;
30
+ let redact;
31
+ if (config.statementRedactor !== false && config.statementRedactor !== void 0) {
32
+ redact = autotel.createStringRedactor(config.statementRedactor);
33
+ }
34
+ return (operation, payload) => {
35
+ const raw = serializer(operation, payload);
36
+ if (raw === void 0) {
37
+ return void 0;
38
+ }
39
+ return redact ? redact(raw) : raw;
40
+ };
41
+ }
42
+
43
+ // src/instrumentation.ts
44
+ var INSTRUMENTED_FLAG = "__autotelMongooseInstrumented";
45
+ var WRAPPED_HOOK_FLAG = "__autotelWrappedHook";
46
+ var _STORED_PARENT_SPAN = /* @__PURE__ */ Symbol("stored-parent-span");
47
+ function createSpan(tracer, operation, modelName, collectionName, config) {
48
+ const spanName = collectionName ? `${operation} ${collectionName}` : modelName ? `${operation} ${modelName}` : `mongoose.${operation}`;
49
+ const attributes = {
50
+ [ATTR_DB_SYSTEM_NAME]: DB_SYSTEM_NAME_VALUE_MONGODB,
51
+ [ATTR_DB_OPERATION_NAME]: operation
52
+ };
53
+ if (collectionName && config.captureCollectionName) {
54
+ attributes[ATTR_DB_COLLECTION_NAME] = collectionName;
55
+ }
56
+ if (config.dbName) {
57
+ attributes[ATTR_DB_NAMESPACE] = config.dbName;
58
+ }
59
+ if (config.peerName) {
60
+ attributes[ATTR_SERVER_ADDRESS] = config.peerName;
61
+ }
62
+ if (config.peerPort) {
63
+ attributes[ATTR_SERVER_PORT] = config.peerPort;
64
+ }
65
+ return tracer.startSpan(spanName, { kind: autotel.SpanKind.CLIENT, attributes });
66
+ }
67
+ function wrapQueryReturningMethod(target, methodName, operation, getCollectionName, getModelName, tracer, config, captureStatement) {
68
+ const original = target[methodName];
69
+ if (typeof original !== "function") {
70
+ return;
71
+ }
72
+ target[methodName] = function instrumented(...args) {
73
+ const collectionName = getCollectionName(this);
74
+ const modelName = getModelName(this);
75
+ const span = createSpan(
76
+ tracer,
77
+ operation,
78
+ modelName,
79
+ collectionName,
80
+ config
81
+ );
82
+ return traceHelpers.runWithSpan(span, () => {
83
+ try {
84
+ const result = original.apply(this, args);
85
+ if (result && typeof result.exec === "function") {
86
+ try {
87
+ const payload = {};
88
+ if (typeof result.getFilter === "function") {
89
+ payload.condition = result.getFilter();
90
+ }
91
+ if (result._update !== void 0) {
92
+ payload.updates = result._update;
93
+ }
94
+ if (typeof result.getOptions === "function") {
95
+ payload.options = result.getOptions();
96
+ }
97
+ if (result._fields !== void 0) {
98
+ payload.fields = result._fields;
99
+ }
100
+ const statementText = captureStatement(operation, payload);
101
+ if (statementText) {
102
+ span.setAttribute(ATTR_DB_QUERY_TEXT, statementText);
103
+ }
104
+ } catch {
105
+ }
106
+ const originalExec = result.exec.bind(result);
107
+ result.exec = function wrappedExec() {
108
+ try {
109
+ const execPromise = originalExec();
110
+ return Promise.resolve(execPromise).then((value) => {
111
+ traceHelpers.finalizeSpan(span);
112
+ return value;
113
+ }).catch((error) => {
114
+ traceHelpers.finalizeSpan(
115
+ span,
116
+ error instanceof Error ? error : new Error(String(error))
117
+ );
118
+ throw error;
119
+ });
120
+ } catch (error) {
121
+ traceHelpers.finalizeSpan(
122
+ span,
123
+ error instanceof Error ? error : new Error(String(error))
124
+ );
125
+ throw error;
126
+ }
127
+ };
128
+ return result;
129
+ }
130
+ traceHelpers.finalizeSpan(span);
131
+ return result;
132
+ } catch (error) {
133
+ traceHelpers.finalizeSpan(
134
+ span,
135
+ error instanceof Error ? error : new Error(String(error))
136
+ );
137
+ throw error;
138
+ }
139
+ });
140
+ };
141
+ }
142
+ function wrapStaticMethod(target, methodName, operation, getCollectionName, getModelName, tracer, config, captureStatement) {
143
+ const original = target[methodName];
144
+ if (typeof original !== "function") {
145
+ return;
146
+ }
147
+ target[methodName] = function instrumented(...args) {
148
+ const collectionName = getCollectionName(this);
149
+ const modelName = getModelName(this);
150
+ const payload = {};
151
+ try {
152
+ switch (operation) {
153
+ case "create": {
154
+ payload.document = args[0];
155
+ break;
156
+ }
157
+ case "insertMany": {
158
+ payload.documents = args[0];
159
+ break;
160
+ }
161
+ case "aggregate": {
162
+ payload.aggregatePipeline = args[0];
163
+ break;
164
+ }
165
+ case "bulkWrite": {
166
+ payload.operations = args[0];
167
+ break;
168
+ }
169
+ default: {
170
+ break;
171
+ }
172
+ }
173
+ } catch {
174
+ }
175
+ const span = createSpan(
176
+ tracer,
177
+ operation,
178
+ modelName,
179
+ collectionName,
180
+ config
181
+ );
182
+ try {
183
+ const statementText = captureStatement(operation, payload);
184
+ if (statementText) {
185
+ span.setAttribute(ATTR_DB_QUERY_TEXT, statementText);
186
+ }
187
+ } catch {
188
+ }
189
+ return traceHelpers.runWithSpan(span, () => {
190
+ try {
191
+ const result = original.apply(this, args);
192
+ if (result && typeof result.exec === "function") {
193
+ const originalExec = result.exec.bind(result);
194
+ result.exec = function wrappedExec() {
195
+ try {
196
+ const execPromise = originalExec();
197
+ return Promise.resolve(execPromise).then((value) => {
198
+ traceHelpers.finalizeSpan(span);
199
+ return value;
200
+ }).catch((error) => {
201
+ traceHelpers.finalizeSpan(
202
+ span,
203
+ error instanceof Error ? error : new Error(String(error))
204
+ );
205
+ throw error;
206
+ });
207
+ } catch (error) {
208
+ traceHelpers.finalizeSpan(
209
+ span,
210
+ error instanceof Error ? error : new Error(String(error))
211
+ );
212
+ throw error;
213
+ }
214
+ };
215
+ return result;
216
+ }
217
+ if (result && typeof result.then === "function") {
218
+ return Promise.resolve(result).then((value) => {
219
+ traceHelpers.finalizeSpan(span);
220
+ return value;
221
+ }).catch((error) => {
222
+ traceHelpers.finalizeSpan(
223
+ span,
224
+ error instanceof Error ? error : new Error(String(error))
225
+ );
226
+ throw error;
227
+ });
228
+ }
229
+ traceHelpers.finalizeSpan(span);
230
+ return result;
231
+ } catch (error) {
232
+ traceHelpers.finalizeSpan(
233
+ span,
234
+ error instanceof Error ? error : new Error(String(error))
235
+ );
236
+ throw error;
237
+ }
238
+ });
239
+ };
240
+ }
241
+ function wrapInstanceMethod(target, methodName, operation, getCollectionName, getModelName, tracer, config, captureStatement) {
242
+ const original = target[methodName];
243
+ if (typeof original !== "function") {
244
+ return;
245
+ }
246
+ target[methodName] = function instrumented(...args) {
247
+ const collectionName = getCollectionName(this);
248
+ const modelName = getModelName(this);
249
+ const payload = {};
250
+ try {
251
+ if (typeof this.toObject === "function") {
252
+ payload.document = this.toObject();
253
+ }
254
+ } catch {
255
+ }
256
+ const span = createSpan(
257
+ tracer,
258
+ operation,
259
+ modelName,
260
+ collectionName,
261
+ config
262
+ );
263
+ try {
264
+ const statementText = captureStatement(operation, payload);
265
+ if (statementText) {
266
+ span.setAttribute(ATTR_DB_QUERY_TEXT, statementText);
267
+ }
268
+ } catch {
269
+ }
270
+ return traceHelpers.runWithSpan(span, () => {
271
+ try {
272
+ const result = original.apply(this, args);
273
+ if (result && typeof result.then === "function") {
274
+ return Promise.resolve(result).then((value) => {
275
+ traceHelpers.finalizeSpan(span);
276
+ return value;
277
+ }).catch((error) => {
278
+ traceHelpers.finalizeSpan(
279
+ span,
280
+ error instanceof Error ? error : new Error(String(error))
281
+ );
282
+ throw error;
283
+ });
284
+ }
285
+ traceHelpers.finalizeSpan(span);
286
+ return result;
287
+ } catch (error) {
288
+ traceHelpers.finalizeSpan(
289
+ span,
290
+ error instanceof Error ? error : new Error(String(error))
291
+ );
292
+ throw error;
293
+ }
294
+ });
295
+ };
296
+ }
297
+ function wrapChainableMethod(target, methodName) {
298
+ const original = target[methodName];
299
+ if (typeof original !== "function") {
300
+ return;
301
+ }
302
+ target[methodName] = function captureContext(...args) {
303
+ const currentSpan = traceHelpers.getActiveSpan();
304
+ const result = original.apply(this, args);
305
+ if (result && typeof result.exec === "function") {
306
+ result[_STORED_PARENT_SPAN] = currentSpan;
307
+ }
308
+ return result;
309
+ };
310
+ }
311
+ function patchSchemaHooks(Schema, tracer, config) {
312
+ if (!Schema?.prototype) {
313
+ return;
314
+ }
315
+ const HOOK_FLAG = "__autotelHookInstrumented";
316
+ if (Schema.prototype[HOOK_FLAG]) {
317
+ return;
318
+ }
319
+ const originalPre = Schema.prototype.pre;
320
+ if (typeof originalPre === "function") {
321
+ Schema.prototype.pre = function(hookName, ...args) {
322
+ const handler = typeof args[0] === "function" ? args[0] : typeof args[1] === "function" ? args[1] : null;
323
+ if (handler && !isMongooseInternalHook(handler)) {
324
+ const wrapped = wrapHookHandler(
325
+ handler,
326
+ hookName,
327
+ "pre",
328
+ tracer,
329
+ config
330
+ );
331
+ if (typeof args[0] === "function") {
332
+ args[0] = wrapped;
333
+ } else if (typeof args[1] === "function") {
334
+ args[1] = wrapped;
335
+ }
336
+ }
337
+ return Reflect.apply(originalPre, this, [hookName, ...args]);
338
+ };
339
+ }
340
+ const originalPost = Schema.prototype.post;
341
+ if (typeof originalPost === "function") {
342
+ Schema.prototype.post = function(hookName, ...args) {
343
+ const handler = typeof args[0] === "function" ? args[0] : typeof args[1] === "function" ? args[1] : null;
344
+ if (handler && !isMongooseInternalHook(handler)) {
345
+ const wrapped = wrapHookHandler(
346
+ handler,
347
+ hookName,
348
+ "post",
349
+ tracer,
350
+ config
351
+ );
352
+ if (typeof args[0] === "function") {
353
+ args[0] = wrapped;
354
+ } else if (typeof args[1] === "function") {
355
+ args[1] = wrapped;
356
+ }
357
+ }
358
+ return Reflect.apply(originalPost, this, [hookName, ...args]);
359
+ };
360
+ }
361
+ Schema.prototype[HOOK_FLAG] = true;
362
+ }
363
+ function isMongooseInternalHook(handler) {
364
+ if (typeof handler !== "function") {
365
+ return false;
366
+ }
367
+ const funcName = handler.name || "";
368
+ if (funcName.startsWith("_") || funcName.startsWith("$")) {
369
+ return true;
370
+ }
371
+ const mongooseInternalNamePatterns = [
372
+ "shardingPlugin",
373
+ "mongooseInternalHook",
374
+ "noop",
375
+ "wrapped",
376
+ "bound "
377
+ ];
378
+ if (mongooseInternalNamePatterns.some((pattern) => funcName.includes(pattern))) {
379
+ return true;
380
+ }
381
+ try {
382
+ const source = handler.toString();
383
+ const mongooseInternalSourcePatterns = [
384
+ "this.$__",
385
+ // Mongoose internal document methods
386
+ "this.$isValid",
387
+ // Mongoose validation
388
+ "this.$locals",
389
+ // Mongoose local properties
390
+ "_this.$__",
391
+ // Mongoose internal with closure
392
+ "schema.s.hooks",
393
+ // Mongoose hooks system
394
+ "kareem"
395
+ // Mongoose's hooks library
396
+ ];
397
+ if (mongooseInternalSourcePatterns.some((pattern) => source.includes(pattern))) {
398
+ return true;
399
+ }
400
+ } catch {
401
+ }
402
+ return false;
403
+ }
404
+ function wrapHookHandler(handler, hookName, hookType, tracer, config) {
405
+ if (typeof handler !== "function") {
406
+ return handler;
407
+ }
408
+ if (handler[WRAPPED_HOOK_FLAG]) {
409
+ return handler;
410
+ }
411
+ const wrappedHook = function wrappedHook2(...args) {
412
+ let modelName;
413
+ let collectionName;
414
+ try {
415
+ if (this.constructor?.modelName) {
416
+ modelName = this.constructor.modelName;
417
+ collectionName = this.constructor.collection?.collectionName || modelName;
418
+ } else if (this.model?.modelName) {
419
+ modelName = this.model.modelName;
420
+ collectionName = this.model.collection?.collectionName || modelName;
421
+ }
422
+ } catch {
423
+ }
424
+ const spanName = collectionName ? `mongoose.${collectionName}.${hookType}.${hookName}` : `mongoose.hook.${hookType}.${hookName}`;
425
+ const span = tracer.startSpan(spanName, { kind: autotel.SpanKind.INTERNAL });
426
+ span.setAttribute("hook.type", hookType);
427
+ span.setAttribute("hook.operation", hookName);
428
+ if (modelName) {
429
+ span.setAttribute("hook.model", modelName);
430
+ }
431
+ if (collectionName && config.captureCollectionName) {
432
+ span.setAttribute(ATTR_DB_COLLECTION_NAME, collectionName);
433
+ }
434
+ span.setAttribute(ATTR_DB_SYSTEM_NAME, DB_SYSTEM_NAME_VALUE_MONGODB);
435
+ if (config.dbName) {
436
+ span.setAttribute(ATTR_DB_NAMESPACE, config.dbName);
437
+ }
438
+ return traceHelpers.runWithSpan(span, () => {
439
+ try {
440
+ const result = handler.apply(this, args);
441
+ if (result && typeof result.then === "function") {
442
+ return Promise.resolve(result).then((value) => {
443
+ traceHelpers.finalizeSpan(span);
444
+ return value;
445
+ }).catch((error) => {
446
+ traceHelpers.finalizeSpan(
447
+ span,
448
+ error instanceof Error ? error : new Error(String(error))
449
+ );
450
+ throw error;
451
+ });
452
+ }
453
+ traceHelpers.finalizeSpan(span);
454
+ return result;
455
+ } catch (error) {
456
+ traceHelpers.finalizeSpan(
457
+ span,
458
+ error instanceof Error ? error : new Error(String(error))
459
+ );
460
+ throw error;
461
+ }
462
+ });
463
+ };
464
+ wrappedHook[WRAPPED_HOOK_FLAG] = true;
465
+ return wrappedHook;
466
+ }
467
+ function instrumentMongoose(mongoose, config) {
468
+ if (!mongoose?.Model) {
469
+ return mongoose;
470
+ }
471
+ const m = mongoose;
472
+ if (m[INSTRUMENTED_FLAG]) {
473
+ return mongoose;
474
+ }
475
+ const resolvedSerializer = config?.dbStatementSerializer;
476
+ const resolvedRedactor = config?.statementRedactor ?? "default";
477
+ const finalConfig = {
478
+ dbName: config?.dbName || "",
479
+ peerName: config?.peerName || "",
480
+ peerPort: config?.peerPort || 27017,
481
+ tracerName: config?.tracerName || DEFAULT_TRACER_NAME,
482
+ captureCollectionName: config?.captureCollectionName ?? true,
483
+ instrumentHooks: config?.instrumentHooks ?? false};
484
+ const tracer = autotel.otelTrace.getTracer(finalConfig.tracerName);
485
+ const captureStatement = createStatementCapture({
486
+ dbStatementSerializer: resolvedSerializer,
487
+ statementRedactor: resolvedRedactor
488
+ });
489
+ if (m.Schema && finalConfig.instrumentHooks) {
490
+ patchSchemaHooks(m.Schema, tracer, finalConfig);
491
+ }
492
+ const getModelCollectionName = (model) => {
493
+ try {
494
+ return model.collection?.collectionName || model.modelName;
495
+ } catch {
496
+ return;
497
+ }
498
+ };
499
+ const queryMethods = [
500
+ { method: "find", operation: "find" },
501
+ { method: "findOne", operation: "findOne" },
502
+ { method: "findById", operation: "findById" },
503
+ { method: "findOneAndUpdate", operation: "findOneAndUpdate" },
504
+ { method: "findOneAndDelete", operation: "findOneAndDelete" },
505
+ { method: "findOneAndReplace", operation: "findOneAndReplace" },
506
+ { method: "deleteOne", operation: "deleteOne" },
507
+ { method: "deleteMany", operation: "deleteMany" },
508
+ { method: "updateOne", operation: "updateOne" },
509
+ { method: "updateMany", operation: "updateMany" },
510
+ { method: "countDocuments", operation: "countDocuments" },
511
+ { method: "estimatedDocumentCount", operation: "estimatedDocumentCount" }
512
+ ];
513
+ for (const { method, operation } of queryMethods) {
514
+ wrapQueryReturningMethod(
515
+ m.Model,
516
+ method,
517
+ operation,
518
+ getModelCollectionName,
519
+ (model) => model.modelName,
520
+ tracer,
521
+ finalConfig,
522
+ captureStatement
523
+ );
524
+ if (m.Query?.prototype?.[method]) {
525
+ wrapChainableMethod(m.Query.prototype, method);
526
+ }
527
+ }
528
+ const instanceMethods = ["save", "deleteOne"];
529
+ for (const method of instanceMethods) {
530
+ if (m.Model.prototype[method]) {
531
+ wrapInstanceMethod(
532
+ m.Model.prototype,
533
+ method,
534
+ method,
535
+ (doc) => {
536
+ try {
537
+ return doc.constructor?.collection?.collectionName || doc.constructor?.modelName;
538
+ } catch {
539
+ return;
540
+ }
541
+ },
542
+ (doc) => {
543
+ try {
544
+ return doc.constructor?.modelName;
545
+ } catch {
546
+ return;
547
+ }
548
+ },
549
+ tracer,
550
+ finalConfig,
551
+ captureStatement
552
+ );
553
+ }
554
+ }
555
+ const staticMethods = ["create", "insertMany", "aggregate", "bulkWrite"];
556
+ for (const method of staticMethods) {
557
+ if (m.Model[method]) {
558
+ wrapStaticMethod(
559
+ m.Model,
560
+ method,
561
+ method,
562
+ (model) => {
563
+ try {
564
+ return model.collection?.collectionName;
565
+ } catch {
566
+ return;
567
+ }
568
+ },
569
+ (model) => model.modelName,
570
+ tracer,
571
+ finalConfig,
572
+ captureStatement
573
+ );
574
+ }
575
+ }
576
+ const chainableMethods = [
577
+ "populate",
578
+ "select",
579
+ "lean",
580
+ "where",
581
+ "sort",
582
+ "limit",
583
+ "skip"
584
+ ];
585
+ for (const method of chainableMethods) {
586
+ if (m.Query?.prototype?.[method]) {
587
+ wrapChainableMethod(m.Query.prototype, method);
588
+ }
589
+ }
590
+ m[INSTRUMENTED_FLAG] = true;
591
+ return mongoose;
592
+ }
593
+
594
+ exports.instrumentMongoose = instrumentMongoose;
595
+ //# sourceMappingURL=index.cjs.map
596
+ //# sourceMappingURL=index.cjs.map