autotel 4.1.0 → 4.2.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.
Files changed (253) hide show
  1. package/dist/auto.cjs +5 -3
  2. package/dist/auto.cjs.map +1 -1
  3. package/dist/auto.js +3 -3
  4. package/dist/auto.js.map +1 -1
  5. package/dist/chunk-C_NdSu1c.cjs +34 -0
  6. package/dist/correlation-id.cjs +1 -1
  7. package/dist/correlation-id.d.cts.map +1 -1
  8. package/dist/correlation-id.d.ts.map +1 -1
  9. package/dist/correlation-id.js +1 -1
  10. package/dist/decorators.cjs +1 -1
  11. package/dist/decorators.js +1 -1
  12. package/dist/{event-ByBTV9M2.js → event-531asIM6.js} +4 -4
  13. package/dist/{event-ByBTV9M2.js.map → event-531asIM6.js.map} +1 -1
  14. package/dist/{event-BhHREDJk.cjs → event-CcZYwp50.cjs} +4 -4
  15. package/dist/{event-BhHREDJk.cjs.map → event-CcZYwp50.cjs.map} +1 -1
  16. package/dist/event.cjs +1 -1
  17. package/dist/event.js +1 -1
  18. package/dist/{functional-zpzNLhky.cjs → functional-C8B0Qa7o.cjs} +10 -7
  19. package/dist/functional-C8B0Qa7o.cjs.map +1 -0
  20. package/dist/{functional-DtI0u4vx.js → functional-r-AUIRy_.js} +9 -9
  21. package/dist/functional-r-AUIRy_.js.map +1 -0
  22. package/dist/functional.cjs +1 -1
  23. package/dist/functional.js +1 -1
  24. package/dist/http.cjs +1 -1
  25. package/dist/http.js +1 -1
  26. package/dist/index.cjs +15 -13
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts.map +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +14 -14
  31. package/dist/index.js.map +1 -1
  32. package/dist/{init-D-jnNMix.js → init-BS2JVkrL.js} +2 -2
  33. package/dist/{init-D-jnNMix.js.map → init-BS2JVkrL.js.map} +1 -1
  34. package/dist/{init-BX7AmFRl.cjs → init-BXiuPK6j.cjs} +3 -3
  35. package/dist/{init-BX7AmFRl.cjs.map → init-BXiuPK6j.cjs.map} +1 -1
  36. package/dist/instrumentation.cjs +2 -2
  37. package/dist/instrumentation.js +2 -2
  38. package/dist/logger.cjs +236 -8
  39. package/dist/logger.cjs.map +1 -0
  40. package/dist/messaging.cjs +1 -1
  41. package/dist/messaging.js +1 -1
  42. package/dist/{node-require-DF5QBX6z.cjs → node-require-CZ_PU448.cjs} +6 -4
  43. package/dist/node-require-CZ_PU448.cjs.map +1 -0
  44. package/dist/{node-require-Db1oDpLj.js → node-require-vROmTeJ8.js} +5 -5
  45. package/dist/node-require-vROmTeJ8.js.map +1 -0
  46. package/dist/{operation-context-C-2hmmtP.js → operation-context-CKBoA4Qy.js} +3 -3
  47. package/dist/operation-context-CKBoA4Qy.js.map +1 -0
  48. package/dist/{operation-context-n4_obUwq.cjs → operation-context-D6LDf4W_.cjs} +3 -1
  49. package/dist/operation-context-D6LDf4W_.cjs.map +1 -0
  50. package/dist/register.cjs +3 -1
  51. package/dist/register.cjs.map +1 -1
  52. package/dist/register.js +2 -2
  53. package/dist/register.js.map +1 -1
  54. package/dist/semantic-helpers.cjs +1 -1
  55. package/dist/semantic-helpers.js +1 -1
  56. package/dist/{stable-hash-Cg5cT34Q.js → stable-hash-ChFBIhNt.js} +3 -3
  57. package/dist/stable-hash-ChFBIhNt.js.map +1 -0
  58. package/dist/{stable-hash-BNTMrmdB.cjs → stable-hash-brKISGf1.cjs} +4 -2
  59. package/dist/stable-hash-brKISGf1.cjs.map +1 -0
  60. package/dist/trace-context-Cijqoi6e.d.cts.map +1 -1
  61. package/dist/trace-context-Cijqoi6e.d.ts.map +1 -1
  62. package/dist/trace-helpers.cjs +1 -1
  63. package/dist/trace-helpers.js +1 -1
  64. package/dist/{track-wc0HafS_.js → track-COUuU48p.js} +5 -5
  65. package/dist/track-COUuU48p.js.map +1 -0
  66. package/dist/{track-D59FfpL0.cjs → track-Cb3Q4QmS.cjs} +4 -2
  67. package/dist/track-Cb3Q4QmS.cjs.map +1 -0
  68. package/dist/validate.cjs +1 -1
  69. package/dist/validate.js +1 -1
  70. package/dist/webhook.cjs +1 -1
  71. package/dist/webhook.js +1 -1
  72. package/dist/workflow-distributed.cjs +1 -1
  73. package/dist/workflow-distributed.js +1 -1
  74. package/dist/workflow.cjs +3 -1
  75. package/dist/workflow.cjs.map +1 -1
  76. package/dist/workflow.d.cts.map +1 -1
  77. package/dist/workflow.d.ts.map +1 -1
  78. package/dist/workflow.js +3 -3
  79. package/dist/workflow.js.map +1 -1
  80. package/dist/yaml-config.cjs +233 -4
  81. package/dist/yaml-config.cjs.map +1 -0
  82. package/dist/yaml-config.d.cts.map +1 -1
  83. package/dist/yaml-config.d.ts.map +1 -1
  84. package/dist/yaml-config.js +8 -7
  85. package/dist/yaml-config.js.map +1 -1
  86. package/package.json +1 -2
  87. package/dist/functional-DtI0u4vx.js.map +0 -1
  88. package/dist/functional-zpzNLhky.cjs.map +0 -1
  89. package/dist/logger-thMPLpOG.cjs +0 -487
  90. package/dist/logger-thMPLpOG.cjs.map +0 -1
  91. package/dist/node-require-DF5QBX6z.cjs.map +0 -1
  92. package/dist/node-require-Db1oDpLj.js.map +0 -1
  93. package/dist/operation-context-C-2hmmtP.js.map +0 -1
  94. package/dist/operation-context-n4_obUwq.cjs.map +0 -1
  95. package/dist/stable-hash-BNTMrmdB.cjs.map +0 -1
  96. package/dist/stable-hash-Cg5cT34Q.js.map +0 -1
  97. package/dist/track-D59FfpL0.cjs.map +0 -1
  98. package/dist/track-wc0HafS_.js.map +0 -1
  99. package/dist/yaml-config-Ck2uB0Dp.cjs +0 -273
  100. package/dist/yaml-config-Ck2uB0Dp.cjs.map +0 -1
  101. package/src/attribute-redacting-processor.test.ts +0 -763
  102. package/src/attribute-redacting-processor.ts +0 -621
  103. package/src/attributes/attachers.ts +0 -161
  104. package/src/attributes/builders.ts +0 -529
  105. package/src/attributes/domains.ts +0 -42
  106. package/src/attributes/index.ts +0 -81
  107. package/src/attributes/registry.ts +0 -323
  108. package/src/attributes/types.ts +0 -211
  109. package/src/attributes/utils.ts +0 -64
  110. package/src/attributes/validators.ts +0 -266
  111. package/src/attributes.test.ts +0 -292
  112. package/src/auto.ts +0 -67
  113. package/src/autotel-logger.test.ts +0 -548
  114. package/src/autotel-logger.ts +0 -364
  115. package/src/baggage-span-processor.test.ts +0 -202
  116. package/src/baggage-span-processor.ts +0 -100
  117. package/src/business-baggage.test.ts +0 -500
  118. package/src/business-baggage.ts +0 -669
  119. package/src/circuit-breaker.test.ts +0 -341
  120. package/src/circuit-breaker.ts +0 -184
  121. package/src/config.test.ts +0 -94
  122. package/src/config.ts +0 -172
  123. package/src/correlated-events.test.ts +0 -151
  124. package/src/correlated-events.ts +0 -47
  125. package/src/correlation-id.test.ts +0 -163
  126. package/src/correlation-id.ts +0 -206
  127. package/src/db.test.ts +0 -252
  128. package/src/db.ts +0 -447
  129. package/src/decorators.test.ts +0 -153
  130. package/src/decorators.ts +0 -188
  131. package/src/define-event.test.ts +0 -41
  132. package/src/define-event.ts +0 -58
  133. package/src/devtools.ts +0 -60
  134. package/src/drain-pipeline.test.ts +0 -68
  135. package/src/drain-pipeline.ts +0 -199
  136. package/src/drain-toolkit.test.ts +0 -113
  137. package/src/drain-toolkit.ts +0 -129
  138. package/src/enricher-toolkit.test.ts +0 -67
  139. package/src/enricher-toolkit.ts +0 -79
  140. package/src/enrichers.test.ts +0 -150
  141. package/src/enrichers.ts +0 -145
  142. package/src/env-config.test.ts +0 -323
  143. package/src/env-config.ts +0 -309
  144. package/src/error-catalog.test.ts +0 -133
  145. package/src/error-catalog.ts +0 -262
  146. package/src/event-queue.test.ts +0 -864
  147. package/src/event-queue.ts +0 -699
  148. package/src/event-subscriber.ts +0 -262
  149. package/src/event-testing.ts +0 -197
  150. package/src/event.test.ts +0 -1104
  151. package/src/event.ts +0 -988
  152. package/src/events-config.ts +0 -235
  153. package/src/exporters.ts +0 -165
  154. package/src/filtering-span-processor.test.ts +0 -281
  155. package/src/filtering-span-processor.ts +0 -111
  156. package/src/flatten-attributes.test.ts +0 -76
  157. package/src/flatten-attributes.ts +0 -80
  158. package/src/functional.strict-types.typecheck.ts +0 -53
  159. package/src/functional.test.ts +0 -1464
  160. package/src/functional.ts +0 -2539
  161. package/src/functional.types.test.ts +0 -135
  162. package/src/hook.mjs +0 -15
  163. package/src/http.test.ts +0 -485
  164. package/src/http.ts +0 -424
  165. package/src/index.ts +0 -433
  166. package/src/init-auto-redactor.test.ts +0 -53
  167. package/src/init-redactor.test.ts +0 -8
  168. package/src/init.customization.test.ts +0 -665
  169. package/src/init.integrations.test.ts +0 -399
  170. package/src/init.openllmetry.test.ts +0 -194
  171. package/src/init.protocol.test.ts +0 -215
  172. package/src/init.ts +0 -2439
  173. package/src/instrumentation.test.ts +0 -108
  174. package/src/instrumentation.ts +0 -319
  175. package/src/logger.test.ts +0 -125
  176. package/src/logger.ts +0 -341
  177. package/src/messaging-adapters.test.ts +0 -595
  178. package/src/messaging-adapters.ts +0 -583
  179. package/src/messaging-testing.test.ts +0 -573
  180. package/src/messaging-testing.ts +0 -935
  181. package/src/messaging.test.ts +0 -1646
  182. package/src/messaging.ts +0 -2245
  183. package/src/metric-helpers.ts +0 -47
  184. package/src/metric-testing.ts +0 -197
  185. package/src/metric.ts +0 -446
  186. package/src/metrics.test.ts +0 -241
  187. package/src/node-require.ts +0 -123
  188. package/src/operation-context.ts +0 -93
  189. package/src/parse-error.test.ts +0 -73
  190. package/src/parse-error.ts +0 -112
  191. package/src/posthog-logs.test.ts +0 -115
  192. package/src/posthog-logs.ts +0 -77
  193. package/src/pretty-console-exporter.test.ts +0 -545
  194. package/src/pretty-console-exporter.ts +0 -413
  195. package/src/pretty-log-formatter.test.ts +0 -123
  196. package/src/pretty-log-formatter.ts +0 -210
  197. package/src/processors/canonical-log-line-processor.test.ts +0 -523
  198. package/src/processors/canonical-log-line-processor.ts +0 -396
  199. package/src/processors.ts +0 -152
  200. package/src/rate-limiter.test.ts +0 -199
  201. package/src/rate-limiter.ts +0 -98
  202. package/src/redact-values.test.ts +0 -90
  203. package/src/redact-values.ts +0 -34
  204. package/src/register.ts +0 -37
  205. package/src/request-logger.test.ts +0 -545
  206. package/src/request-logger.ts +0 -342
  207. package/src/sampling.test.ts +0 -1060
  208. package/src/sampling.ts +0 -737
  209. package/src/security-schema.test.ts +0 -45
  210. package/src/security-schema.ts +0 -107
  211. package/src/semantic-conventions.ts +0 -15
  212. package/src/semantic-helpers.test.ts +0 -226
  213. package/src/semantic-helpers.ts +0 -438
  214. package/src/shutdown.test.ts +0 -364
  215. package/src/shutdown.ts +0 -246
  216. package/src/span-name-normalizer.test.ts +0 -377
  217. package/src/span-name-normalizer.ts +0 -213
  218. package/src/stable-hash.ts +0 -27
  219. package/src/structured-error.test.ts +0 -191
  220. package/src/structured-error.ts +0 -157
  221. package/src/stub.integration.test.ts +0 -361
  222. package/src/tail-sampling-processor.test.ts +0 -230
  223. package/src/tail-sampling-processor.ts +0 -55
  224. package/src/test-span-collector.test.ts +0 -234
  225. package/src/test-span-collector.ts +0 -150
  226. package/src/testing.ts +0 -705
  227. package/src/trace-context.test.ts +0 -73
  228. package/src/trace-context.ts +0 -567
  229. package/src/trace-helpers.new.test.ts +0 -278
  230. package/src/trace-helpers.test.ts +0 -290
  231. package/src/trace-helpers.ts +0 -710
  232. package/src/trace-hybrid.test.ts +0 -42
  233. package/src/trace-hybrid.ts +0 -37
  234. package/src/tracer-provider.test.ts +0 -183
  235. package/src/tracer-provider.ts +0 -266
  236. package/src/track.test.ts +0 -154
  237. package/src/track.ts +0 -216
  238. package/src/validate.test.ts +0 -287
  239. package/src/validate.ts +0 -307
  240. package/src/validation-attributes.ts +0 -43
  241. package/src/validation.test.ts +0 -330
  242. package/src/validation.ts +0 -246
  243. package/src/variable-name-inference.test.ts +0 -178
  244. package/src/variable-name-inference.ts +0 -242
  245. package/src/webhook.test.ts +0 -649
  246. package/src/webhook.ts +0 -637
  247. package/src/workflow-distributed.test.ts +0 -786
  248. package/src/workflow-distributed.ts +0 -916
  249. package/src/workflow.async-safety.integration.test.ts +0 -345
  250. package/src/workflow.test.ts +0 -647
  251. package/src/workflow.ts +0 -810
  252. package/src/yaml-config.test.ts +0 -373
  253. package/src/yaml-config.ts +0 -351
@@ -1,377 +0,0 @@
1
- /**
2
- * Tests for SpanNameNormalizingProcessor
3
- */
4
-
5
- import { describe, it, expect, beforeEach, vi } from 'vitest';
6
- import {
7
- SpanNameNormalizingProcessor,
8
- NORMALIZER_PATTERNS,
9
- NORMALIZER_PRESETS,
10
- type SpanNameNormalizerFn,
11
- } from './span-name-normalizer';
12
- import type {
13
- SpanProcessor,
14
- ReadableSpan,
15
- } from '@opentelemetry/sdk-trace-base';
16
- import type { Context } from '@opentelemetry/api';
17
- import type { Span } from '@opentelemetry/sdk-trace-base';
18
-
19
- /**
20
- * Mock span processor to capture forwarded spans
21
- */
22
- class MockSpanProcessor implements SpanProcessor {
23
- public startedSpans: Span[] = [];
24
- public endedSpans: ReadableSpan[] = [];
25
- public flushed = false;
26
- public shutdownCalled = false;
27
-
28
- onStart(span: Span): void {
29
- this.startedSpans.push(span);
30
- }
31
-
32
- onEnd(span: ReadableSpan): void {
33
- this.endedSpans.push(span);
34
- }
35
-
36
- async forceFlush(): Promise<void> {
37
- this.flushed = true;
38
- }
39
-
40
- async shutdown(): Promise<void> {
41
- this.shutdownCalled = true;
42
- }
43
- }
44
-
45
- /**
46
- * Create a mock Span with updateName capability
47
- */
48
- function createMockSpan(initialName: string): Span {
49
- const span = {
50
- _name: initialName,
51
- updateName: vi.fn(function (this: { _name: string }, newName: string) {
52
- this._name = newName;
53
- }),
54
- spanContext: () => ({
55
- traceId: 'trace123',
56
- spanId: 'span123',
57
- traceFlags: 1,
58
- }),
59
- };
60
-
61
- // Define name as a getter to return current _name
62
- Object.defineProperty(span, 'name', {
63
- get() {
64
- return span._name;
65
- },
66
- enumerable: true,
67
- });
68
-
69
- return span as unknown as Span;
70
- }
71
-
72
- describe('SpanNameNormalizingProcessor', () => {
73
- let mockProcessor: MockSpanProcessor;
74
-
75
- beforeEach(() => {
76
- mockProcessor = new MockSpanProcessor();
77
- });
78
-
79
- describe('custom normalizer function', () => {
80
- it('should normalize span names using custom function', () => {
81
- const normalizer: SpanNameNormalizerFn = (name) =>
82
- name.replaceAll(/\/[0-9]+/g, '/:id');
83
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
84
- normalizer,
85
- });
86
-
87
- const span = createMockSpan('GET /users/123/posts/456');
88
- processor.onStart(span, {} as Context);
89
-
90
- expect(span.updateName).toHaveBeenCalledWith('GET /users/:id/posts/:id');
91
- });
92
-
93
- it('should not call updateName if name unchanged', () => {
94
- const normalizer: SpanNameNormalizerFn = (name) => name; // No change
95
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
96
- normalizer,
97
- });
98
-
99
- const span = createMockSpan('createUser');
100
- processor.onStart(span, {} as Context);
101
-
102
- expect(span.updateName).not.toHaveBeenCalled();
103
- });
104
-
105
- it('should forward span to wrapped processor', () => {
106
- const normalizer: SpanNameNormalizerFn = (name) => name;
107
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
108
- normalizer,
109
- });
110
-
111
- const span = createMockSpan('test');
112
- const context = {} as Context;
113
- processor.onStart(span, context);
114
-
115
- expect(mockProcessor.startedSpans).toHaveLength(1);
116
- });
117
- });
118
-
119
- describe('built-in presets', () => {
120
- describe('rest-api preset', () => {
121
- it('should normalize numeric IDs', () => {
122
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
123
- normalizer: 'rest-api',
124
- });
125
-
126
- const span = createMockSpan('GET /users/123');
127
- processor.onStart(span, {} as Context);
128
-
129
- expect(span.updateName).toHaveBeenCalledWith('GET /users/:id');
130
- });
131
-
132
- it('should normalize UUIDs', () => {
133
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
134
- normalizer: 'rest-api',
135
- });
136
-
137
- const span = createMockSpan(
138
- 'GET /items/550e8400-e29b-41d4-a716-446655440000',
139
- );
140
- processor.onStart(span, {} as Context);
141
-
142
- expect(span.updateName).toHaveBeenCalledWith('GET /items/:uuid');
143
- });
144
-
145
- it('should normalize MongoDB ObjectIds', () => {
146
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
147
- normalizer: 'rest-api',
148
- });
149
-
150
- const span = createMockSpan('GET /docs/507f1f77bcf86cd799439011');
151
- processor.onStart(span, {} as Context);
152
-
153
- expect(span.updateName).toHaveBeenCalledWith('GET /docs/:objectId');
154
- });
155
-
156
- it('should normalize ISO dates', () => {
157
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
158
- normalizer: 'rest-api',
159
- });
160
-
161
- const span = createMockSpan('GET /logs/2024-01-15');
162
- processor.onStart(span, {} as Context);
163
-
164
- expect(span.updateName).toHaveBeenCalledWith('GET /logs/:date');
165
- });
166
-
167
- it('should normalize timestamps', () => {
168
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
169
- normalizer: 'rest-api',
170
- });
171
-
172
- const span = createMockSpan('GET /events/1705334400');
173
- processor.onStart(span, {} as Context);
174
-
175
- expect(span.updateName).toHaveBeenCalledWith('GET /events/:timestamp');
176
- });
177
-
178
- it('should normalize emails', () => {
179
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
180
- normalizer: 'rest-api',
181
- });
182
-
183
- const span = createMockSpan('GET /users/john@example.com');
184
- processor.onStart(span, {} as Context);
185
-
186
- expect(span.updateName).toHaveBeenCalledWith('GET /users/:email');
187
- });
188
-
189
- it('should handle multiple dynamic segments', () => {
190
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
191
- normalizer: 'rest-api',
192
- });
193
-
194
- const span = createMockSpan('GET /users/123/posts/456/comments/789');
195
- processor.onStart(span, {} as Context);
196
-
197
- expect(span.updateName).toHaveBeenCalledWith(
198
- 'GET /users/:id/posts/:id/comments/:id',
199
- );
200
- });
201
- });
202
-
203
- describe('minimal preset', () => {
204
- it('should only normalize numeric IDs and UUIDs', () => {
205
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
206
- normalizer: 'minimal',
207
- });
208
-
209
- const span = createMockSpan('GET /users/123');
210
- processor.onStart(span, {} as Context);
211
-
212
- expect(span.updateName).toHaveBeenCalledWith('GET /users/:id');
213
- });
214
-
215
- it('should not normalize ObjectIds (only rest-api does)', () => {
216
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
217
- normalizer: 'minimal',
218
- });
219
-
220
- // ObjectId is 24 hex chars - minimal doesn't handle this
221
- const span = createMockSpan('GET /docs/507f1f77bcf86cd799439011');
222
- processor.onStart(span, {} as Context);
223
-
224
- // Minimal only does numeric IDs and UUIDs, not ObjectIds
225
- expect(span.updateName).not.toHaveBeenCalled();
226
- });
227
- });
228
-
229
- describe('graphql preset', () => {
230
- it('should normalize UUIDs in paths', () => {
231
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
232
- normalizer: 'graphql',
233
- });
234
-
235
- const span = createMockSpan(
236
- 'POST /graphql/550e8400-e29b-41d4-a716-446655440000',
237
- );
238
- processor.onStart(span, {} as Context);
239
-
240
- expect(span.updateName).toHaveBeenCalledWith('POST /graphql/:uuid');
241
- });
242
-
243
- it('should normalize path-style IDs in GraphQL endpoints', () => {
244
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
245
- normalizer: 'graphql',
246
- });
247
-
248
- const span = createMockSpan('POST /graphql/users/123');
249
- processor.onStart(span, {} as Context);
250
-
251
- expect(span.updateName).toHaveBeenCalledWith('POST /graphql/users/:id');
252
- });
253
-
254
- it('should not modify pure operation names without IDs', () => {
255
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
256
- normalizer: 'graphql',
257
- });
258
-
259
- const span = createMockSpan('query GetUserById');
260
- processor.onStart(span, {} as Context);
261
-
262
- // No change expected since there are no path segments to normalize
263
- expect(span.updateName).not.toHaveBeenCalled();
264
- });
265
- });
266
- });
267
-
268
- describe('error handling', () => {
269
- it('should keep original name if normalizer throws (fail-open)', () => {
270
- const normalizer: SpanNameNormalizerFn = () => {
271
- throw new Error('Normalizer error');
272
- };
273
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
274
- normalizer,
275
- });
276
-
277
- const span = createMockSpan('GET /users/123');
278
- processor.onStart(span, {} as Context);
279
-
280
- // Should not throw and should not call updateName
281
- expect(span.updateName).not.toHaveBeenCalled();
282
- expect(mockProcessor.startedSpans).toHaveLength(1);
283
- });
284
-
285
- it('should throw for unknown preset', () => {
286
- expect(() => {
287
- new SpanNameNormalizingProcessor(mockProcessor, {
288
- normalizer: 'unknown-preset' as 'rest-api',
289
- });
290
- }).toThrow('Unknown span name normalizer preset');
291
- });
292
- });
293
-
294
- describe('lifecycle methods', () => {
295
- it('should forward onEnd to wrapped processor', () => {
296
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
297
- normalizer: 'rest-api',
298
- });
299
-
300
- const span = { name: 'test' } as ReadableSpan;
301
- processor.onEnd(span);
302
-
303
- expect(mockProcessor.endedSpans).toHaveLength(1);
304
- });
305
-
306
- it('should forward forceFlush to wrapped processor', async () => {
307
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
308
- normalizer: 'rest-api',
309
- });
310
-
311
- await processor.forceFlush();
312
-
313
- expect(mockProcessor.flushed).toBe(true);
314
- });
315
-
316
- it('should forward shutdown to wrapped processor', async () => {
317
- const processor = new SpanNameNormalizingProcessor(mockProcessor, {
318
- normalizer: 'rest-api',
319
- });
320
-
321
- await processor.shutdown();
322
-
323
- expect(mockProcessor.shutdownCalled).toBe(true);
324
- });
325
- });
326
- });
327
-
328
- describe('NORMALIZER_PATTERNS', () => {
329
- it('should export regex patterns for advanced users', () => {
330
- expect(NORMALIZER_PATTERNS.numericId).toBeInstanceOf(RegExp);
331
- expect(NORMALIZER_PATTERNS.uuid).toBeInstanceOf(RegExp);
332
- expect(NORMALIZER_PATTERNS.objectId).toBeInstanceOf(RegExp);
333
- expect(NORMALIZER_PATTERNS.isoDate).toBeInstanceOf(RegExp);
334
- expect(NORMALIZER_PATTERNS.timestamp).toBeInstanceOf(RegExp);
335
- expect(NORMALIZER_PATTERNS.email).toBeInstanceOf(RegExp);
336
- });
337
-
338
- describe('pattern matching', () => {
339
- it('numericId should match numeric path segments', () => {
340
- expect('/users/123'.replace(NORMALIZER_PATTERNS.numericId, '/:id')).toBe(
341
- '/users/:id',
342
- );
343
- expect(
344
- '/users/123/posts'.replace(NORMALIZER_PATTERNS.numericId, '/:id'),
345
- ).toBe('/users/:id/posts');
346
- });
347
-
348
- it('uuid should match standard UUIDs', () => {
349
- const uuid = '550e8400-e29b-41d4-a716-446655440000';
350
- expect(`/items/${uuid}`.replace(NORMALIZER_PATTERNS.uuid, '/:uuid')).toBe(
351
- '/items/:uuid',
352
- );
353
- });
354
-
355
- it('objectId should match MongoDB ObjectIds', () => {
356
- expect(
357
- '/docs/507f1f77bcf86cd799439011'.replace(
358
- NORMALIZER_PATTERNS.objectId,
359
- '/:objectId',
360
- ),
361
- ).toBe('/docs/:objectId');
362
- });
363
- });
364
- });
365
-
366
- describe('NORMALIZER_PRESETS', () => {
367
- it('should export preset functions for advanced users', () => {
368
- expect(typeof NORMALIZER_PRESETS['rest-api']).toBe('function');
369
- expect(typeof NORMALIZER_PRESETS['graphql']).toBe('function');
370
- expect(typeof NORMALIZER_PRESETS['minimal']).toBe('function');
371
- });
372
-
373
- it('presets should be usable directly', () => {
374
- const result = NORMALIZER_PRESETS['rest-api']('GET /users/123/posts/456');
375
- expect(result).toBe('GET /users/:id/posts/:id');
376
- });
377
- });
@@ -1,213 +0,0 @@
1
- /**
2
- * Span Name Normalizer
3
- *
4
- * Normalizes span names to reduce cardinality from dynamic path segments.
5
- * This is critical for observability backends that charge by unique span names
6
- * or have cardinality limits.
7
- *
8
- * @example Basic usage with custom function
9
- * ```typescript
10
- * init({
11
- * service: 'my-app',
12
- * spanNameNormalizer: (name) => {
13
- * return name.replace(/\/[0-9]+/g, '/:id');
14
- * }
15
- * })
16
- * ```
17
- *
18
- * @example Using built-in preset
19
- * ```typescript
20
- * init({
21
- * service: 'my-app',
22
- * spanNameNormalizer: 'rest-api'
23
- * })
24
- * ```
25
- */
26
-
27
- import type {
28
- SpanProcessor,
29
- ReadableSpan,
30
- } from '@opentelemetry/sdk-trace-base';
31
- import type { Context } from '@opentelemetry/api';
32
- import type { Span } from '@opentelemetry/sdk-trace-base';
33
-
34
- /**
35
- * Function to normalize a span name
36
- * @param name - The original span name
37
- * @returns The normalized span name
38
- */
39
- export type SpanNameNormalizerFn = (name: string) => string;
40
-
41
- /**
42
- * Built-in normalizer preset names
43
- */
44
- export type SpanNameNormalizerPreset = 'rest-api' | 'graphql' | 'minimal';
45
-
46
- /**
47
- * Normalizer config - either a function or a preset name
48
- */
49
- export type SpanNameNormalizerConfig =
50
- | SpanNameNormalizerFn
51
- | SpanNameNormalizerPreset;
52
-
53
- export interface SpanNameNormalizingProcessorOptions {
54
- /**
55
- * Normalizer function or preset name
56
- */
57
- normalizer: SpanNameNormalizerConfig;
58
- }
59
-
60
- /**
61
- * Built-in normalizer patterns
62
- */
63
- const NORMALIZER_PATTERNS = {
64
- // Numeric IDs: /users/123 → /users/:id
65
- numericId: /\/\d+(?=\/|$)/g,
66
-
67
- // UUIDs: /users/550e8400-e29b-41d4-a716-446655440000 → /users/:uuid
68
- uuid: /\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?=\/|$)/gi,
69
-
70
- // Short UUIDs (without dashes): /users/550e8400e29b41d4a716446655440000 → /users/:uuid
71
- shortUuid: /\/[0-9a-f]{32}(?=\/|$)/gi,
72
-
73
- // MongoDB ObjectIds: /docs/507f1f77bcf86cd799439011 → /docs/:objectId
74
- objectId: /\/[0-9a-f]{24}(?=\/|$)/gi,
75
-
76
- // Hashes (6+ hex chars): /assets/abc123def.js → /assets/:hash.js
77
- hash: /\/[0-9a-f]{6,}(?=\.[a-z]+$)/gi,
78
-
79
- // ISO dates: /logs/2024-01-15 → /logs/:date
80
- isoDate: /\/\d{4}-\d{2}-\d{2}(?=\/|$)/g,
81
-
82
- // Timestamps: /events/1705334400 → /events/:timestamp
83
- timestamp: /\/1[0-9]{9}(?=\/|$)/g,
84
-
85
- // Email-like segments: /users/john@example.com → /users/:email
86
- email: /\/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?=\/|$)/g,
87
- } as const;
88
-
89
- /**
90
- * Built-in normalizer presets
91
- */
92
- const NORMALIZER_PRESETS: Record<
93
- SpanNameNormalizerPreset,
94
- SpanNameNormalizerFn
95
- > = {
96
- /**
97
- * REST API preset - normalizes common REST path patterns
98
- * Handles: numeric IDs, UUIDs, ObjectIds, dates, timestamps, emails
99
- */
100
- 'rest-api': (name: string): string => {
101
- return name
102
- .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')
103
- .replaceAll(NORMALIZER_PATTERNS.shortUuid, '/:uuid')
104
- .replaceAll(NORMALIZER_PATTERNS.objectId, '/:objectId')
105
- .replaceAll(NORMALIZER_PATTERNS.isoDate, '/:date')
106
- .replaceAll(NORMALIZER_PATTERNS.timestamp, '/:timestamp')
107
- .replaceAll(NORMALIZER_PATTERNS.email, '/:email')
108
- .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');
109
- },
110
-
111
- /**
112
- * GraphQL preset - normalizes GraphQL operation names and paths
113
- * Keeps query/mutation names but normalizes embedded IDs
114
- */
115
- graphql: (name: string): string => {
116
- // For GraphQL, normalize both path-style and embedded IDs
117
- return name
118
- .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')
119
- .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');
120
- },
121
-
122
- /**
123
- * Minimal preset - only normalizes numeric IDs and UUIDs
124
- */
125
- minimal: (name: string): string => {
126
- return name
127
- .replaceAll(NORMALIZER_PATTERNS.uuid, '/:uuid')
128
- .replaceAll(NORMALIZER_PATTERNS.numericId, '/:id');
129
- },
130
- };
131
-
132
- /**
133
- * Resolve normalizer config to a function
134
- */
135
- function resolveNormalizer(
136
- config: SpanNameNormalizerConfig,
137
- ): SpanNameNormalizerFn {
138
- if (typeof config === 'function') {
139
- return config;
140
- }
141
-
142
- const preset = NORMALIZER_PRESETS[config];
143
- if (!preset) {
144
- throw new Error(
145
- `Unknown span name normalizer preset: "${config}". ` +
146
- `Available presets: ${Object.keys(NORMALIZER_PRESETS).join(', ')}`,
147
- );
148
- }
149
-
150
- return preset;
151
- }
152
-
153
- /**
154
- * Span processor that normalizes span names to reduce cardinality.
155
- *
156
- * Normalization happens in onStart() when we have access to the mutable Span.
157
- * This allows us to call span.updateName() before the span is finalized.
158
- *
159
- * Common use cases:
160
- * - REST APIs: /users/123/posts/456 → /users/:id/posts/:id
161
- * - UUIDs: /items/550e8400-e29b-41d4-a716-446655440000 → /items/:uuid
162
- * - Dates: /logs/2024-01-15 → /logs/:date
163
- */
164
- export class SpanNameNormalizingProcessor implements SpanProcessor {
165
- private readonly wrappedProcessor: SpanProcessor;
166
- private readonly normalizer: SpanNameNormalizerFn;
167
-
168
- constructor(
169
- wrappedProcessor: SpanProcessor,
170
- options: SpanNameNormalizingProcessorOptions,
171
- ) {
172
- this.wrappedProcessor = wrappedProcessor;
173
- this.normalizer = resolveNormalizer(options.normalizer);
174
- }
175
-
176
- /**
177
- * Normalize span name on start (when Span is mutable)
178
- */
179
- onStart(span: Span, parentContext: Context): void {
180
- try {
181
- const originalName = span.name;
182
- const normalizedName = this.normalizer(originalName);
183
-
184
- if (normalizedName !== originalName) {
185
- span.updateName(normalizedName);
186
- }
187
- } catch {
188
- // If normalizer throws, keep original name (fail-open)
189
- }
190
-
191
- this.wrappedProcessor.onStart(span, parentContext);
192
- }
193
-
194
- /**
195
- * Pass through onEnd unchanged
196
- */
197
- onEnd(span: ReadableSpan): void {
198
- this.wrappedProcessor.onEnd(span);
199
- }
200
-
201
- forceFlush(): Promise<void> {
202
- return this.wrappedProcessor.forceFlush();
203
- }
204
-
205
- shutdown(): Promise<void> {
206
- return this.wrappedProcessor.shutdown();
207
- }
208
- }
209
-
210
- /**
211
- * Export built-in patterns for advanced users who want to compose their own normalizers
212
- */
213
- export { NORMALIZER_PATTERNS, NORMALIZER_PRESETS };
@@ -1,27 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
-
3
- /**
4
- * Deterministic JSON stringify with sorted object keys, so two structurally
5
- * equal values always produce the same string regardless of key insertion
6
- * order. Shared by `defineEvent` and the validation layer for stable schema
7
- * hashes.
8
- */
9
- export function stableStringify(value: unknown): string {
10
- if (value === null || value === undefined || typeof value !== 'object') {
11
- return JSON.stringify(value);
12
- }
13
- if (Array.isArray(value)) {
14
- return '[' + value.map((v) => stableStringify(v)).join(',') + ']';
15
- }
16
- const obj = value as Record<string, unknown>;
17
- const body = Object.keys(obj)
18
- .toSorted()
19
- .map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k]))
20
- .join(',');
21
- return '{' + body + '}';
22
- }
23
-
24
- /** Stable sha256 of any JSON-serializable value. */
25
- export function hashJson(value: unknown): string {
26
- return createHash('sha256').update(stableStringify(value)).digest('hex');
27
- }