autotel-mongoose 8.0.0 → 8.1.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/README.md +115 -1
- package/dist/index.cjs +340 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -1
- package/dist/index.d.ts +80 -1
- package/dist/index.js +340 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +14 -0
- package/src/custom-methods.integration.test.ts +344 -0
- package/src/index.ts +8 -1
- package/src/instrumentation.ts +447 -0
- package/src/statement.ts +105 -1
- package/src/types.ts +110 -0
package/README.md
CHANGED
|
@@ -77,6 +77,117 @@ userSchema.pre('save', async function () {
|
|
|
77
77
|
});
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
## Custom Statics, Methods & Query Helpers
|
|
81
|
+
|
|
82
|
+
The functions you add via `schema.statics`, `schema.methods`, and `schema.query`
|
|
83
|
+
are invisible to the built-in Model/Query instrumentation. This package traces
|
|
84
|
+
them automatically — **no manual `trace()` calls** and no behavioral side
|
|
85
|
+
effects (same `this`, same return value, same error propagation). Each call
|
|
86
|
+
gets an `INTERNAL` span named `mongoose.<Model>.<fn>`.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
userSchema.statics.findByEmail = function (email: string) {
|
|
90
|
+
return this.findOne({ email }); // span: mongoose.User.findByEmail
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
userSchema.methods.describe = function () {
|
|
94
|
+
return `${this.name} <${this.email}>`; // span: mongoose.User.describe
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
userSchema.query.byEmailDomain = function (domain: string) {
|
|
98
|
+
return this.where({ email: new RegExp(`@${domain}$`) }); // span: mongoose.User.byEmailDomain
|
|
99
|
+
};
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Example spans (from `apps/example-mongoose`, debug output). Note that a static
|
|
103
|
+
returning a Query becomes the **parent** of the underlying operation span, and
|
|
104
|
+
parameters are redacted by default:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
✓ findOne users 1ms [autotel-mongoose]
|
|
108
|
+
db.system.name=mongodb, db.operation.name=findOne, db.collection.name=users, db.query.text={"condition":{"email":"A***@***.com"},...
|
|
109
|
+
✓ mongoose.User.findByEmail 2ms [autotel-mongoose]
|
|
110
|
+
db.system.name=mongodb, code.function.name=findByEmail, mongoose.method.name=findByEmail, mongoose.method.type=static, mongoose.method.model=User, db.collection.name=users, mongoose.method.parameter_count=1, mongoose.method.parameters=["A***@***.com"]
|
|
111
|
+
|
|
112
|
+
✓ mongoose.User.describe 27µs [autotel-mongoose]
|
|
113
|
+
db.system.name=mongodb, code.function.name=describe, mongoose.method.name=describe, mongoose.method.type=instance, mongoose.method.model=User, db.collection.name=users, mongoose.method.parameter_count=0
|
|
114
|
+
|
|
115
|
+
✓ mongoose.User.countByDomain 2ms [autotel-mongoose]
|
|
116
|
+
db.system.name=mongodb, code.function.name=countByDomain, mongoose.method.name=countByDomain, mongoose.method.type=static, mongoose.method.model=User, db.collection.name=users, mongoose.method.parameter_count=1, mongoose.method.parameters=["hotmail.com"]
|
|
117
|
+
|
|
118
|
+
✓ mongoose.User.byEmailDomain 82µs [autotel-mongoose]
|
|
119
|
+
db.system.name=mongodb, code.function.name=byEmailDomain, mongoose.method.name=byEmailDomain, mongoose.method.type=query, mongoose.method.model=User, db.collection.name=users, mongoose.method.parameter_count=1, mongoose.method.parameters=["hotmail.com"]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
As JSON:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"name": "mongoose.User.findByEmail",
|
|
127
|
+
"kind": "INTERNAL",
|
|
128
|
+
"instrumentationScope": { "name": "autotel-mongoose" },
|
|
129
|
+
"attributes": {
|
|
130
|
+
"db.system.name": "mongodb",
|
|
131
|
+
"code.function.name": "findByEmail",
|
|
132
|
+
"mongoose.method.name": "findByEmail",
|
|
133
|
+
"mongoose.method.type": "static",
|
|
134
|
+
"mongoose.method.model": "User",
|
|
135
|
+
"db.collection.name": "users",
|
|
136
|
+
"mongoose.method.parameter_count": 1,
|
|
137
|
+
"mongoose.method.parameters": "[\"A***@***.com\"]"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Span attributes: `mongoose.method.name`, `mongoose.method.type`
|
|
143
|
+
(`static` | `instance` | `query`), `mongoose.method.model`, `code.function.name`,
|
|
144
|
+
and — when parameter capture is on — `mongoose.method.parameters` (+
|
|
145
|
+
`mongoose.method.parameter_count`).
|
|
146
|
+
|
|
147
|
+
> **Behavior note (default on):** With no `customMethods` option, `instrumentMongoose(mongoose)`
|
|
148
|
+
> wraps **all** custom functions and captures their arguments by default
|
|
149
|
+
> (maximum observability). Arguments pass through the same redactor as
|
|
150
|
+
> `db.query.text`, but custom-function args are often business payloads rather
|
|
151
|
+
> than DB filters — redaction won't catch arbitrary fields. Use the options
|
|
152
|
+
> below to scope this down for privacy/compliance.
|
|
153
|
+
|
|
154
|
+
### Opting out / scoping (privacy & compliance)
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Disable entirely
|
|
158
|
+
instrumentMongoose(mongoose, { customMethods: false });
|
|
159
|
+
|
|
160
|
+
// Per-category control. Anything not explicitly disabled stays on.
|
|
161
|
+
instrumentMongoose(mongoose, {
|
|
162
|
+
customMethods: {
|
|
163
|
+
statics: { exclude: ['chargeCard'] }, // opt-out specific statics
|
|
164
|
+
methods: ['describe'], // opt-in: only these instance methods
|
|
165
|
+
query: false, // no query helpers
|
|
166
|
+
captureParameters: false, // trace calls, don't serialize args
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Keep tracing, but never serialize arguments anywhere
|
|
171
|
+
instrumentMongoose(mongoose, { customMethods: { captureParameters: false } });
|
|
172
|
+
|
|
173
|
+
// Custom parameter serializer / longer cap / dedicated redactor
|
|
174
|
+
instrumentMongoose(mongoose, {
|
|
175
|
+
customMethods: {
|
|
176
|
+
captureParameters: {
|
|
177
|
+
maxLength: 4096,
|
|
178
|
+
redactor: 'default',
|
|
179
|
+
serializer: (args, { methodName }) =>
|
|
180
|
+
methodName === 'chargeCard' ? undefined : JSON.stringify(args),
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
A selector accepts `true` (all), `false` (none), `string[]` (opt-in to those
|
|
187
|
+
names), or `{ include?, exclude? }`. Config is resolved **per Mongoose
|
|
188
|
+
instance** at call time, so a schema object reused across multiple
|
|
189
|
+
instances/connections honors each instance's own configuration.
|
|
190
|
+
|
|
80
191
|
## Configuration
|
|
81
192
|
|
|
82
193
|
```typescript
|
|
@@ -91,6 +202,7 @@ const config: InstrumentMongooseConfig = {
|
|
|
91
202
|
instrumentHooks: false,
|
|
92
203
|
dbStatementSerializer: false,
|
|
93
204
|
statementRedactor: 'default',
|
|
205
|
+
customMethods: true, // wrap all custom statics/methods/query helpers (default)
|
|
94
206
|
};
|
|
95
207
|
```
|
|
96
208
|
|
|
@@ -137,13 +249,15 @@ instrumentMongoose(mongoose, {
|
|
|
137
249
|
- `instrumentMongoose(mongoose, config?)`
|
|
138
250
|
- `InstrumentMongooseConfig`
|
|
139
251
|
- `SerializerPayload`
|
|
252
|
+
- `CustomMethodsConfig`, `CustomMethodType`, `MethodSelector`, `ParameterCaptureConfig`
|
|
140
253
|
|
|
141
254
|
## Notes
|
|
142
255
|
|
|
143
256
|
- Query and aggregate operations are traced automatically
|
|
144
257
|
- Instance methods like `save()` and `deleteOne()` are traced
|
|
145
258
|
- Static methods like `create()`, `insertMany()`, `aggregate()`, and `bulkWrite()` are traced
|
|
146
|
-
-
|
|
259
|
+
- User-defined statics, instance methods, and query helpers are traced automatically (see above)
|
|
260
|
+
- Hook and custom-function spans use `SpanKind.INTERNAL`
|
|
147
261
|
|
|
148
262
|
## License
|
|
149
263
|
|
package/dist/index.cjs
CHANGED
|
@@ -13,6 +13,12 @@ var ATTR_DB_COLLECTION_NAME = "db.collection.name";
|
|
|
13
13
|
var ATTR_DB_NAMESPACE = "db.namespace";
|
|
14
14
|
var ATTR_SERVER_ADDRESS = "server.address";
|
|
15
15
|
var ATTR_SERVER_PORT = "server.port";
|
|
16
|
+
var ATTR_CODE_FUNCTION_NAME = "code.function.name";
|
|
17
|
+
var ATTR_MONGOOSE_METHOD_NAME = "mongoose.method.name";
|
|
18
|
+
var ATTR_MONGOOSE_METHOD_TYPE = "mongoose.method.type";
|
|
19
|
+
var ATTR_MONGOOSE_METHOD_MODEL = "mongoose.method.model";
|
|
20
|
+
var ATTR_MONGOOSE_METHOD_PARAMETERS = "mongoose.method.parameters";
|
|
21
|
+
var ATTR_MONGOOSE_METHOD_PARAMETER_COUNT = "mongoose.method.parameter_count";
|
|
16
22
|
var DB_SYSTEM_NAME_VALUE_MONGODB = "mongodb";
|
|
17
23
|
|
|
18
24
|
// src/types.ts
|
|
@@ -39,10 +45,99 @@ function createStatementCapture(config) {
|
|
|
39
45
|
return redact ? redact(raw) : raw;
|
|
40
46
|
};
|
|
41
47
|
}
|
|
48
|
+
var DEFAULT_PARAMETER_MAX_LENGTH = 2048;
|
|
49
|
+
function defaultParameterSerializer(args) {
|
|
50
|
+
if (args.length === 0) {
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
54
|
+
const replacer = (_key, value) => {
|
|
55
|
+
if (typeof value === "bigint") {
|
|
56
|
+
return value.toString();
|
|
57
|
+
}
|
|
58
|
+
if (typeof value === "function") {
|
|
59
|
+
return "[Function]";
|
|
60
|
+
}
|
|
61
|
+
if (typeof value === "object" && value !== null) {
|
|
62
|
+
if (seen.has(value)) {
|
|
63
|
+
return "[Circular]";
|
|
64
|
+
}
|
|
65
|
+
seen.add(value);
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
};
|
|
69
|
+
const normalized = args.map((arg) => {
|
|
70
|
+
if (arg !== null && typeof arg === "object" && typeof arg.toObject === "function") {
|
|
71
|
+
try {
|
|
72
|
+
return arg.toObject();
|
|
73
|
+
} catch {
|
|
74
|
+
return arg;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return arg;
|
|
78
|
+
});
|
|
79
|
+
try {
|
|
80
|
+
const json = JSON.stringify(normalized, replacer);
|
|
81
|
+
return json === void 0 ? void 0 : json;
|
|
82
|
+
} catch {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function createParameterCapture(config) {
|
|
87
|
+
const { parameterConfig } = config;
|
|
88
|
+
const maxLength = parameterConfig?.maxLength ?? DEFAULT_PARAMETER_MAX_LENGTH;
|
|
89
|
+
const serialize = parameterConfig?.serializer ?? defaultParameterSerializer;
|
|
90
|
+
const redactorSetting = parameterConfig?.redactor === void 0 ? config.statementRedactor : parameterConfig.redactor;
|
|
91
|
+
let redact;
|
|
92
|
+
if (redactorSetting !== false && redactorSetting !== void 0) {
|
|
93
|
+
redact = autotel.createStringRedactor(redactorSetting);
|
|
94
|
+
}
|
|
95
|
+
return (args, context) => {
|
|
96
|
+
const raw = serialize(args, context);
|
|
97
|
+
if (raw === void 0) {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
const redacted = redact ? redact(raw) : raw;
|
|
101
|
+
return redacted.length > maxLength ? `${redacted.slice(0, maxLength)}\u2026[truncated]` : redacted;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
42
104
|
|
|
43
105
|
// src/instrumentation.ts
|
|
44
106
|
var INSTRUMENTED_FLAG = "__autotelMongooseInstrumented";
|
|
45
107
|
var WRAPPED_HOOK_FLAG = "__autotelWrappedHook";
|
|
108
|
+
var WRAPPED_METHOD_FLAG = "__autotelWrappedMethod";
|
|
109
|
+
var MODEL_PATCHED_FLAG = "__autotelModelPatched";
|
|
110
|
+
var INSTANCE_REGISTRY = /* @__PURE__ */ new WeakMap();
|
|
111
|
+
function resolveMongooseInstance(self, methodType) {
|
|
112
|
+
try {
|
|
113
|
+
switch (methodType) {
|
|
114
|
+
case "static": {
|
|
115
|
+
return self?.base ?? self?.db?.base;
|
|
116
|
+
}
|
|
117
|
+
case "instance": {
|
|
118
|
+
return self?.constructor?.base ?? self?.db?.base;
|
|
119
|
+
}
|
|
120
|
+
case "query": {
|
|
121
|
+
return self?.model?.base;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
function selectorFor(cm, methodType) {
|
|
129
|
+
switch (methodType) {
|
|
130
|
+
case "static": {
|
|
131
|
+
return cm.statics;
|
|
132
|
+
}
|
|
133
|
+
case "instance": {
|
|
134
|
+
return cm.methods;
|
|
135
|
+
}
|
|
136
|
+
case "query": {
|
|
137
|
+
return cm.query;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
46
141
|
var _STORED_PARENT_SPAN = /* @__PURE__ */ Symbol("stored-parent-span");
|
|
47
142
|
function createSpan(tracer, operation, modelName, collectionName, config) {
|
|
48
143
|
const spanName = collectionName ? `${operation} ${collectionName}` : modelName ? `${operation} ${modelName}` : `mongoose.${operation}`;
|
|
@@ -499,6 +594,244 @@ function wrapHookHandler(handler, hookName, hookType, tracer, config) {
|
|
|
499
594
|
wrappedHook[WRAPPED_HOOK_FLAG] = true;
|
|
500
595
|
return wrappedHook;
|
|
501
596
|
}
|
|
597
|
+
function resolveCustomMethods(config) {
|
|
598
|
+
const setting = config?.customMethods;
|
|
599
|
+
if (setting === false) {
|
|
600
|
+
return {
|
|
601
|
+
enabled: false,
|
|
602
|
+
statics: false,
|
|
603
|
+
methods: false,
|
|
604
|
+
query: false,
|
|
605
|
+
captureParameters: false
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
const obj = setting === void 0 || setting === true ? {} : setting;
|
|
609
|
+
const cp = obj.captureParameters;
|
|
610
|
+
let captureParameters = false;
|
|
611
|
+
if (cp !== false) {
|
|
612
|
+
captureParameters = createParameterCapture({
|
|
613
|
+
parameterConfig: cp === void 0 || cp === true ? void 0 : cp,
|
|
614
|
+
// Parameters inherit the same PII redaction as db.query.text by default.
|
|
615
|
+
statementRedactor: config?.statementRedactor ?? "default"
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
return {
|
|
619
|
+
enabled: true,
|
|
620
|
+
statics: obj.statics ?? true,
|
|
621
|
+
methods: obj.methods ?? true,
|
|
622
|
+
query: obj.query ?? true,
|
|
623
|
+
captureParameters
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function selectorAllows(selector, name) {
|
|
627
|
+
if (selector === false) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
if (selector === true) {
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
if (Array.isArray(selector)) {
|
|
634
|
+
return selector.includes(name);
|
|
635
|
+
}
|
|
636
|
+
if (selector.include && !selector.include.includes(name)) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
if (selector.exclude && selector.exclude.includes(name)) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
function resolveModelContext(self, methodType) {
|
|
645
|
+
try {
|
|
646
|
+
switch (methodType) {
|
|
647
|
+
case "static": {
|
|
648
|
+
return {
|
|
649
|
+
modelName: self?.modelName,
|
|
650
|
+
collectionName: self?.collection?.collectionName || self?.modelName
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
case "instance": {
|
|
654
|
+
const ctor = self?.constructor;
|
|
655
|
+
return {
|
|
656
|
+
modelName: ctor?.modelName,
|
|
657
|
+
collectionName: ctor?.collection?.collectionName || ctor?.modelName
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
case "query": {
|
|
661
|
+
const model = self?.model;
|
|
662
|
+
return {
|
|
663
|
+
modelName: model?.modelName,
|
|
664
|
+
collectionName: model?.collection?.collectionName || model?.modelName
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
} catch {
|
|
669
|
+
}
|
|
670
|
+
return {};
|
|
671
|
+
}
|
|
672
|
+
function wrapCustomFunction(original, methodName, methodType) {
|
|
673
|
+
if (original[WRAPPED_METHOD_FLAG]) {
|
|
674
|
+
return original;
|
|
675
|
+
}
|
|
676
|
+
const wrapped = function instrumentedCustomFn(...args) {
|
|
677
|
+
const instance = resolveMongooseInstance(this, methodType);
|
|
678
|
+
const entry = instance ? INSTANCE_REGISTRY.get(instance) : void 0;
|
|
679
|
+
if (!entry || !entry.config.customMethods.enabled || !selectorAllows(
|
|
680
|
+
selectorFor(entry.config.customMethods, methodType),
|
|
681
|
+
methodName
|
|
682
|
+
)) {
|
|
683
|
+
return original.apply(this, args);
|
|
684
|
+
}
|
|
685
|
+
const { tracer, config } = entry;
|
|
686
|
+
const captureParameters = config.customMethods.captureParameters;
|
|
687
|
+
const { modelName, collectionName } = resolveModelContext(this, methodType);
|
|
688
|
+
const spanName = modelName ? `mongoose.${modelName}.${methodName}` : `mongoose.${methodType}.${methodName}`;
|
|
689
|
+
const span = tracer.startSpan(spanName, { kind: autotel.SpanKind.INTERNAL });
|
|
690
|
+
span.setAttribute(ATTR_DB_SYSTEM_NAME, DB_SYSTEM_NAME_VALUE_MONGODB);
|
|
691
|
+
span.setAttribute(ATTR_CODE_FUNCTION_NAME, methodName);
|
|
692
|
+
span.setAttribute(ATTR_MONGOOSE_METHOD_NAME, methodName);
|
|
693
|
+
span.setAttribute(ATTR_MONGOOSE_METHOD_TYPE, methodType);
|
|
694
|
+
if (modelName) {
|
|
695
|
+
span.setAttribute(ATTR_MONGOOSE_METHOD_MODEL, modelName);
|
|
696
|
+
}
|
|
697
|
+
if (collectionName && config.captureCollectionName) {
|
|
698
|
+
span.setAttribute(ATTR_DB_COLLECTION_NAME, collectionName);
|
|
699
|
+
}
|
|
700
|
+
if (config.dbName) {
|
|
701
|
+
span.setAttribute(ATTR_DB_NAMESPACE, config.dbName);
|
|
702
|
+
}
|
|
703
|
+
if (captureParameters) {
|
|
704
|
+
span.setAttribute(ATTR_MONGOOSE_METHOD_PARAMETER_COUNT, args.length);
|
|
705
|
+
try {
|
|
706
|
+
const params = captureParameters(args, { methodName, methodType });
|
|
707
|
+
if (params !== void 0) {
|
|
708
|
+
span.setAttribute(ATTR_MONGOOSE_METHOD_PARAMETERS, params);
|
|
709
|
+
}
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return traceHelpers.runWithSpan(span, () => {
|
|
714
|
+
try {
|
|
715
|
+
const result = original.apply(this, args);
|
|
716
|
+
if (methodType === "query") {
|
|
717
|
+
traceHelpers.finalizeSpan(span);
|
|
718
|
+
return result;
|
|
719
|
+
}
|
|
720
|
+
if (result && typeof result.exec === "function") {
|
|
721
|
+
const originalExec = result.exec.bind(result);
|
|
722
|
+
result.exec = function wrappedExec() {
|
|
723
|
+
try {
|
|
724
|
+
return Promise.resolve(originalExec()).then((value) => {
|
|
725
|
+
traceHelpers.finalizeSpan(span);
|
|
726
|
+
return value;
|
|
727
|
+
}).catch((error) => {
|
|
728
|
+
traceHelpers.finalizeSpan(
|
|
729
|
+
span,
|
|
730
|
+
error instanceof Error ? error : new Error(String(error))
|
|
731
|
+
);
|
|
732
|
+
throw error;
|
|
733
|
+
});
|
|
734
|
+
} catch (error) {
|
|
735
|
+
traceHelpers.finalizeSpan(
|
|
736
|
+
span,
|
|
737
|
+
error instanceof Error ? error : new Error(String(error))
|
|
738
|
+
);
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
return result;
|
|
743
|
+
}
|
|
744
|
+
if (result && typeof result.then === "function") {
|
|
745
|
+
return Promise.resolve(result).then((value) => {
|
|
746
|
+
traceHelpers.finalizeSpan(span);
|
|
747
|
+
return value;
|
|
748
|
+
}).catch((error) => {
|
|
749
|
+
traceHelpers.finalizeSpan(
|
|
750
|
+
span,
|
|
751
|
+
error instanceof Error ? error : new Error(String(error))
|
|
752
|
+
);
|
|
753
|
+
throw error;
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
traceHelpers.finalizeSpan(span);
|
|
757
|
+
return result;
|
|
758
|
+
} catch (error) {
|
|
759
|
+
traceHelpers.finalizeSpan(
|
|
760
|
+
span,
|
|
761
|
+
error instanceof Error ? error : new Error(String(error))
|
|
762
|
+
);
|
|
763
|
+
throw error;
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
};
|
|
767
|
+
try {
|
|
768
|
+
Object.defineProperty(wrapped, "name", {
|
|
769
|
+
value: original.name || methodName,
|
|
770
|
+
configurable: true
|
|
771
|
+
});
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
wrapped[WRAPPED_METHOD_FLAG] = true;
|
|
775
|
+
return wrapped;
|
|
776
|
+
}
|
|
777
|
+
var MONGOOSE_INTERNAL_FUNCTION_NAMES = /* @__PURE__ */ new Set([
|
|
778
|
+
"initializeTimestamps"
|
|
779
|
+
]);
|
|
780
|
+
function isMongooseInternalFunctionName(name) {
|
|
781
|
+
return name.startsWith("$") || MONGOOSE_INTERNAL_FUNCTION_NAMES.has(name);
|
|
782
|
+
}
|
|
783
|
+
function instrumentSchemaCustomFunctions(schema) {
|
|
784
|
+
if (!schema) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const wrapCollection = (collection, methodType) => {
|
|
788
|
+
if (!collection) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
for (const name of Object.keys(collection)) {
|
|
792
|
+
if (isMongooseInternalFunctionName(name)) {
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
const fn = collection[name];
|
|
796
|
+
if (typeof fn !== "function" || fn[WRAPPED_METHOD_FLAG]) {
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
collection[name] = wrapCustomFunction(fn, name, methodType);
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
wrapCollection(schema.statics, "static");
|
|
803
|
+
wrapCollection(schema.methods, "instance");
|
|
804
|
+
wrapCollection(schema.query, "query");
|
|
805
|
+
}
|
|
806
|
+
function patchModelFactory(m, config) {
|
|
807
|
+
if (!config.customMethods.enabled) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const patchHost = (host) => {
|
|
811
|
+
if (!host || typeof host.model !== "function" || host[MODEL_PATCHED_FLAG]) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
const originalModel = host.model;
|
|
815
|
+
host.model = function patchedModel(nameOrSchema, schema, ...rest) {
|
|
816
|
+
if (schema && typeof schema === "object") {
|
|
817
|
+
try {
|
|
818
|
+
instrumentSchemaCustomFunctions(schema);
|
|
819
|
+
} catch {
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return Reflect.apply(originalModel, this, [
|
|
823
|
+
nameOrSchema,
|
|
824
|
+
schema,
|
|
825
|
+
...rest
|
|
826
|
+
]);
|
|
827
|
+
};
|
|
828
|
+
host[MODEL_PATCHED_FLAG] = true;
|
|
829
|
+
};
|
|
830
|
+
patchHost(m);
|
|
831
|
+
if (m.Connection?.prototype) {
|
|
832
|
+
patchHost(m.Connection.prototype);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
502
835
|
function instrumentMongoose(mongoose, config) {
|
|
503
836
|
if (!mongoose?.Model) {
|
|
504
837
|
return mongoose;
|
|
@@ -515,8 +848,14 @@ function instrumentMongoose(mongoose, config) {
|
|
|
515
848
|
peerPort: config?.peerPort || 27017,
|
|
516
849
|
tracerName: config?.tracerName || DEFAULT_TRACER_NAME,
|
|
517
850
|
captureCollectionName: config?.captureCollectionName ?? true,
|
|
518
|
-
instrumentHooks: config?.instrumentHooks ?? false
|
|
851
|
+
instrumentHooks: config?.instrumentHooks ?? false,
|
|
852
|
+
dbStatementSerializer: resolvedSerializer === false ? false : resolvedSerializer ?? defaultSerializer,
|
|
853
|
+
statementRedactor: resolvedRedactor,
|
|
854
|
+
customMethods: resolveCustomMethods(config)
|
|
855
|
+
};
|
|
519
856
|
const tracer = autotel.otelTrace.getTracer(finalConfig.tracerName);
|
|
857
|
+
INSTANCE_REGISTRY.set(mongoose, { tracer, config: finalConfig });
|
|
858
|
+
patchModelFactory(m, finalConfig);
|
|
520
859
|
const captureStatement = createStatementCapture({
|
|
521
860
|
dbStatementSerializer: resolvedSerializer,
|
|
522
861
|
statementRedactor: resolvedRedactor
|