autotel 2.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/LICENSE +21 -0
- package/README.md +1946 -0
- package/dist/chunk-2LNRY4QK.js +273 -0
- package/dist/chunk-2LNRY4QK.js.map +1 -0
- package/dist/chunk-3HENGDW2.js +587 -0
- package/dist/chunk-3HENGDW2.js.map +1 -0
- package/dist/chunk-4OAT42CA.cjs +73 -0
- package/dist/chunk-4OAT42CA.cjs.map +1 -0
- package/dist/chunk-5GWX5LFW.js +70 -0
- package/dist/chunk-5GWX5LFW.js.map +1 -0
- package/dist/chunk-5R2M36QB.js +195 -0
- package/dist/chunk-5R2M36QB.js.map +1 -0
- package/dist/chunk-5ZN622AO.js +73 -0
- package/dist/chunk-5ZN622AO.js.map +1 -0
- package/dist/chunk-77MSMAUQ.cjs +498 -0
- package/dist/chunk-77MSMAUQ.cjs.map +1 -0
- package/dist/chunk-ABPEQ6RK.cjs +596 -0
- package/dist/chunk-ABPEQ6RK.cjs.map +1 -0
- package/dist/chunk-BWYGJKRB.js +95 -0
- package/dist/chunk-BWYGJKRB.js.map +1 -0
- package/dist/chunk-BZHG5IZ4.js +73 -0
- package/dist/chunk-BZHG5IZ4.js.map +1 -0
- package/dist/chunk-G7VZBCD6.cjs +35 -0
- package/dist/chunk-G7VZBCD6.cjs.map +1 -0
- package/dist/chunk-GVLK7YUU.cjs +30 -0
- package/dist/chunk-GVLK7YUU.cjs.map +1 -0
- package/dist/chunk-HCCXC7XG.js +205 -0
- package/dist/chunk-HCCXC7XG.js.map +1 -0
- package/dist/chunk-HE6T6FIX.cjs +203 -0
- package/dist/chunk-HE6T6FIX.cjs.map +1 -0
- package/dist/chunk-KIXWPOCO.cjs +100 -0
- package/dist/chunk-KIXWPOCO.cjs.map +1 -0
- package/dist/chunk-KVGNW3FC.js +87 -0
- package/dist/chunk-KVGNW3FC.js.map +1 -0
- package/dist/chunk-LITNXTTT.js +3 -0
- package/dist/chunk-LITNXTTT.js.map +1 -0
- package/dist/chunk-M4ANN7RL.js +114 -0
- package/dist/chunk-M4ANN7RL.js.map +1 -0
- package/dist/chunk-NC52UBR2.cjs +32 -0
- package/dist/chunk-NC52UBR2.cjs.map +1 -0
- package/dist/chunk-NHCNRQD3.cjs +212 -0
- package/dist/chunk-NHCNRQD3.cjs.map +1 -0
- package/dist/chunk-NZ72VDNY.cjs +4 -0
- package/dist/chunk-NZ72VDNY.cjs.map +1 -0
- package/dist/chunk-P6JUDYNO.js +57 -0
- package/dist/chunk-P6JUDYNO.js.map +1 -0
- package/dist/chunk-RJYY7BWX.js +1349 -0
- package/dist/chunk-RJYY7BWX.js.map +1 -0
- package/dist/chunk-TRI4V5BF.cjs +126 -0
- package/dist/chunk-TRI4V5BF.cjs.map +1 -0
- package/dist/chunk-UL33I6IS.js +139 -0
- package/dist/chunk-UL33I6IS.js.map +1 -0
- package/dist/chunk-URRW6M2C.cjs +61 -0
- package/dist/chunk-URRW6M2C.cjs.map +1 -0
- package/dist/chunk-UY3UYPBZ.cjs +77 -0
- package/dist/chunk-UY3UYPBZ.cjs.map +1 -0
- package/dist/chunk-W3253FGB.cjs +277 -0
- package/dist/chunk-W3253FGB.cjs.map +1 -0
- package/dist/chunk-W7LHZVQF.js +26 -0
- package/dist/chunk-W7LHZVQF.js.map +1 -0
- package/dist/chunk-WBWNM6LB.cjs +1360 -0
- package/dist/chunk-WBWNM6LB.cjs.map +1 -0
- package/dist/chunk-WFJ7L2RV.js +494 -0
- package/dist/chunk-WFJ7L2RV.js.map +1 -0
- package/dist/chunk-X4RMFFMR.js +28 -0
- package/dist/chunk-X4RMFFMR.js.map +1 -0
- package/dist/chunk-Y4Y2S7BM.cjs +92 -0
- package/dist/chunk-Y4Y2S7BM.cjs.map +1 -0
- package/dist/chunk-YLPNXZFI.cjs +143 -0
- package/dist/chunk-YLPNXZFI.cjs.map +1 -0
- package/dist/chunk-YTXEZ4SD.cjs +77 -0
- package/dist/chunk-YTXEZ4SD.cjs.map +1 -0
- package/dist/chunk-Z6ZWNWWR.js +30 -0
- package/dist/chunk-Z6ZWNWWR.js.map +1 -0
- package/dist/config.cjs +26 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +75 -0
- package/dist/config.d.ts +75 -0
- package/dist/config.js +5 -0
- package/dist/config.js.map +1 -0
- package/dist/db.cjs +233 -0
- package/dist/db.cjs.map +1 -0
- package/dist/db.d.cts +123 -0
- package/dist/db.d.ts +123 -0
- package/dist/db.js +228 -0
- package/dist/db.js.map +1 -0
- package/dist/decorators.cjs +67 -0
- package/dist/decorators.cjs.map +1 -0
- package/dist/decorators.d.cts +91 -0
- package/dist/decorators.d.ts +91 -0
- package/dist/decorators.js +65 -0
- package/dist/decorators.js.map +1 -0
- package/dist/event-subscriber.cjs +6 -0
- package/dist/event-subscriber.cjs.map +1 -0
- package/dist/event-subscriber.d.cts +116 -0
- package/dist/event-subscriber.d.ts +116 -0
- package/dist/event-subscriber.js +3 -0
- package/dist/event-subscriber.js.map +1 -0
- package/dist/event-testing.cjs +21 -0
- package/dist/event-testing.cjs.map +1 -0
- package/dist/event-testing.d.cts +110 -0
- package/dist/event-testing.d.ts +110 -0
- package/dist/event-testing.js +4 -0
- package/dist/event-testing.js.map +1 -0
- package/dist/event.cjs +30 -0
- package/dist/event.cjs.map +1 -0
- package/dist/event.d.cts +282 -0
- package/dist/event.d.ts +282 -0
- package/dist/event.js +13 -0
- package/dist/event.js.map +1 -0
- package/dist/exporters.cjs +17 -0
- package/dist/exporters.cjs.map +1 -0
- package/dist/exporters.d.cts +1 -0
- package/dist/exporters.d.ts +1 -0
- package/dist/exporters.js +4 -0
- package/dist/exporters.js.map +1 -0
- package/dist/functional.cjs +46 -0
- package/dist/functional.cjs.map +1 -0
- package/dist/functional.d.cts +478 -0
- package/dist/functional.d.ts +478 -0
- package/dist/functional.js +13 -0
- package/dist/functional.js.map +1 -0
- package/dist/http.cjs +189 -0
- package/dist/http.cjs.map +1 -0
- package/dist/http.d.cts +169 -0
- package/dist/http.d.ts +169 -0
- package/dist/http.js +184 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +333 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +758 -0
- package/dist/index.d.ts +758 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation.cjs +182 -0
- package/dist/instrumentation.cjs.map +1 -0
- package/dist/instrumentation.d.cts +49 -0
- package/dist/instrumentation.d.ts +49 -0
- package/dist/instrumentation.js +179 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/logger.cjs +19 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.cts +146 -0
- package/dist/logger.d.ts +146 -0
- package/dist/logger.js +6 -0
- package/dist/logger.js.map +1 -0
- package/dist/metric-helpers.cjs +31 -0
- package/dist/metric-helpers.cjs.map +1 -0
- package/dist/metric-helpers.d.cts +13 -0
- package/dist/metric-helpers.d.ts +13 -0
- package/dist/metric-helpers.js +6 -0
- package/dist/metric-helpers.js.map +1 -0
- package/dist/metric-testing.cjs +21 -0
- package/dist/metric-testing.cjs.map +1 -0
- package/dist/metric-testing.d.cts +110 -0
- package/dist/metric-testing.d.ts +110 -0
- package/dist/metric-testing.js +4 -0
- package/dist/metric-testing.js.map +1 -0
- package/dist/metric.cjs +26 -0
- package/dist/metric.cjs.map +1 -0
- package/dist/metric.d.cts +240 -0
- package/dist/metric.d.ts +240 -0
- package/dist/metric.js +9 -0
- package/dist/metric.js.map +1 -0
- package/dist/processors.cjs +17 -0
- package/dist/processors.cjs.map +1 -0
- package/dist/processors.d.cts +1 -0
- package/dist/processors.d.ts +1 -0
- package/dist/processors.js +4 -0
- package/dist/processors.js.map +1 -0
- package/dist/sampling.cjs +40 -0
- package/dist/sampling.cjs.map +1 -0
- package/dist/sampling.d.cts +260 -0
- package/dist/sampling.d.ts +260 -0
- package/dist/sampling.js +7 -0
- package/dist/sampling.js.map +1 -0
- package/dist/semantic-helpers.cjs +35 -0
- package/dist/semantic-helpers.cjs.map +1 -0
- package/dist/semantic-helpers.d.cts +442 -0
- package/dist/semantic-helpers.d.ts +442 -0
- package/dist/semantic-helpers.js +14 -0
- package/dist/semantic-helpers.js.map +1 -0
- package/dist/tail-sampling-processor.cjs +13 -0
- package/dist/tail-sampling-processor.cjs.map +1 -0
- package/dist/tail-sampling-processor.d.cts +27 -0
- package/dist/tail-sampling-processor.d.ts +27 -0
- package/dist/tail-sampling-processor.js +4 -0
- package/dist/tail-sampling-processor.js.map +1 -0
- package/dist/testing.cjs +286 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +291 -0
- package/dist/testing.d.ts +291 -0
- package/dist/testing.js +263 -0
- package/dist/testing.js.map +1 -0
- package/dist/trace-context-DRZdUvVY.d.cts +181 -0
- package/dist/trace-context-DRZdUvVY.d.ts +181 -0
- package/dist/trace-helpers.cjs +54 -0
- package/dist/trace-helpers.cjs.map +1 -0
- package/dist/trace-helpers.d.cts +524 -0
- package/dist/trace-helpers.d.ts +524 -0
- package/dist/trace-helpers.js +5 -0
- package/dist/trace-helpers.js.map +1 -0
- package/dist/tracer-provider.cjs +21 -0
- package/dist/tracer-provider.cjs.map +1 -0
- package/dist/tracer-provider.d.cts +169 -0
- package/dist/tracer-provider.d.ts +169 -0
- package/dist/tracer-provider.js +4 -0
- package/dist/tracer-provider.js.map +1 -0
- package/package.json +280 -0
- package/src/baggage-span-processor.test.ts +202 -0
- package/src/baggage-span-processor.ts +98 -0
- package/src/circuit-breaker.test.ts +341 -0
- package/src/circuit-breaker.ts +184 -0
- package/src/config.test.ts +94 -0
- package/src/config.ts +169 -0
- package/src/db.test.ts +252 -0
- package/src/db.ts +447 -0
- package/src/decorators.test.ts +203 -0
- package/src/decorators.ts +188 -0
- package/src/env-config.test.ts +246 -0
- package/src/env-config.ts +158 -0
- package/src/event-queue.test.ts +222 -0
- package/src/event-queue.ts +203 -0
- package/src/event-subscriber.ts +136 -0
- package/src/event-testing.ts +197 -0
- package/src/event.test.ts +718 -0
- package/src/event.ts +556 -0
- package/src/exporters.ts +96 -0
- package/src/functional.test.ts +1059 -0
- package/src/functional.ts +2295 -0
- package/src/http.test.ts +487 -0
- package/src/http.ts +424 -0
- package/src/index.ts +158 -0
- package/src/init.customization.test.ts +210 -0
- package/src/init.integrations.test.ts +366 -0
- package/src/init.openllmetry.test.ts +282 -0
- package/src/init.protocol.test.ts +215 -0
- package/src/init.ts +1426 -0
- package/src/instrumentation.test.ts +108 -0
- package/src/instrumentation.ts +308 -0
- package/src/logger.test.ts +117 -0
- package/src/logger.ts +246 -0
- package/src/metric-helpers.ts +47 -0
- package/src/metric-testing.ts +197 -0
- package/src/metric.ts +434 -0
- package/src/metrics.test.ts +205 -0
- package/src/operation-context.ts +93 -0
- package/src/processors.ts +106 -0
- package/src/rate-limiter.test.ts +199 -0
- package/src/rate-limiter.ts +98 -0
- package/src/sampling.test.ts +513 -0
- package/src/sampling.ts +428 -0
- package/src/semantic-helpers.test.ts +311 -0
- package/src/semantic-helpers.ts +584 -0
- package/src/shutdown.test.ts +311 -0
- package/src/shutdown.ts +222 -0
- package/src/stub.integration.test.ts +361 -0
- package/src/tail-sampling-processor.test.ts +226 -0
- package/src/tail-sampling-processor.ts +51 -0
- package/src/testing.ts +670 -0
- package/src/trace-context.ts +470 -0
- package/src/trace-helpers.new.test.ts +278 -0
- package/src/trace-helpers.test.ts +242 -0
- package/src/trace-helpers.ts +690 -0
- package/src/tracer-provider.test.ts +183 -0
- package/src/tracer-provider.ts +266 -0
- package/src/track.test.ts +153 -0
- package/src/track.ts +120 -0
- package/src/validation.test.ts +306 -0
- package/src/validation.ts +239 -0
- package/src/variable-name-inference.test.ts +178 -0
- package/src/variable-name-inference.ts +242 -0
package/src/logger.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger types and utilities for autotel
|
|
3
|
+
*
|
|
4
|
+
* **Recommended Approach:** Bring your own logger (Pino, Winston, Bunyan, etc.)
|
|
5
|
+
*
|
|
6
|
+
* Simply create your logger instance and pass it to `init()`.
|
|
7
|
+
* Autotel automatically instruments Pino and Winston to:
|
|
8
|
+
* - Inject trace context (traceId, spanId) into log records
|
|
9
|
+
* - Record errors in the active span
|
|
10
|
+
* - Bridge logs to OpenTelemetry Logs API for OTLP export
|
|
11
|
+
*
|
|
12
|
+
* @example Using Pino (recommended, auto-instrumented)
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import pino from 'pino'; // npm install pino
|
|
15
|
+
* import { init } from 'autotel';
|
|
16
|
+
*
|
|
17
|
+
* const logger = pino({ level: 'info' });
|
|
18
|
+
* init({ service: 'my-app', logger });
|
|
19
|
+
*
|
|
20
|
+
* // Logs automatically include traceId/spanId and export via OTLP!
|
|
21
|
+
* logger.info('User created', { userId: '123' });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example Using Winston (auto-instrumented)
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import winston from 'winston'; // npm install winston
|
|
27
|
+
* import { init } from 'autotel';
|
|
28
|
+
*
|
|
29
|
+
* const logger = winston.createLogger({
|
|
30
|
+
* level: 'info',
|
|
31
|
+
* format: winston.format.json(),
|
|
32
|
+
* transports: [new winston.transports.Console()]
|
|
33
|
+
* });
|
|
34
|
+
* init({ service: 'my-app', logger });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example Using Bunyan (manual instrumentation)
|
|
38
|
+
* ```typescript
|
|
39
|
+
* import bunyan from 'bunyan'; // npm install bunyan @opentelemetry/instrumentation-bunyan
|
|
40
|
+
* import { init } from 'autotel';
|
|
41
|
+
* import { BunyanInstrumentation } from '@opentelemetry/instrumentation-bunyan';
|
|
42
|
+
*
|
|
43
|
+
* const logger = bunyan.createLogger({ name: 'my-app' });
|
|
44
|
+
* init({
|
|
45
|
+
* service: 'my-app',
|
|
46
|
+
* logger,
|
|
47
|
+
* instrumentations: [new BunyanInstrumentation()]
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @example Custom logger (any logger with 4 methods)
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const logger = {
|
|
54
|
+
* info: (msg, extra) => console.log(msg, extra),
|
|
55
|
+
* warn: (msg, extra) => console.warn(msg, extra),
|
|
56
|
+
* error: (msg, err, extra) => console.error(msg, err, extra),
|
|
57
|
+
* debug: (msg, extra) => console.debug(msg, extra),
|
|
58
|
+
* };
|
|
59
|
+
* init({ service: 'my-app', logger });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
64
|
+
import { getConfig } from './config';
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Logger Types
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Log level constants
|
|
72
|
+
*/
|
|
73
|
+
export const LOG_LEVEL = {
|
|
74
|
+
DEBUG: 'debug',
|
|
75
|
+
INFO: 'info',
|
|
76
|
+
WARN: 'warn',
|
|
77
|
+
ERROR: 'error',
|
|
78
|
+
} as const;
|
|
79
|
+
|
|
80
|
+
export type LogLevel = (typeof LOG_LEVEL)[keyof typeof LOG_LEVEL];
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Logger configuration (for reference - not needed with BYOL approach)
|
|
84
|
+
*/
|
|
85
|
+
export interface LoggerConfig {
|
|
86
|
+
service: string;
|
|
87
|
+
level?: LogLevel;
|
|
88
|
+
pretty?: boolean;
|
|
89
|
+
redact?: string[] | false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Simple logger interface - minimal contract for any logger
|
|
94
|
+
*
|
|
95
|
+
* Bring your own Pino, Winston, or any logger with these 4 methods.
|
|
96
|
+
* Autotel automatically instruments Pino and Winston loggers to:
|
|
97
|
+
* - Inject trace context (traceId, spanId) into log records
|
|
98
|
+
* - Record errors in the active span
|
|
99
|
+
* - Bridge logs to OpenTelemetry Logs API for OTLP export
|
|
100
|
+
*
|
|
101
|
+
* @example Using Pino
|
|
102
|
+
* ```typescript
|
|
103
|
+
* import pino from 'pino';
|
|
104
|
+
* const logger = pino({ level: 'info' });
|
|
105
|
+
* init({ service: 'my-app', logger });
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @example Using Winston
|
|
109
|
+
* ```typescript
|
|
110
|
+
* import winston from 'winston';
|
|
111
|
+
* const logger = winston.createLogger({ level: 'info' });
|
|
112
|
+
* init({ service: 'my-app', logger });
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export interface Logger {
|
|
116
|
+
info(message: string, extra?: Record<string, unknown>): void;
|
|
117
|
+
warn(message: string, extra?: Record<string, unknown>): void;
|
|
118
|
+
error(message: string, error?: Error, extra?: Record<string, unknown>): void;
|
|
119
|
+
debug(message: string, extra?: Record<string, unknown>): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Alias for Logger interface (backwards compatibility)
|
|
124
|
+
* @deprecated Use Logger instead
|
|
125
|
+
*/
|
|
126
|
+
export type ILogger = Logger;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Pino logger type - re-exported for convenience
|
|
130
|
+
*
|
|
131
|
+
* Note: This is a type-only export. To use Pino, install it as a peer dependency:
|
|
132
|
+
* `npm install pino`
|
|
133
|
+
*/
|
|
134
|
+
export type { Logger as PinoLogger } from 'pino';
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// LoggedOperation Decorator
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
export interface LoggedOperationOptions {
|
|
141
|
+
/** Operation name for tracing (e.g., 'user.createUser') */
|
|
142
|
+
operationName: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* TS5+ Standard Decorator for logging and tracing operations
|
|
147
|
+
* Uses TC39 Stage 3 decorator syntax
|
|
148
|
+
*
|
|
149
|
+
* This is the traditional per-method decorator approach.
|
|
150
|
+
* For zero-boilerplate solution, see @Instrumented class decorator.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* // Simple usage
|
|
154
|
+
* class OrderService {
|
|
155
|
+
* constructor(private readonly deps: { log: Logger }) {}
|
|
156
|
+
*
|
|
157
|
+
* @LoggedOperation('order.create')
|
|
158
|
+
* async createOrder(data: CreateOrderData) {
|
|
159
|
+
* this.deps.logger.info('Creating order', data)
|
|
160
|
+
* }
|
|
161
|
+
* }
|
|
162
|
+
*
|
|
163
|
+
* // Advanced usage (future-proof for options)
|
|
164
|
+
* @LoggedOperation({ operationName: 'order.create' })
|
|
165
|
+
* async createOrder(data: CreateOrderData) { }
|
|
166
|
+
*/
|
|
167
|
+
export function LoggedOperation(
|
|
168
|
+
operationNameOrOptions: string | LoggedOperationOptions,
|
|
169
|
+
) {
|
|
170
|
+
const operationName =
|
|
171
|
+
typeof operationNameOrOptions === 'string'
|
|
172
|
+
? operationNameOrOptions
|
|
173
|
+
: operationNameOrOptions.operationName;
|
|
174
|
+
|
|
175
|
+
return function <This, Args extends unknown[], Return>(
|
|
176
|
+
originalMethod: (this: This, ...args: Args) => Promise<Return>,
|
|
177
|
+
context: ClassMethodDecoratorContext<
|
|
178
|
+
This,
|
|
179
|
+
(this: This, ...args: Args) => Promise<Return>
|
|
180
|
+
>,
|
|
181
|
+
) {
|
|
182
|
+
const methodName = String(context.name);
|
|
183
|
+
|
|
184
|
+
return async function (this: This, ...args: Args): Promise<Return> {
|
|
185
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
186
|
+
const log = (this as any).deps?.log;
|
|
187
|
+
const startTime = performance.now();
|
|
188
|
+
|
|
189
|
+
const config = getConfig();
|
|
190
|
+
const tracer = config.tracer;
|
|
191
|
+
|
|
192
|
+
return tracer.startActiveSpan(operationName, async (span) => {
|
|
193
|
+
try {
|
|
194
|
+
log?.info('Operation started', {
|
|
195
|
+
operation: operationName,
|
|
196
|
+
method: methodName,
|
|
197
|
+
args,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const result = await originalMethod.apply(this, args);
|
|
201
|
+
|
|
202
|
+
const duration = performance.now() - startTime;
|
|
203
|
+
log?.info('Operation completed', {
|
|
204
|
+
operation: operationName,
|
|
205
|
+
method: methodName,
|
|
206
|
+
duration,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
210
|
+
span.setAttributes({
|
|
211
|
+
'operation.name': operationName,
|
|
212
|
+
'operation.method': methodName,
|
|
213
|
+
'operation.duration': duration,
|
|
214
|
+
'operation.success': true,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const duration = performance.now() - startTime;
|
|
220
|
+
log?.error(
|
|
221
|
+
'Operation failed',
|
|
222
|
+
error instanceof Error ? error : undefined,
|
|
223
|
+
{ operation: operationName, method: methodName, duration },
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
span.setStatus({
|
|
227
|
+
code: SpanStatusCode.ERROR,
|
|
228
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
229
|
+
});
|
|
230
|
+
span.setAttributes({
|
|
231
|
+
'operation.name': operationName,
|
|
232
|
+
'operation.method': methodName,
|
|
233
|
+
'operation.duration': duration,
|
|
234
|
+
'operation.success': false,
|
|
235
|
+
'error.type':
|
|
236
|
+
error instanceof Error ? error.constructor.name : 'Unknown',
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
throw error;
|
|
240
|
+
} finally {
|
|
241
|
+
span.end();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Counter,
|
|
3
|
+
Histogram,
|
|
4
|
+
Meter,
|
|
5
|
+
ObservableGauge,
|
|
6
|
+
UpDownCounter,
|
|
7
|
+
} from '@opentelemetry/api';
|
|
8
|
+
import { getConfig } from './config';
|
|
9
|
+
|
|
10
|
+
function getActiveMeter(): Meter {
|
|
11
|
+
const config = getConfig();
|
|
12
|
+
return config.meter;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getMeter(): Meter {
|
|
16
|
+
return getActiveMeter();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type CounterOptions = Parameters<Meter['createCounter']>[1];
|
|
20
|
+
type HistogramOptions = Parameters<Meter['createHistogram']>[1];
|
|
21
|
+
type UpDownCounterOptions = Parameters<Meter['createUpDownCounter']>[1];
|
|
22
|
+
type ObservableGaugeOptions = Parameters<Meter['createObservableGauge']>[1];
|
|
23
|
+
|
|
24
|
+
export function createCounter(name: string, options?: CounterOptions): Counter {
|
|
25
|
+
return getActiveMeter().createCounter(name, options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createHistogram(
|
|
29
|
+
name: string,
|
|
30
|
+
options?: HistogramOptions,
|
|
31
|
+
): Histogram {
|
|
32
|
+
return getActiveMeter().createHistogram(name, options);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createUpDownCounter(
|
|
36
|
+
name: string,
|
|
37
|
+
options?: UpDownCounterOptions,
|
|
38
|
+
): UpDownCounter {
|
|
39
|
+
return getActiveMeter().createUpDownCounter(name, options);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createObservableGauge(
|
|
43
|
+
name: string,
|
|
44
|
+
options?: ObservableGaugeOptions,
|
|
45
|
+
): ObservableGauge {
|
|
46
|
+
return getActiveMeter().createObservableGauge(name, options);
|
|
47
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testing utilities for Metrics
|
|
3
|
+
*
|
|
4
|
+
* Provides in-memory collection of metrics for testing purposes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
EventAttributes,
|
|
9
|
+
FunnelStatus,
|
|
10
|
+
OutcomeStatus,
|
|
11
|
+
} from './event-subscriber';
|
|
12
|
+
|
|
13
|
+
export interface MetricsEvent {
|
|
14
|
+
event: string;
|
|
15
|
+
attributes?: EventAttributes;
|
|
16
|
+
service: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MetricsFunnelStep {
|
|
21
|
+
funnel: string;
|
|
22
|
+
status: FunnelStatus;
|
|
23
|
+
attributes?: EventAttributes;
|
|
24
|
+
service: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MetricsOutcome {
|
|
29
|
+
operation: string;
|
|
30
|
+
status: OutcomeStatus;
|
|
31
|
+
attributes?: EventAttributes;
|
|
32
|
+
service: string;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface MetricsValue {
|
|
37
|
+
metric: string;
|
|
38
|
+
value: number;
|
|
39
|
+
attributes?: EventAttributes;
|
|
40
|
+
service: string;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* In-memory metrics collector for testing
|
|
46
|
+
*/
|
|
47
|
+
export interface MetricsCollector {
|
|
48
|
+
/** Get all collected events */
|
|
49
|
+
getEvents(): MetricsEvent[];
|
|
50
|
+
/** Get all collected funnel steps */
|
|
51
|
+
getFunnelSteps(): MetricsFunnelStep[];
|
|
52
|
+
/** Get all collected outcomes */
|
|
53
|
+
getOutcomes(): MetricsOutcome[];
|
|
54
|
+
/** Get all collected values */
|
|
55
|
+
getValues(): MetricsValue[];
|
|
56
|
+
/** Clear all collected metrics */
|
|
57
|
+
clear(): void;
|
|
58
|
+
/** Record an event (internal use) */
|
|
59
|
+
recordEvent(event: MetricsEvent): void;
|
|
60
|
+
/** Record a funnel step (internal use) */
|
|
61
|
+
recordFunnelStep(step: MetricsFunnelStep): void;
|
|
62
|
+
/** Record an outcome (internal use) */
|
|
63
|
+
recordOutcome(outcome: MetricsOutcome): void;
|
|
64
|
+
/** Record a value (internal use) */
|
|
65
|
+
recordValue(value: MetricsValue): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Create an in-memory metrics collector for testing
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const collector = createMetricsCollector()
|
|
74
|
+
*
|
|
75
|
+
* const metrics = new Metric('test-service', { collector })
|
|
76
|
+
* metrics.trackEvent('order.completed', { orderId: '123' })
|
|
77
|
+
*
|
|
78
|
+
* const event =collector.getEvents()
|
|
79
|
+
* expect(events).toHaveLength(1)
|
|
80
|
+
* expect(events[0].event).toBe('order.completed')
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function createMetricsCollector(): MetricsCollector {
|
|
84
|
+
const events: MetricsEvent[] = [];
|
|
85
|
+
const funnelSteps: MetricsFunnelStep[] = [];
|
|
86
|
+
const outcomes: MetricsOutcome[] = [];
|
|
87
|
+
const values: MetricsValue[] = [];
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
getEvents(): MetricsEvent[] {
|
|
91
|
+
return [...events];
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
getFunnelSteps(): MetricsFunnelStep[] {
|
|
95
|
+
return [...funnelSteps];
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
getOutcomes(): MetricsOutcome[] {
|
|
99
|
+
return [...outcomes];
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
getValues(): MetricsValue[] {
|
|
103
|
+
return [...values];
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
clear(): void {
|
|
107
|
+
events.length = 0;
|
|
108
|
+
funnelSteps.length = 0;
|
|
109
|
+
outcomes.length = 0;
|
|
110
|
+
values.length = 0;
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
recordEvent(event: MetricsEvent): void {
|
|
114
|
+
events.push(event);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
recordFunnelStep(step: MetricsFunnelStep): void {
|
|
118
|
+
funnelSteps.push(step);
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
recordOutcome(outcome: MetricsOutcome): void {
|
|
122
|
+
outcomes.push(outcome);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
recordValue(value: MetricsValue): void {
|
|
126
|
+
values.push(value);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Assert that a metric event was tracked
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* assertEventTracked({
|
|
137
|
+
* collector,
|
|
138
|
+
* eventName: 'order.completed',
|
|
139
|
+
* attributes: { orderId: '123' }
|
|
140
|
+
* })
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function assertEventTracked(options: {
|
|
144
|
+
collector: MetricsCollector;
|
|
145
|
+
eventName: string;
|
|
146
|
+
attributes?: Record<string, unknown>;
|
|
147
|
+
}): void {
|
|
148
|
+
const events = options.collector.getEvents();
|
|
149
|
+
const matching = events.filter((e) => e.event === options.eventName);
|
|
150
|
+
|
|
151
|
+
if (matching.length === 0) {
|
|
152
|
+
throw new Error(`No events found with name: ${options.eventName}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (options.attributes) {
|
|
156
|
+
const matchingWithAttrs = matching.filter((e) =>
|
|
157
|
+
Object.entries(options.attributes!).every(
|
|
158
|
+
([key, value]) => e.attributes && e.attributes[key] === value,
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (matchingWithAttrs.length === 0) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Event ${options.eventName} found but attributes don't match: ${JSON.stringify(options.attributes)}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Assert that an outcome was tracked
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* assertOutcomeTracked({
|
|
176
|
+
* collector,
|
|
177
|
+
* operation: 'payment.process',
|
|
178
|
+
* status: 'success'
|
|
179
|
+
* })
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export function assertOutcomeTracked(options: {
|
|
183
|
+
collector: MetricsCollector;
|
|
184
|
+
operation: string;
|
|
185
|
+
status: 'success' | 'failure' | 'partial';
|
|
186
|
+
}): void {
|
|
187
|
+
const outcomes = options.collector.getOutcomes();
|
|
188
|
+
const matching = outcomes.filter(
|
|
189
|
+
(o) => o.operation === options.operation && o.status === options.status,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (matching.length === 0) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`No outcomes found with operation: ${options.operation} and status: ${options.status}`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|