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/README.md +150 -0
- package/dist/index.cjs +596 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +594 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
- package/src/config-custom.integration.test.ts +91 -0
- package/src/config-disabled.integration.test.ts +79 -0
- package/src/constants.ts +11 -0
- package/src/index.ts +3 -0
- package/src/instrumentation.integration.test.ts +202 -0
- package/src/instrumentation.ts +880 -0
- package/src/statement.test.ts +78 -0
- package/src/statement.ts +58 -0
- package/src/test-support.ts +32 -0
- package/src/types.ts +74 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { defaultSerializer, createStatementCapture } from './statement';
|
|
3
|
+
import type { SerializerPayload } from './types';
|
|
4
|
+
|
|
5
|
+
describe('defaultSerializer', () => {
|
|
6
|
+
it('serializes a query condition payload to JSON', () => {
|
|
7
|
+
const payload: SerializerPayload = {
|
|
8
|
+
condition: { name: 'Alice' },
|
|
9
|
+
options: { lean: true },
|
|
10
|
+
};
|
|
11
|
+
const result = defaultSerializer('find', payload);
|
|
12
|
+
expect(result).toBe(JSON.stringify(payload));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('serializes an aggregate pipeline payload', () => {
|
|
16
|
+
const payload: SerializerPayload = {
|
|
17
|
+
aggregatePipeline: [{ $match: { status: 'active' } }],
|
|
18
|
+
};
|
|
19
|
+
const result = defaultSerializer('aggregate', payload);
|
|
20
|
+
expect(result).toBe(JSON.stringify(payload));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('createStatementCapture', () => {
|
|
25
|
+
it('uses default serializer and default redactor when no config provided', () => {
|
|
26
|
+
const capture = createStatementCapture({
|
|
27
|
+
dbStatementSerializer: undefined as any,
|
|
28
|
+
statementRedactor: 'default',
|
|
29
|
+
});
|
|
30
|
+
const result = capture('find', {
|
|
31
|
+
condition: { email: 'test@example.com' },
|
|
32
|
+
});
|
|
33
|
+
expect(result).toBeDefined();
|
|
34
|
+
// Email should be redacted by default preset
|
|
35
|
+
expect(result).not.toContain('test@example.com');
|
|
36
|
+
expect(result).toContain('[REDACTED]');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('returns undefined when dbStatementSerializer is false', () => {
|
|
40
|
+
const capture = createStatementCapture({
|
|
41
|
+
dbStatementSerializer: false,
|
|
42
|
+
statementRedactor: 'default',
|
|
43
|
+
});
|
|
44
|
+
const result = capture('find', { condition: { name: 'Alice' } });
|
|
45
|
+
expect(result).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('uses custom serializer when provided', () => {
|
|
49
|
+
const customSerializer = (op: string, _payload: SerializerPayload) =>
|
|
50
|
+
`custom:${op}`;
|
|
51
|
+
const capture = createStatementCapture({
|
|
52
|
+
dbStatementSerializer: customSerializer,
|
|
53
|
+
statementRedactor: false,
|
|
54
|
+
});
|
|
55
|
+
const result = capture('find', { condition: { name: 'Alice' } });
|
|
56
|
+
expect(result).toBe('custom:find');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('skips redaction when statementRedactor is false', () => {
|
|
60
|
+
const capture = createStatementCapture({
|
|
61
|
+
dbStatementSerializer: undefined as any,
|
|
62
|
+
statementRedactor: false,
|
|
63
|
+
});
|
|
64
|
+
const result = capture('find', {
|
|
65
|
+
condition: { email: 'test@example.com' },
|
|
66
|
+
});
|
|
67
|
+
expect(result).toContain('test@example.com');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('returns undefined when custom serializer returns undefined', () => {
|
|
71
|
+
const capture = createStatementCapture({
|
|
72
|
+
dbStatementSerializer: () => {},
|
|
73
|
+
statementRedactor: 'default',
|
|
74
|
+
});
|
|
75
|
+
const result = capture('find', { condition: {} });
|
|
76
|
+
expect(result).toBeUndefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
package/src/statement.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createStringRedactor, type StringRedactor } from 'autotel';
|
|
2
|
+
import type { SerializerPayload, InstrumentMongooseConfig } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Default serializer — JSON.stringify of the payload.
|
|
6
|
+
*/
|
|
7
|
+
export function defaultSerializer(
|
|
8
|
+
_operation: string,
|
|
9
|
+
payload: SerializerPayload,
|
|
10
|
+
): string {
|
|
11
|
+
return JSON.stringify(payload);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type StatementCaptureFn = (
|
|
15
|
+
operation: string,
|
|
16
|
+
payload: SerializerPayload,
|
|
17
|
+
) => string | undefined;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Composes the serializer and redactor into a single capture function.
|
|
21
|
+
* Returns undefined if statement capture is disabled.
|
|
22
|
+
*/
|
|
23
|
+
export function createStatementCapture(config: {
|
|
24
|
+
dbStatementSerializer: InstrumentMongooseConfig['dbStatementSerializer'];
|
|
25
|
+
statementRedactor: InstrumentMongooseConfig['statementRedactor'];
|
|
26
|
+
}): StatementCaptureFn {
|
|
27
|
+
// Statement capture disabled
|
|
28
|
+
if (config.dbStatementSerializer === false) {
|
|
29
|
+
return (): undefined => {
|
|
30
|
+
return;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const serializer =
|
|
35
|
+
typeof config.dbStatementSerializer === 'function'
|
|
36
|
+
? config.dbStatementSerializer
|
|
37
|
+
: defaultSerializer;
|
|
38
|
+
|
|
39
|
+
// Build redactor (or no-op)
|
|
40
|
+
let redact: StringRedactor | undefined;
|
|
41
|
+
if (
|
|
42
|
+
config.statementRedactor !== false &&
|
|
43
|
+
config.statementRedactor !== undefined
|
|
44
|
+
) {
|
|
45
|
+
redact = createStringRedactor(config.statementRedactor);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
operation: string,
|
|
50
|
+
payload: SerializerPayload,
|
|
51
|
+
): string | undefined => {
|
|
52
|
+
const raw = serializer(operation, payload);
|
|
53
|
+
if (raw === undefined) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return redact ? redact(raw) : raw;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
|
|
3
|
+
export async function canListenOnLoopback(): Promise<boolean> {
|
|
4
|
+
return await new Promise((resolve, reject) => {
|
|
5
|
+
const server = createServer();
|
|
6
|
+
|
|
7
|
+
server.once('error', (error) => {
|
|
8
|
+
if (
|
|
9
|
+
error &&
|
|
10
|
+
typeof error === 'object' &&
|
|
11
|
+
'code' in error &&
|
|
12
|
+
error.code === 'EPERM'
|
|
13
|
+
) {
|
|
14
|
+
resolve(false);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
reject(error);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
server.listen(0, '127.0.0.1', () => {
|
|
22
|
+
server.close((closeError) => {
|
|
23
|
+
if (closeError) {
|
|
24
|
+
reject(closeError);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resolve(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { AttributeRedactorPreset, AttributeRedactorConfig } from 'autotel';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Payload passed to the dbStatementSerializer.
|
|
5
|
+
* Shape matches @opentelemetry/instrumentation-mongodb for migration compatibility.
|
|
6
|
+
*/
|
|
7
|
+
export interface SerializerPayload {
|
|
8
|
+
condition?: Record<string, unknown>;
|
|
9
|
+
updates?: Record<string, unknown>;
|
|
10
|
+
options?: Record<string, unknown>;
|
|
11
|
+
fields?: Record<string, unknown>;
|
|
12
|
+
aggregatePipeline?: unknown[];
|
|
13
|
+
document?: unknown;
|
|
14
|
+
documents?: unknown[];
|
|
15
|
+
operations?: unknown[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Configuration for Mongoose instrumentation.
|
|
20
|
+
*/
|
|
21
|
+
export interface InstrumentMongooseConfig {
|
|
22
|
+
/** Database name for spans (sets db.namespace). */
|
|
23
|
+
dbName?: string;
|
|
24
|
+
|
|
25
|
+
/** MongoDB server hostname (sets server.address). */
|
|
26
|
+
peerName?: string;
|
|
27
|
+
|
|
28
|
+
/** MongoDB server port (sets server.port, default: 27017). */
|
|
29
|
+
peerPort?: number;
|
|
30
|
+
|
|
31
|
+
/** Custom tracer name (default: "autotel-mongoose"). */
|
|
32
|
+
tracerName?: string;
|
|
33
|
+
|
|
34
|
+
/** Capture collection names in spans (default: true). */
|
|
35
|
+
captureCollectionName?: boolean;
|
|
36
|
+
|
|
37
|
+
/** Instrument Schema hooks — pre/post save, validate, etc. (default: false). */
|
|
38
|
+
instrumentHooks?: boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Serializer for db.query.text attribute.
|
|
42
|
+
* Default: JSON.stringify of the payload.
|
|
43
|
+
* Pass false to disable statement capture entirely.
|
|
44
|
+
*/
|
|
45
|
+
dbStatementSerializer?:
|
|
46
|
+
| ((operation: string, payload: SerializerPayload) => string | undefined)
|
|
47
|
+
| false;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Redactor applied to serialized statements before setting db.query.text.
|
|
51
|
+
* Default: 'default' preset (emails, phones, SSNs, credit cards).
|
|
52
|
+
* Pass a preset name, custom config, or false to disable redaction.
|
|
53
|
+
*/
|
|
54
|
+
statementRedactor?: AttributeRedactorPreset | AttributeRedactorConfig | false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolved config with all defaults applied.
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export interface ResolvedConfig {
|
|
62
|
+
dbName: string;
|
|
63
|
+
peerName: string;
|
|
64
|
+
peerPort: number;
|
|
65
|
+
tracerName: string;
|
|
66
|
+
captureCollectionName: boolean;
|
|
67
|
+
instrumentHooks: boolean;
|
|
68
|
+
dbStatementSerializer:
|
|
69
|
+
| ((operation: string, payload: SerializerPayload) => string | undefined)
|
|
70
|
+
| false;
|
|
71
|
+
statementRedactor: AttributeRedactorPreset | AttributeRedactorConfig | false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const DEFAULT_TRACER_NAME = 'autotel-mongoose';
|