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.
Files changed (272) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1946 -0
  3. package/dist/chunk-2LNRY4QK.js +273 -0
  4. package/dist/chunk-2LNRY4QK.js.map +1 -0
  5. package/dist/chunk-3HENGDW2.js +587 -0
  6. package/dist/chunk-3HENGDW2.js.map +1 -0
  7. package/dist/chunk-4OAT42CA.cjs +73 -0
  8. package/dist/chunk-4OAT42CA.cjs.map +1 -0
  9. package/dist/chunk-5GWX5LFW.js +70 -0
  10. package/dist/chunk-5GWX5LFW.js.map +1 -0
  11. package/dist/chunk-5R2M36QB.js +195 -0
  12. package/dist/chunk-5R2M36QB.js.map +1 -0
  13. package/dist/chunk-5ZN622AO.js +73 -0
  14. package/dist/chunk-5ZN622AO.js.map +1 -0
  15. package/dist/chunk-77MSMAUQ.cjs +498 -0
  16. package/dist/chunk-77MSMAUQ.cjs.map +1 -0
  17. package/dist/chunk-ABPEQ6RK.cjs +596 -0
  18. package/dist/chunk-ABPEQ6RK.cjs.map +1 -0
  19. package/dist/chunk-BWYGJKRB.js +95 -0
  20. package/dist/chunk-BWYGJKRB.js.map +1 -0
  21. package/dist/chunk-BZHG5IZ4.js +73 -0
  22. package/dist/chunk-BZHG5IZ4.js.map +1 -0
  23. package/dist/chunk-G7VZBCD6.cjs +35 -0
  24. package/dist/chunk-G7VZBCD6.cjs.map +1 -0
  25. package/dist/chunk-GVLK7YUU.cjs +30 -0
  26. package/dist/chunk-GVLK7YUU.cjs.map +1 -0
  27. package/dist/chunk-HCCXC7XG.js +205 -0
  28. package/dist/chunk-HCCXC7XG.js.map +1 -0
  29. package/dist/chunk-HE6T6FIX.cjs +203 -0
  30. package/dist/chunk-HE6T6FIX.cjs.map +1 -0
  31. package/dist/chunk-KIXWPOCO.cjs +100 -0
  32. package/dist/chunk-KIXWPOCO.cjs.map +1 -0
  33. package/dist/chunk-KVGNW3FC.js +87 -0
  34. package/dist/chunk-KVGNW3FC.js.map +1 -0
  35. package/dist/chunk-LITNXTTT.js +3 -0
  36. package/dist/chunk-LITNXTTT.js.map +1 -0
  37. package/dist/chunk-M4ANN7RL.js +114 -0
  38. package/dist/chunk-M4ANN7RL.js.map +1 -0
  39. package/dist/chunk-NC52UBR2.cjs +32 -0
  40. package/dist/chunk-NC52UBR2.cjs.map +1 -0
  41. package/dist/chunk-NHCNRQD3.cjs +212 -0
  42. package/dist/chunk-NHCNRQD3.cjs.map +1 -0
  43. package/dist/chunk-NZ72VDNY.cjs +4 -0
  44. package/dist/chunk-NZ72VDNY.cjs.map +1 -0
  45. package/dist/chunk-P6JUDYNO.js +57 -0
  46. package/dist/chunk-P6JUDYNO.js.map +1 -0
  47. package/dist/chunk-RJYY7BWX.js +1349 -0
  48. package/dist/chunk-RJYY7BWX.js.map +1 -0
  49. package/dist/chunk-TRI4V5BF.cjs +126 -0
  50. package/dist/chunk-TRI4V5BF.cjs.map +1 -0
  51. package/dist/chunk-UL33I6IS.js +139 -0
  52. package/dist/chunk-UL33I6IS.js.map +1 -0
  53. package/dist/chunk-URRW6M2C.cjs +61 -0
  54. package/dist/chunk-URRW6M2C.cjs.map +1 -0
  55. package/dist/chunk-UY3UYPBZ.cjs +77 -0
  56. package/dist/chunk-UY3UYPBZ.cjs.map +1 -0
  57. package/dist/chunk-W3253FGB.cjs +277 -0
  58. package/dist/chunk-W3253FGB.cjs.map +1 -0
  59. package/dist/chunk-W7LHZVQF.js +26 -0
  60. package/dist/chunk-W7LHZVQF.js.map +1 -0
  61. package/dist/chunk-WBWNM6LB.cjs +1360 -0
  62. package/dist/chunk-WBWNM6LB.cjs.map +1 -0
  63. package/dist/chunk-WFJ7L2RV.js +494 -0
  64. package/dist/chunk-WFJ7L2RV.js.map +1 -0
  65. package/dist/chunk-X4RMFFMR.js +28 -0
  66. package/dist/chunk-X4RMFFMR.js.map +1 -0
  67. package/dist/chunk-Y4Y2S7BM.cjs +92 -0
  68. package/dist/chunk-Y4Y2S7BM.cjs.map +1 -0
  69. package/dist/chunk-YLPNXZFI.cjs +143 -0
  70. package/dist/chunk-YLPNXZFI.cjs.map +1 -0
  71. package/dist/chunk-YTXEZ4SD.cjs +77 -0
  72. package/dist/chunk-YTXEZ4SD.cjs.map +1 -0
  73. package/dist/chunk-Z6ZWNWWR.js +30 -0
  74. package/dist/chunk-Z6ZWNWWR.js.map +1 -0
  75. package/dist/config.cjs +26 -0
  76. package/dist/config.cjs.map +1 -0
  77. package/dist/config.d.cts +75 -0
  78. package/dist/config.d.ts +75 -0
  79. package/dist/config.js +5 -0
  80. package/dist/config.js.map +1 -0
  81. package/dist/db.cjs +233 -0
  82. package/dist/db.cjs.map +1 -0
  83. package/dist/db.d.cts +123 -0
  84. package/dist/db.d.ts +123 -0
  85. package/dist/db.js +228 -0
  86. package/dist/db.js.map +1 -0
  87. package/dist/decorators.cjs +67 -0
  88. package/dist/decorators.cjs.map +1 -0
  89. package/dist/decorators.d.cts +91 -0
  90. package/dist/decorators.d.ts +91 -0
  91. package/dist/decorators.js +65 -0
  92. package/dist/decorators.js.map +1 -0
  93. package/dist/event-subscriber.cjs +6 -0
  94. package/dist/event-subscriber.cjs.map +1 -0
  95. package/dist/event-subscriber.d.cts +116 -0
  96. package/dist/event-subscriber.d.ts +116 -0
  97. package/dist/event-subscriber.js +3 -0
  98. package/dist/event-subscriber.js.map +1 -0
  99. package/dist/event-testing.cjs +21 -0
  100. package/dist/event-testing.cjs.map +1 -0
  101. package/dist/event-testing.d.cts +110 -0
  102. package/dist/event-testing.d.ts +110 -0
  103. package/dist/event-testing.js +4 -0
  104. package/dist/event-testing.js.map +1 -0
  105. package/dist/event.cjs +30 -0
  106. package/dist/event.cjs.map +1 -0
  107. package/dist/event.d.cts +282 -0
  108. package/dist/event.d.ts +282 -0
  109. package/dist/event.js +13 -0
  110. package/dist/event.js.map +1 -0
  111. package/dist/exporters.cjs +17 -0
  112. package/dist/exporters.cjs.map +1 -0
  113. package/dist/exporters.d.cts +1 -0
  114. package/dist/exporters.d.ts +1 -0
  115. package/dist/exporters.js +4 -0
  116. package/dist/exporters.js.map +1 -0
  117. package/dist/functional.cjs +46 -0
  118. package/dist/functional.cjs.map +1 -0
  119. package/dist/functional.d.cts +478 -0
  120. package/dist/functional.d.ts +478 -0
  121. package/dist/functional.js +13 -0
  122. package/dist/functional.js.map +1 -0
  123. package/dist/http.cjs +189 -0
  124. package/dist/http.cjs.map +1 -0
  125. package/dist/http.d.cts +169 -0
  126. package/dist/http.d.ts +169 -0
  127. package/dist/http.js +184 -0
  128. package/dist/http.js.map +1 -0
  129. package/dist/index.cjs +333 -0
  130. package/dist/index.cjs.map +1 -0
  131. package/dist/index.d.cts +758 -0
  132. package/dist/index.d.ts +758 -0
  133. package/dist/index.js +143 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/instrumentation.cjs +182 -0
  136. package/dist/instrumentation.cjs.map +1 -0
  137. package/dist/instrumentation.d.cts +49 -0
  138. package/dist/instrumentation.d.ts +49 -0
  139. package/dist/instrumentation.js +179 -0
  140. package/dist/instrumentation.js.map +1 -0
  141. package/dist/logger.cjs +19 -0
  142. package/dist/logger.cjs.map +1 -0
  143. package/dist/logger.d.cts +146 -0
  144. package/dist/logger.d.ts +146 -0
  145. package/dist/logger.js +6 -0
  146. package/dist/logger.js.map +1 -0
  147. package/dist/metric-helpers.cjs +31 -0
  148. package/dist/metric-helpers.cjs.map +1 -0
  149. package/dist/metric-helpers.d.cts +13 -0
  150. package/dist/metric-helpers.d.ts +13 -0
  151. package/dist/metric-helpers.js +6 -0
  152. package/dist/metric-helpers.js.map +1 -0
  153. package/dist/metric-testing.cjs +21 -0
  154. package/dist/metric-testing.cjs.map +1 -0
  155. package/dist/metric-testing.d.cts +110 -0
  156. package/dist/metric-testing.d.ts +110 -0
  157. package/dist/metric-testing.js +4 -0
  158. package/dist/metric-testing.js.map +1 -0
  159. package/dist/metric.cjs +26 -0
  160. package/dist/metric.cjs.map +1 -0
  161. package/dist/metric.d.cts +240 -0
  162. package/dist/metric.d.ts +240 -0
  163. package/dist/metric.js +9 -0
  164. package/dist/metric.js.map +1 -0
  165. package/dist/processors.cjs +17 -0
  166. package/dist/processors.cjs.map +1 -0
  167. package/dist/processors.d.cts +1 -0
  168. package/dist/processors.d.ts +1 -0
  169. package/dist/processors.js +4 -0
  170. package/dist/processors.js.map +1 -0
  171. package/dist/sampling.cjs +40 -0
  172. package/dist/sampling.cjs.map +1 -0
  173. package/dist/sampling.d.cts +260 -0
  174. package/dist/sampling.d.ts +260 -0
  175. package/dist/sampling.js +7 -0
  176. package/dist/sampling.js.map +1 -0
  177. package/dist/semantic-helpers.cjs +35 -0
  178. package/dist/semantic-helpers.cjs.map +1 -0
  179. package/dist/semantic-helpers.d.cts +442 -0
  180. package/dist/semantic-helpers.d.ts +442 -0
  181. package/dist/semantic-helpers.js +14 -0
  182. package/dist/semantic-helpers.js.map +1 -0
  183. package/dist/tail-sampling-processor.cjs +13 -0
  184. package/dist/tail-sampling-processor.cjs.map +1 -0
  185. package/dist/tail-sampling-processor.d.cts +27 -0
  186. package/dist/tail-sampling-processor.d.ts +27 -0
  187. package/dist/tail-sampling-processor.js +4 -0
  188. package/dist/tail-sampling-processor.js.map +1 -0
  189. package/dist/testing.cjs +286 -0
  190. package/dist/testing.cjs.map +1 -0
  191. package/dist/testing.d.cts +291 -0
  192. package/dist/testing.d.ts +291 -0
  193. package/dist/testing.js +263 -0
  194. package/dist/testing.js.map +1 -0
  195. package/dist/trace-context-DRZdUvVY.d.cts +181 -0
  196. package/dist/trace-context-DRZdUvVY.d.ts +181 -0
  197. package/dist/trace-helpers.cjs +54 -0
  198. package/dist/trace-helpers.cjs.map +1 -0
  199. package/dist/trace-helpers.d.cts +524 -0
  200. package/dist/trace-helpers.d.ts +524 -0
  201. package/dist/trace-helpers.js +5 -0
  202. package/dist/trace-helpers.js.map +1 -0
  203. package/dist/tracer-provider.cjs +21 -0
  204. package/dist/tracer-provider.cjs.map +1 -0
  205. package/dist/tracer-provider.d.cts +169 -0
  206. package/dist/tracer-provider.d.ts +169 -0
  207. package/dist/tracer-provider.js +4 -0
  208. package/dist/tracer-provider.js.map +1 -0
  209. package/package.json +280 -0
  210. package/src/baggage-span-processor.test.ts +202 -0
  211. package/src/baggage-span-processor.ts +98 -0
  212. package/src/circuit-breaker.test.ts +341 -0
  213. package/src/circuit-breaker.ts +184 -0
  214. package/src/config.test.ts +94 -0
  215. package/src/config.ts +169 -0
  216. package/src/db.test.ts +252 -0
  217. package/src/db.ts +447 -0
  218. package/src/decorators.test.ts +203 -0
  219. package/src/decorators.ts +188 -0
  220. package/src/env-config.test.ts +246 -0
  221. package/src/env-config.ts +158 -0
  222. package/src/event-queue.test.ts +222 -0
  223. package/src/event-queue.ts +203 -0
  224. package/src/event-subscriber.ts +136 -0
  225. package/src/event-testing.ts +197 -0
  226. package/src/event.test.ts +718 -0
  227. package/src/event.ts +556 -0
  228. package/src/exporters.ts +96 -0
  229. package/src/functional.test.ts +1059 -0
  230. package/src/functional.ts +2295 -0
  231. package/src/http.test.ts +487 -0
  232. package/src/http.ts +424 -0
  233. package/src/index.ts +158 -0
  234. package/src/init.customization.test.ts +210 -0
  235. package/src/init.integrations.test.ts +366 -0
  236. package/src/init.openllmetry.test.ts +282 -0
  237. package/src/init.protocol.test.ts +215 -0
  238. package/src/init.ts +1426 -0
  239. package/src/instrumentation.test.ts +108 -0
  240. package/src/instrumentation.ts +308 -0
  241. package/src/logger.test.ts +117 -0
  242. package/src/logger.ts +246 -0
  243. package/src/metric-helpers.ts +47 -0
  244. package/src/metric-testing.ts +197 -0
  245. package/src/metric.ts +434 -0
  246. package/src/metrics.test.ts +205 -0
  247. package/src/operation-context.ts +93 -0
  248. package/src/processors.ts +106 -0
  249. package/src/rate-limiter.test.ts +199 -0
  250. package/src/rate-limiter.ts +98 -0
  251. package/src/sampling.test.ts +513 -0
  252. package/src/sampling.ts +428 -0
  253. package/src/semantic-helpers.test.ts +311 -0
  254. package/src/semantic-helpers.ts +584 -0
  255. package/src/shutdown.test.ts +311 -0
  256. package/src/shutdown.ts +222 -0
  257. package/src/stub.integration.test.ts +361 -0
  258. package/src/tail-sampling-processor.test.ts +226 -0
  259. package/src/tail-sampling-processor.ts +51 -0
  260. package/src/testing.ts +670 -0
  261. package/src/trace-context.ts +470 -0
  262. package/src/trace-helpers.new.test.ts +278 -0
  263. package/src/trace-helpers.test.ts +242 -0
  264. package/src/trace-helpers.ts +690 -0
  265. package/src/tracer-provider.test.ts +183 -0
  266. package/src/tracer-provider.ts +266 -0
  267. package/src/track.test.ts +153 -0
  268. package/src/track.ts +120 -0
  269. package/src/validation.test.ts +306 -0
  270. package/src/validation.ts +239 -0
  271. package/src/variable-name-inference.test.ts +178 -0
  272. 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
+ }