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/metric.ts ADDED
@@ -0,0 +1,434 @@
1
+ /**
2
+ * Metrics API for OpenTelemetry
3
+ *
4
+ * Track business metrics for OpenTelemetry (Prometheus/Grafana).
5
+ * For business people who think in metrics.
6
+ *
7
+ * @example Track business metrics
8
+ * ```typescript
9
+ * const metrics = new Metric('checkout')
10
+ *
11
+ * // Track events as metrics
12
+ * metrics.trackEvent('order.completed', {
13
+ * amount: 99.99,
14
+ * currency: 'USD'
15
+ * })
16
+ *
17
+ * // Track conversion funnels
18
+ * metrics.trackFunnelStep('checkout', 'started', { cartValue: 99.99 })
19
+ * metrics.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 })
20
+ *
21
+ * // Track outcomes
22
+ * metrics.trackOutcome('payment.process', 'success', { amount: 99.99 })
23
+ * metrics.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' })
24
+ *
25
+ * // Track values
26
+ * metrics.trackValue('revenue', 149.99, { currency: 'USD' })
27
+ * ```
28
+ */
29
+
30
+ import {
31
+ type Counter,
32
+ type Histogram,
33
+ type Attributes,
34
+ } from '@opentelemetry/api';
35
+ import { getConfig } from './config';
36
+ import { type Logger } from './logger';
37
+ import {
38
+ type EventAttributes,
39
+ type FunnelStatus,
40
+ type OutcomeStatus,
41
+ } from './event-subscriber';
42
+ import { type MetricsCollector } from './metric-testing';
43
+
44
+ // Re-export types for convenience
45
+ export type {
46
+ EventAttributes,
47
+ FunnelStatus,
48
+ OutcomeStatus,
49
+ } from './event-subscriber';
50
+
51
+ /**
52
+ * Metrics class for tracking business metrics in OpenTelemetry
53
+ *
54
+ * Track critical business indicators such as:
55
+ * - User events (signups, purchases, feature usage) as metrics
56
+ * - Conversion funnels (signup → activation → purchase)
57
+ * - Business outcomes (success/failure rates)
58
+ * - Value metrics (revenue, counts, etc.)
59
+ *
60
+ * All metrics are sent to OpenTelemetry (OTLP/Prometheus/Grafana).
61
+ */
62
+ /**
63
+ * Metric configuration for customizing metric names and descriptions
64
+ */
65
+ export interface MetricConfig {
66
+ /** Metric name (e.g., 'metrics.events' or 'custom.events') */
67
+ name?: string;
68
+ /** Metric description */
69
+ description?: string;
70
+ /** Metric unit (default: '1') */
71
+ unit?: string;
72
+ }
73
+
74
+ /**
75
+ * Metrics options
76
+ */
77
+ export interface MetricsOptions {
78
+ /** Optional logger for audit trail */
79
+ logger?: Logger;
80
+ /** Optional collector for testing (captures metrics in memory) */
81
+ collector?: MetricsCollector;
82
+
83
+ /**
84
+ * Namespace for metrics (default: 'metrics')
85
+ * Results in metrics like: {serviceName}.{namespace}.events
86
+ */
87
+ namespace?: string;
88
+
89
+ /**
90
+ * Custom metric configurations
91
+ * Override metric names, descriptions, and units
92
+ */
93
+ metrics?: {
94
+ events?: MetricConfig;
95
+ funnel?: MetricConfig;
96
+ outcomes?: MetricConfig;
97
+ value?: MetricConfig;
98
+ };
99
+ }
100
+
101
+ export class Metric {
102
+ private serviceName: string;
103
+ private eventCounter: Counter;
104
+ private funnelCounter: Counter;
105
+ private outcomeCounter: Counter;
106
+ private valueHistogram: Histogram;
107
+ private logger?: Logger;
108
+ private collector?: MetricsCollector;
109
+
110
+ /**
111
+ * Create a new Metrics instance
112
+ *
113
+ * @param serviceName - Service name for metric namespacing
114
+ * @param options - Optional configuration (logger, collector, namespace, metrics)
115
+ *
116
+ * @example Basic usage (default 'metrics' namespace)
117
+ * ```typescript
118
+ * const metrics = new Metric('checkout');
119
+ * // Creates: checkout.metrics.events, checkout.metrics.funnel, etc.
120
+ * ```
121
+ *
122
+ * @example Custom namespace
123
+ * ```typescript
124
+ * const metrics = new Metric('api', { namespace: 'business' });
125
+ * // Creates: api.business.events, api.business.funnel, etc.
126
+ * ```
127
+ *
128
+ * @example Custom metric names and descriptions
129
+ * ```typescript
130
+ * const metrics = new Metric('payments', {
131
+ * metrics: {
132
+ * outcomes: {
133
+ * name: 'payments.transactions',
134
+ * description: 'Payment transaction outcomes',
135
+ * unit: 'transactions'
136
+ * },
137
+ * value: {
138
+ * name: 'payments.revenue',
139
+ * description: 'Payment revenue in USD',
140
+ * unit: 'USD'
141
+ * }
142
+ * }
143
+ * });
144
+ * ```
145
+ */
146
+ constructor(serviceName: string, options: MetricsOptions = {}) {
147
+ this.serviceName = serviceName;
148
+ this.logger = options.logger;
149
+ this.collector = options.collector;
150
+
151
+ const config = getConfig();
152
+ const meter = config.meter;
153
+
154
+ // Default namespace and metric configurations
155
+ const namespace = options.namespace || 'metrics';
156
+ const metricsConfig = options.metrics || {};
157
+
158
+ // Event counter configuration
159
+ const eventsConfig = metricsConfig.events || {};
160
+ this.eventCounter = meter.createCounter(
161
+ eventsConfig.name || `${serviceName}.${namespace}.events`,
162
+ {
163
+ description: eventsConfig.description || 'Count of business events',
164
+ unit: eventsConfig.unit || '1',
165
+ },
166
+ );
167
+
168
+ // Funnel counter configuration
169
+ const funnelConfig = metricsConfig.funnel || {};
170
+ this.funnelCounter = meter.createCounter(
171
+ funnelConfig.name || `${serviceName}.${namespace}.funnel`,
172
+ {
173
+ description: funnelConfig.description || 'Conversion funnel tracking',
174
+ unit: funnelConfig.unit || '1',
175
+ },
176
+ );
177
+
178
+ // Outcome counter configuration
179
+ const outcomesConfig = metricsConfig.outcomes || {};
180
+ this.outcomeCounter = meter.createCounter(
181
+ outcomesConfig.name || `${serviceName}.${namespace}.outcomes`,
182
+ {
183
+ description:
184
+ outcomesConfig.description || 'Outcome tracking (success/failure)',
185
+ unit: outcomesConfig.unit || '1',
186
+ },
187
+ );
188
+
189
+ // Value histogram configuration
190
+ const valueConfig = metricsConfig.value || {};
191
+ this.valueHistogram = meter.createHistogram(
192
+ valueConfig.name || `${serviceName}.${namespace}.value`,
193
+ {
194
+ description:
195
+ valueConfig.description || 'Value metrics (revenue, counts, etc.)',
196
+ unit: valueConfig.unit || '1',
197
+ },
198
+ );
199
+ }
200
+
201
+ /**
202
+ * Track a business event as a metric
203
+ *
204
+ * Use this for tracking user actions, business events, product usage as metrics:
205
+ * - "user.signup"
206
+ * - "order.completed"
207
+ * - "feature.used"
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * // Track user signup as metric
212
+ * metrics.trackEvent('user.signup', {
213
+ * userId: '123',
214
+ * plan: 'pro'
215
+ * })
216
+ *
217
+ * // Track order as metric
218
+ * metrics.trackEvent('order.completed', {
219
+ * orderId: 'ord_123',
220
+ * amount: 99.99
221
+ * })
222
+ * ```
223
+ */
224
+ trackEvent(eventName: string, attributes?: EventAttributes): void {
225
+ const attrs: Attributes = {
226
+ service: this.serviceName,
227
+ event: eventName,
228
+ ...attributes,
229
+ };
230
+
231
+ this.eventCounter.add(1, attrs);
232
+
233
+ this.logger?.info('Metric event tracked', {
234
+ event: eventName,
235
+ attributes,
236
+ });
237
+
238
+ // Record for testing
239
+ this.collector?.recordEvent({
240
+ event: eventName,
241
+ attributes,
242
+ service: this.serviceName,
243
+ timestamp: Date.now(),
244
+ });
245
+ }
246
+
247
+ /**
248
+ * Track conversion funnel steps as metrics
249
+ *
250
+ * Monitor where users drop off in multi-step processes.
251
+ *
252
+ * @example
253
+ * ```typescript
254
+ * // Track signup funnel
255
+ * metrics.trackFunnelStep('signup', 'started', { userId: '123' })
256
+ * metrics.trackFunnelStep('signup', 'email_verified', { userId: '123' })
257
+ * metrics.trackFunnelStep('signup', 'completed', { userId: '123' })
258
+ *
259
+ * // Track checkout flow
260
+ * metrics.trackFunnelStep('checkout', 'started', { cartValue: 99.99 })
261
+ * metrics.trackFunnelStep('checkout', 'payment_info', { cartValue: 99.99 })
262
+ * metrics.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 })
263
+ * ```
264
+ */
265
+ trackFunnelStep(
266
+ funnelName: string,
267
+ status: FunnelStatus,
268
+ attributes?: EventAttributes,
269
+ ): void {
270
+ const attrs: Attributes = {
271
+ service: this.serviceName,
272
+ funnel: funnelName,
273
+ status,
274
+ ...attributes,
275
+ };
276
+
277
+ this.funnelCounter.add(1, attrs);
278
+
279
+ this.logger?.info('Funnel step tracked', {
280
+ funnel: funnelName,
281
+ status,
282
+ attributes,
283
+ });
284
+
285
+ // Record for testing
286
+ this.collector?.recordFunnelStep({
287
+ funnel: funnelName,
288
+ status,
289
+ attributes,
290
+ service: this.serviceName,
291
+ timestamp: Date.now(),
292
+ });
293
+ }
294
+
295
+ /**
296
+ * Track outcomes (success/failure/partial) as metrics
297
+ *
298
+ * Monitor success rates of critical operations.
299
+ *
300
+ * @example
301
+ * ```typescript
302
+ * // Track email delivery
303
+ * metrics.trackOutcome('email.delivery', 'success', {
304
+ * recipientType: 'user',
305
+ * emailType: 'welcome'
306
+ * })
307
+ *
308
+ * metrics.trackOutcome('email.delivery', 'failure', {
309
+ * recipientType: 'user',
310
+ * errorCode: 'invalid_email'
311
+ * })
312
+ *
313
+ * // Track payment processing
314
+ * metrics.trackOutcome('payment.process', 'success', { amount: 99.99 })
315
+ * metrics.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' })
316
+ * ```
317
+ */
318
+ trackOutcome(
319
+ operationName: string,
320
+ status: OutcomeStatus,
321
+ attributes?: EventAttributes,
322
+ ): void {
323
+ const attrs: Attributes = {
324
+ service: this.serviceName,
325
+ operation: operationName,
326
+ status,
327
+ ...attributes,
328
+ };
329
+
330
+ this.outcomeCounter.add(1, attrs);
331
+
332
+ this.logger?.info('Outcome tracked', {
333
+ operation: operationName,
334
+ status,
335
+ attributes,
336
+ });
337
+
338
+ // Record for testing
339
+ this.collector?.recordOutcome({
340
+ operation: operationName,
341
+ status,
342
+ attributes,
343
+ service: this.serviceName,
344
+ timestamp: Date.now(),
345
+ });
346
+ }
347
+
348
+ /**
349
+ * Track value metrics
350
+ *
351
+ * Record numerical values like revenue, transaction amounts,
352
+ * item counts, processing times, engagement scores, etc.
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * // Track revenue
357
+ * metrics.trackValue('order.revenue', 149.99, {
358
+ * currency: 'USD',
359
+ * productCategory: 'electronics'
360
+ * })
361
+ *
362
+ * // Track items per cart
363
+ * metrics.trackValue('cart.item_count', 5, {
364
+ * userId: '123'
365
+ * })
366
+ *
367
+ * // Track processing time
368
+ * metrics.trackValue('api.response_time', 250, {
369
+ * unit: 'ms',
370
+ * endpoint: '/api/checkout'
371
+ * })
372
+ * ```
373
+ */
374
+ trackValue(
375
+ metricName: string,
376
+ value: number,
377
+ attributes?: EventAttributes,
378
+ ): void {
379
+ const attrs: Attributes = {
380
+ service: this.serviceName,
381
+ metric: metricName,
382
+ ...attributes,
383
+ };
384
+
385
+ this.valueHistogram.record(value, attrs);
386
+
387
+ this.logger?.debug('Value metric tracked', {
388
+ metric: metricName,
389
+ value,
390
+ attributes,
391
+ });
392
+
393
+ // Record for testing
394
+ this.collector?.recordValue({
395
+ metric: metricName,
396
+ value,
397
+ attributes,
398
+ service: this.serviceName,
399
+ timestamp: Date.now(),
400
+ });
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Global metrics instances (singleton pattern)
406
+ */
407
+ const metricsInstances = new Map<string, Metric>();
408
+
409
+ /**
410
+ * Get or create a Metrics instance for a service
411
+ *
412
+ * @param serviceName - Service name for metric namespacing
413
+ * @param logger - Optional logger
414
+ * @returns Metrics instance
415
+ *
416
+ * @example
417
+ * ```typescript
418
+ * const metrics = getMetrics('checkout')
419
+ * metrics.trackEvent('order.completed', { orderId: '123' })
420
+ * ```
421
+ */
422
+ export function getMetrics(serviceName: string, logger?: Logger): Metric {
423
+ if (!metricsInstances.has(serviceName)) {
424
+ metricsInstances.set(serviceName, new Metric(serviceName, { logger }));
425
+ }
426
+ return metricsInstances.get(serviceName)!;
427
+ }
428
+
429
+ /**
430
+ * Reset all metrics instances (mainly for testing)
431
+ */
432
+ export function resetMetrics(): void {
433
+ metricsInstances.clear();
434
+ }
@@ -0,0 +1,205 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { Metric, getMetrics, resetMetrics } from './metric';
3
+ import { type ILogger } from './logger';
4
+ import { init } from './init';
5
+ import { configure } from './config';
6
+
7
+ describe('Metrics', () => {
8
+ let mockLogger: ILogger;
9
+
10
+ beforeEach(() => {
11
+ resetMetrics();
12
+ init({ service: 'test-app' });
13
+ configure({
14
+ meterName: 'test',
15
+ });
16
+ mockLogger = {
17
+ info: vi.fn(),
18
+ warn: vi.fn(),
19
+ error: vi.fn(),
20
+ debug: vi.fn(),
21
+ };
22
+ });
23
+
24
+ describe('trackEvent', () => {
25
+ it('should track business events as metrics', () => {
26
+ const metrics = new Metric('test-service', { logger: mockLogger });
27
+
28
+ metrics.trackEvent('order.completed', {
29
+ orderId: '123',
30
+ amount: 99.99,
31
+ });
32
+
33
+ expect(mockLogger.info).toHaveBeenCalledWith('Metric event tracked', {
34
+ event: 'order.completed',
35
+ attributes: { orderId: '123', amount: 99.99 },
36
+ });
37
+ });
38
+
39
+ it('should track events without attributes', () => {
40
+ const metrics = new Metric('test-service', { logger: mockLogger });
41
+
42
+ metrics.trackEvent('user.login');
43
+
44
+ expect(mockLogger.info).toHaveBeenCalledWith('Metric event tracked', {
45
+ event: 'user.login',
46
+ attributes: undefined,
47
+ });
48
+ });
49
+ });
50
+
51
+ describe('trackFunnelStep', () => {
52
+ it('should track funnel progression as metrics', () => {
53
+ const metrics = new Metric('test-service', { logger: mockLogger });
54
+
55
+ metrics.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });
56
+ metrics.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 });
57
+
58
+ expect(mockLogger.info).toHaveBeenCalledTimes(2);
59
+ expect(mockLogger.info).toHaveBeenCalledWith('Funnel step tracked', {
60
+ funnel: 'checkout',
61
+ status: 'started',
62
+ attributes: { cartValue: 99.99 },
63
+ });
64
+ });
65
+
66
+ it('should track funnel abandonment', () => {
67
+ const metrics = new Metric('test-service', { logger: mockLogger });
68
+
69
+ metrics.trackFunnelStep('checkout', 'abandoned', { reason: 'timeout' });
70
+
71
+ expect(mockLogger.info).toHaveBeenCalledWith('Funnel step tracked', {
72
+ funnel: 'checkout',
73
+ status: 'abandoned',
74
+ attributes: { reason: 'timeout' },
75
+ });
76
+ });
77
+ });
78
+
79
+ describe('trackOutcome', () => {
80
+ it('should track successful outcomes as metrics', () => {
81
+ const metrics = new Metric('test-service', { logger: mockLogger });
82
+
83
+ metrics.trackOutcome('payment.process', 'success', {
84
+ amount: 99.99,
85
+ });
86
+
87
+ expect(mockLogger.info).toHaveBeenCalledWith('Outcome tracked', {
88
+ operation: 'payment.process',
89
+ status: 'success',
90
+ attributes: { amount: 99.99 },
91
+ });
92
+ });
93
+
94
+ it('should track failed outcomes', () => {
95
+ const metrics = new Metric('test-service', { logger: mockLogger });
96
+
97
+ metrics.trackOutcome('payment.process', 'failure', {
98
+ error: 'insufficient_funds',
99
+ });
100
+
101
+ expect(mockLogger.info).toHaveBeenCalledWith('Outcome tracked', {
102
+ operation: 'payment.process',
103
+ status: 'failure',
104
+ attributes: { error: 'insufficient_funds' },
105
+ });
106
+ });
107
+
108
+ it('should track partial outcomes', () => {
109
+ const metrics = new Metric('test-service', { logger: mockLogger });
110
+
111
+ metrics.trackOutcome('batch.process', 'partial', {
112
+ successCount: 8,
113
+ failureCount: 2,
114
+ });
115
+
116
+ expect(mockLogger.info).toHaveBeenCalledWith('Outcome tracked', {
117
+ operation: 'batch.process',
118
+ status: 'partial',
119
+ attributes: { successCount: 8, failureCount: 2 },
120
+ });
121
+ });
122
+ });
123
+
124
+ describe('trackValue', () => {
125
+ it('should track revenue metrics', () => {
126
+ const metrics = new Metric('test-service', { logger: mockLogger });
127
+
128
+ metrics.trackValue('order.revenue', 149.99, {
129
+ currency: 'USD',
130
+ productCategory: 'electronics',
131
+ });
132
+
133
+ expect(mockLogger.debug).toHaveBeenCalledWith('Value metric tracked', {
134
+ metric: 'order.revenue',
135
+ value: 149.99,
136
+ attributes: { currency: 'USD', productCategory: 'electronics' },
137
+ });
138
+ });
139
+
140
+ it('should track processing time', () => {
141
+ const metrics = new Metric('test-service', { logger: mockLogger });
142
+
143
+ metrics.trackValue('application.processing_time', 2500, {
144
+ unit: 'ms',
145
+ });
146
+
147
+ expect(mockLogger.debug).toHaveBeenCalledWith('Value metric tracked', {
148
+ metric: 'application.processing_time',
149
+ value: 2500,
150
+ attributes: { unit: 'ms' },
151
+ });
152
+ });
153
+ });
154
+
155
+ describe('getMetrics', () => {
156
+ it('should return singleton instance', () => {
157
+ const metrics1 = getMetrics('test-service');
158
+ const metrics2 = getMetrics('test-service');
159
+
160
+ expect(metrics1).toBe(metrics2);
161
+ });
162
+
163
+ it('should return different instances for different services', () => {
164
+ const metrics1 = getMetrics('service-1');
165
+ const metrics2 = getMetrics('service-2');
166
+
167
+ expect(metrics1).not.toBe(metrics2);
168
+ });
169
+
170
+ it('should reset instances', () => {
171
+ const metrics1 = getMetrics('test-service');
172
+ resetMetrics();
173
+ const metrics2 = getMetrics('test-service');
174
+
175
+ expect(metrics1).not.toBe(metrics2);
176
+ });
177
+ });
178
+
179
+ describe('real-world usage example', () => {
180
+ it('should track checkout flow', () => {
181
+ const metrics = new Metric('checkout', { logger: mockLogger });
182
+
183
+ // User starts checkout
184
+ metrics.trackFunnelStep('checkout', 'started', { cartValue: 99.99 });
185
+
186
+ // Order completed
187
+ metrics.trackEvent('order.completed', {
188
+ orderId: 'ord_123',
189
+ amount: 99.99,
190
+ });
191
+ metrics.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 });
192
+
193
+ // Payment processed successfully
194
+ metrics.trackOutcome('payment.process', 'success', {
195
+ amount: 99.99,
196
+ });
197
+
198
+ // Track revenue
199
+ metrics.trackValue('revenue', 99.99, { currency: 'USD' });
200
+
201
+ expect(mockLogger.info).toHaveBeenCalledTimes(4);
202
+ expect(mockLogger.debug).toHaveBeenCalledTimes(1);
203
+ });
204
+ });
205
+ });