@geekmidas/telescope 1.1.1 → 10.0.0-alpha.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 (167) hide show
  1. package/README.md +14 -4
  2. package/dist/{hono-Bz9PQQzj.mjs → hono-Bw-ey6CT.mjs} +2 -2
  3. package/dist/{hono-Bz9PQQzj.mjs.map → hono-Bw-ey6CT.mjs.map} +1 -1
  4. package/dist/{hono-Z_JIICmt.cjs → hono-CPHX7FAm.cjs} +2 -2
  5. package/dist/{hono-Z_JIICmt.cjs.map → hono-CPHX7FAm.cjs.map} +1 -1
  6. package/dist/{http-Crsriv32.mjs → http-BIvr0YdI.mjs} +1 -1
  7. package/dist/{http-Crsriv32.mjs.map → http-BIvr0YdI.mjs.map} +1 -1
  8. package/dist/{http-JycFRfSe.cjs → http-BsqmhurZ.cjs} +1 -1
  9. package/dist/{http-JycFRfSe.cjs.map → http-BsqmhurZ.cjs.map} +1 -1
  10. package/dist/{http-D5kn3_Mt.d.mts → http-DULM1u2L.d.cts} +3 -3
  11. package/dist/{http-D5kn3_Mt.d.mts.map → http-DULM1u2L.d.cts.map} +1 -1
  12. package/dist/{http-Dvca6fC3.d.cts → http-GB-Nlvwc.d.mts} +3 -3
  13. package/dist/{http-Dvca6fC3.d.cts.map → http-GB-Nlvwc.d.mts.map} +1 -1
  14. package/dist/instrumentation/hono.cjs +2 -2
  15. package/dist/instrumentation/hono.mjs +2 -2
  16. package/dist/instrumentation/http.cjs +1 -1
  17. package/dist/instrumentation/http.d.cts +1 -1
  18. package/dist/instrumentation/http.d.mts +1 -1
  19. package/dist/instrumentation/http.mjs +1 -1
  20. package/dist/instrumentation/index.cjs +5 -5
  21. package/dist/instrumentation/index.d.cts +3 -3
  22. package/dist/instrumentation/index.d.mts +3 -3
  23. package/dist/instrumentation/index.mjs +5 -5
  24. package/dist/instrumentation/middleware.cjs +2 -2
  25. package/dist/instrumentation/middleware.d.cts +1 -1
  26. package/dist/instrumentation/middleware.d.mts +1 -1
  27. package/dist/instrumentation/middleware.mjs +2 -2
  28. package/dist/instrumentation/otel.cjs +2 -2
  29. package/dist/instrumentation/otel.d.cts +1 -1
  30. package/dist/instrumentation/otel.d.mts +1 -1
  31. package/dist/instrumentation/otel.mjs +2 -2
  32. package/dist/instrumentation/setup.cjs +1 -1
  33. package/dist/instrumentation/setup.mjs +1 -1
  34. package/dist/logger/console.d.cts.map +1 -1
  35. package/dist/{middleware-D3v5SnSs.cjs → middleware-Bcq6XXGG.cjs} +3 -3
  36. package/dist/middleware-Bcq6XXGG.cjs.map +1 -0
  37. package/dist/{middleware-Dy1ctr2N.mjs → middleware-D1XEHvyy.mjs} +3 -3
  38. package/dist/middleware-D1XEHvyy.mjs.map +1 -0
  39. package/dist/{middleware-CA84nsNN.d.cts → middleware-PeyS-9Ny.d.cts} +2 -2
  40. package/dist/{middleware-CA84nsNN.d.cts.map → middleware-PeyS-9Ny.d.cts.map} +1 -1
  41. package/dist/{middleware-DAceVpfw.d.mts → middleware-oHvo_ZgU.d.mts} +2 -2
  42. package/dist/{middleware-DAceVpfw.d.mts.map → middleware-oHvo_ZgU.d.mts.map} +1 -1
  43. package/dist/{otel-DPKNyJ0t.mjs → otel-B9bNoeZ2.mjs} +4 -4
  44. package/dist/otel-B9bNoeZ2.mjs.map +1 -0
  45. package/dist/{otel-1bJPb2Z7.d.mts → otel-Bk89b1lX.d.cts} +3 -3
  46. package/dist/{otel-1bJPb2Z7.d.mts.map → otel-Bk89b1lX.d.cts.map} +1 -1
  47. package/dist/{otel-DuiBeBPU.cjs → otel-BzjOtVxZ.cjs} +4 -4
  48. package/dist/otel-BzjOtVxZ.cjs.map +1 -0
  49. package/dist/{otel-SBVoVEHr.d.cts → otel-lPMsM26e.d.mts} +3 -3
  50. package/dist/{otel-SBVoVEHr.d.cts.map → otel-lPMsM26e.d.mts.map} +1 -1
  51. package/dist/otlp/hono.cjs +3 -3
  52. package/dist/otlp/hono.mjs +3 -3
  53. package/dist/otlp/index.cjs +3 -3
  54. package/dist/otlp/index.mjs +3 -3
  55. package/dist/otlp/receiver.cjs +3 -3
  56. package/dist/otlp/receiver.mjs +3 -3
  57. package/dist/otlp/transformer.cjs +2 -2
  58. package/dist/otlp/transformer.mjs +2 -2
  59. package/dist/otlp/types.cjs +1 -1
  60. package/dist/otlp/types.mjs +1 -1
  61. package/dist/{receiver-q4f7eirs.mjs → receiver-D46dfkYI.mjs} +2 -2
  62. package/dist/{receiver-q4f7eirs.mjs.map → receiver-D46dfkYI.mjs.map} +1 -1
  63. package/dist/{receiver-BW_4go_X.cjs → receiver-slCoAFat.cjs} +2 -2
  64. package/dist/{receiver-BW_4go_X.cjs.map → receiver-slCoAFat.cjs.map} +1 -1
  65. package/dist/{setup-BhCp2LUp.mjs → setup-DC9JMEYZ.mjs} +1 -1
  66. package/dist/{setup-BhCp2LUp.mjs.map → setup-DC9JMEYZ.mjs.map} +1 -1
  67. package/dist/{setup-_uT4L_x5.cjs → setup-DufxQpi9.cjs} +1 -1
  68. package/dist/{setup-_uT4L_x5.cjs.map → setup-DufxQpi9.cjs.map} +1 -1
  69. package/dist/storage/kysely.cjs +48 -38
  70. package/dist/storage/kysely.cjs.map +1 -1
  71. package/dist/storage/kysely.d.cts +29 -9
  72. package/dist/storage/kysely.d.cts.map +1 -1
  73. package/dist/storage/kysely.d.mts +29 -9
  74. package/dist/storage/kysely.d.mts.map +1 -1
  75. package/dist/storage/kysely.mjs +48 -38
  76. package/dist/storage/kysely.mjs.map +1 -1
  77. package/dist/tracing-BX8uY2nR.d.cts.map +1 -1
  78. package/dist/tracing-PnECEbEu.d.mts.map +1 -1
  79. package/dist/{transformer-CFrJQkUg.mjs → transformer-CFQ4WFk6.mjs} +2 -2
  80. package/dist/{transformer-CFrJQkUg.mjs.map → transformer-CFQ4WFk6.mjs.map} +1 -1
  81. package/dist/{transformer-DvDYyFRQ.cjs → transformer-Csl6fbgv.cjs} +2 -2
  82. package/dist/{transformer-DvDYyFRQ.cjs.map → transformer-Csl6fbgv.cjs.map} +1 -1
  83. package/dist/types-B1EIa_cQ.d.mts.map +1 -1
  84. package/dist/{types-BBeTDpHr.cjs → types-BMiHgsi4.cjs} +1 -1
  85. package/dist/{types-BBeTDpHr.cjs.map → types-BMiHgsi4.cjs.map} +1 -1
  86. package/dist/{types-CXYLhG_G.mjs → types-Cj59nqy7.mjs} +1 -1
  87. package/dist/{types-CXYLhG_G.mjs.map → types-Cj59nqy7.mjs.map} +1 -1
  88. package/dist/types-l7Rlb5Mg.d.cts.map +1 -1
  89. package/package.json +6 -3
  90. package/CHANGELOG.md +0 -51
  91. package/dist/middleware-D3v5SnSs.cjs.map +0 -1
  92. package/dist/middleware-Dy1ctr2N.mjs.map +0 -1
  93. package/dist/otel-DPKNyJ0t.mjs.map +0 -1
  94. package/dist/otel-DuiBeBPU.cjs.map +0 -1
  95. package/src/Telescope.ts +0 -561
  96. package/src/__tests__/Telescope.spec.ts +0 -438
  97. package/src/__tests__/pino-transport.spec.ts +0 -115
  98. package/src/__tests__/redact.spec.ts +0 -336
  99. package/src/adapters/__tests__/lambda.spec.ts +0 -318
  100. package/src/adapters/__tests__/middy.spec.ts +0 -741
  101. package/src/adapters/index.ts +0 -22
  102. package/src/adapters/lambda.ts +0 -432
  103. package/src/adapters/middy.ts +0 -402
  104. package/src/adapters/types.ts +0 -181
  105. package/src/core.ts +0 -51
  106. package/src/index.ts +0 -34
  107. package/src/instrumentation/__tests__/core.spec.ts +0 -248
  108. package/src/instrumentation/__tests__/hono.spec.ts +0 -348
  109. package/src/instrumentation/__tests__/middleware.spec.ts +0 -528
  110. package/src/instrumentation/__tests__/otel.spec.ts +0 -291
  111. package/src/instrumentation/__tests__/tracing.spec.ts +0 -208
  112. package/src/instrumentation/core.ts +0 -228
  113. package/src/instrumentation/hono.ts +0 -270
  114. package/src/instrumentation/http.ts +0 -306
  115. package/src/instrumentation/index.ts +0 -90
  116. package/src/instrumentation/middleware.ts +0 -297
  117. package/src/instrumentation/otel.ts +0 -271
  118. package/src/instrumentation/setup.ts +0 -246
  119. package/src/instrumentation/tracing.ts +0 -145
  120. package/src/logger/__tests__/console.spec.ts +0 -366
  121. package/src/logger/__tests__/pino.spec.ts +0 -217
  122. package/src/logger/console.ts +0 -230
  123. package/src/logger/pino.ts +0 -201
  124. package/src/metrics/MetricsAggregator.ts +0 -555
  125. package/src/metrics/__tests__/MetricsAggregator.spec.ts +0 -298
  126. package/src/metrics/index.ts +0 -2
  127. package/src/metrics/storage/InMemoryMetricsStorage.ts +0 -380
  128. package/src/metrics/storage/MetricsStorage.ts +0 -74
  129. package/src/metrics/storage/index.ts +0 -3
  130. package/src/metrics/storage/types.ts +0 -243
  131. package/src/otlp/__tests__/receiver.spec.ts +0 -367
  132. package/src/otlp/__tests__/transformer.spec.ts +0 -455
  133. package/src/otlp/hono.ts +0 -120
  134. package/src/otlp/index.ts +0 -22
  135. package/src/otlp/receiver.ts +0 -162
  136. package/src/otlp/transformer.ts +0 -367
  137. package/src/otlp/types.ts +0 -364
  138. package/src/redact.ts +0 -116
  139. package/src/server/__tests__/hono.spec.ts +0 -814
  140. package/src/server/hono.ts +0 -442
  141. package/src/storage/__tests__/kysely.spec.ts +0 -715
  142. package/src/storage/__tests__/memory.spec.ts +0 -694
  143. package/src/storage/kysely.ts +0 -599
  144. package/src/storage/memory.ts +0 -204
  145. package/src/types.ts +0 -447
  146. package/src/ui-assets.ts +0 -40
  147. package/tsconfig.json +0 -9
  148. package/ui/CHANGELOG.md +0 -7
  149. package/ui/index.html +0 -12
  150. package/ui/node_modules/.bin/tsc +0 -21
  151. package/ui/node_modules/.bin/tsserver +0 -21
  152. package/ui/node_modules/.bin/vite +0 -21
  153. package/ui/package.json +0 -24
  154. package/ui/src/App.tsx +0 -595
  155. package/ui/src/api.ts +0 -88
  156. package/ui/src/components/ExceptionDetail.tsx +0 -100
  157. package/ui/src/components/FilterBar.tsx +0 -162
  158. package/ui/src/components/LogDetail.tsx +0 -91
  159. package/ui/src/components/RequestDetail.tsx +0 -143
  160. package/ui/src/main.tsx +0 -10
  161. package/ui/src/styles.css +0 -11
  162. package/ui/src/types.ts +0 -63
  163. package/ui/src/vite-env.d.ts +0 -1
  164. package/ui/src/vite-plugin-gkm-config.ts +0 -54
  165. package/ui/tsconfig.json +0 -20
  166. package/ui/tsconfig.tsbuildinfo +0 -1
  167. package/ui/vite.config.ts +0 -13
@@ -1,248 +0,0 @@
1
- import type { SpanExporter } from '@opentelemetry/sdk-trace-base';
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
- import {
4
- createSpanProcessor,
5
- flushTelemetry,
6
- getRecommendedStrategy,
7
- isTelemetryInitialized,
8
- NoopSpanProcessor,
9
- setGlobalLogProcessor,
10
- setGlobalSpanProcessor,
11
- shutdownTelemetry,
12
- withTelemetryFlush,
13
- } from '../core';
14
-
15
- describe('core telemetry utilities', () => {
16
- beforeEach(async () => {
17
- // Reset state before each test
18
- await shutdownTelemetry();
19
- });
20
-
21
- afterEach(async () => {
22
- await shutdownTelemetry();
23
- });
24
-
25
- describe('createSpanProcessor', () => {
26
- const mockExporter: SpanExporter = {
27
- export: vi.fn(),
28
- shutdown: vi.fn().mockResolvedValue(undefined),
29
- };
30
-
31
- it('should create a simple span processor when strategy is simple', () => {
32
- const processor = createSpanProcessor(mockExporter, {
33
- strategy: 'simple',
34
- });
35
-
36
- expect(processor).toBeDefined();
37
- expect(processor.forceFlush).toBeDefined();
38
- });
39
-
40
- it('should create a batch span processor when strategy is batch', () => {
41
- const processor = createSpanProcessor(mockExporter, {
42
- strategy: 'batch',
43
- });
44
-
45
- expect(processor).toBeDefined();
46
- expect(processor.forceFlush).toBeDefined();
47
- });
48
-
49
- it('should create batch processor with custom options', () => {
50
- const processor = createSpanProcessor(mockExporter, {
51
- strategy: 'batch',
52
- maxQueueSize: 1024,
53
- scheduledDelayMillis: 1000,
54
- exportTimeoutMillis: 10000,
55
- maxExportBatchSize: 256,
56
- });
57
-
58
- expect(processor).toBeDefined();
59
- });
60
-
61
- it('should default to batch when strategy is not simple', () => {
62
- const processor = createSpanProcessor(mockExporter, {
63
- strategy: 'batch',
64
- });
65
-
66
- expect(processor).toBeDefined();
67
- });
68
- });
69
-
70
- describe('getRecommendedStrategy', () => {
71
- it('should return simple for lambda environment', () => {
72
- expect(getRecommendedStrategy('lambda')).toBe('simple');
73
- });
74
-
75
- it('should return simple for edge environment', () => {
76
- expect(getRecommendedStrategy('edge')).toBe('simple');
77
- });
78
-
79
- it('should return batch for server environment', () => {
80
- expect(getRecommendedStrategy('server')).toBe('batch');
81
- });
82
-
83
- it('should return batch for custom environment', () => {
84
- expect(getRecommendedStrategy('custom')).toBe('batch');
85
- });
86
- });
87
-
88
- describe('global state management', () => {
89
- it('should track initialization state', () => {
90
- expect(isTelemetryInitialized()).toBe(false);
91
-
92
- const processor = new NoopSpanProcessor();
93
- setGlobalSpanProcessor(processor);
94
-
95
- expect(isTelemetryInitialized()).toBe(true);
96
- });
97
-
98
- it('should set and use global log processor', () => {
99
- const mockLogProcessor = {
100
- forceFlush: vi.fn().mockResolvedValue(undefined),
101
- shutdown: vi.fn().mockResolvedValue(undefined),
102
- };
103
-
104
- setGlobalLogProcessor(mockLogProcessor);
105
- // The log processor is stored internally
106
- });
107
- });
108
-
109
- describe('flushTelemetry', () => {
110
- it('should flush span processor', async () => {
111
- const processor = new NoopSpanProcessor();
112
- const flushSpy = vi.spyOn(processor, 'forceFlush');
113
- setGlobalSpanProcessor(processor);
114
-
115
- await flushTelemetry();
116
-
117
- expect(flushSpy).toHaveBeenCalled();
118
- });
119
-
120
- it('should flush log processor if available', async () => {
121
- const mockLogProcessor = {
122
- forceFlush: vi.fn().mockResolvedValue(undefined),
123
- };
124
- setGlobalLogProcessor(mockLogProcessor);
125
-
126
- await flushTelemetry();
127
-
128
- expect(mockLogProcessor.forceFlush).toHaveBeenCalled();
129
- });
130
-
131
- it('should handle flush timeout gracefully', async () => {
132
- const slowProcessor = {
133
- forceFlush: vi
134
- .fn()
135
- .mockImplementation(
136
- () => new Promise((resolve) => setTimeout(resolve, 100000)),
137
- ),
138
- onStart: vi.fn(),
139
- onEnd: vi.fn(),
140
- shutdown: vi.fn().mockResolvedValue(undefined),
141
- };
142
- setGlobalSpanProcessor(slowProcessor);
143
-
144
- // Should not throw, should timeout gracefully
145
- await flushTelemetry(10); // Very short timeout
146
- });
147
-
148
- it('should do nothing if not initialized', async () => {
149
- // No processor set, should complete without error
150
- await flushTelemetry();
151
- });
152
- });
153
-
154
- describe('shutdownTelemetry', () => {
155
- it('should shutdown span processor', async () => {
156
- const processor = new NoopSpanProcessor();
157
- const shutdownSpy = vi.spyOn(processor, 'shutdown');
158
- setGlobalSpanProcessor(processor);
159
-
160
- await shutdownTelemetry();
161
-
162
- expect(shutdownSpy).toHaveBeenCalled();
163
- expect(isTelemetryInitialized()).toBe(false);
164
- });
165
-
166
- it('should shutdown log processor if available', async () => {
167
- const mockLogProcessor = {
168
- forceFlush: vi.fn().mockResolvedValue(undefined),
169
- shutdown: vi.fn().mockResolvedValue(undefined),
170
- };
171
- setGlobalLogProcessor(mockLogProcessor);
172
-
173
- await shutdownTelemetry();
174
-
175
- expect(mockLogProcessor.shutdown).toHaveBeenCalled();
176
- });
177
-
178
- it('should handle multiple shutdowns gracefully', async () => {
179
- const processor = new NoopSpanProcessor();
180
- setGlobalSpanProcessor(processor);
181
-
182
- await shutdownTelemetry();
183
- await shutdownTelemetry(); // Second call should be safe
184
- });
185
- });
186
-
187
- describe('withTelemetryFlush', () => {
188
- it('should wrap handler and flush after execution', async () => {
189
- const processor = new NoopSpanProcessor();
190
- const flushSpy = vi.spyOn(processor, 'forceFlush');
191
- setGlobalSpanProcessor(processor);
192
-
193
- const mockHandler = vi.fn().mockResolvedValue({ result: 'success' });
194
- const wrappedHandler = withTelemetryFlush(mockHandler);
195
-
196
- const result = await wrappedHandler({ event: 'test' }, {});
197
-
198
- expect(result).toEqual({ result: 'success' });
199
- expect(mockHandler).toHaveBeenCalledWith({ event: 'test' }, {});
200
- expect(flushSpy).toHaveBeenCalled();
201
- });
202
-
203
- it('should flush even when handler throws', async () => {
204
- const processor = new NoopSpanProcessor();
205
- const flushSpy = vi.spyOn(processor, 'forceFlush');
206
- setGlobalSpanProcessor(processor);
207
-
208
- const mockHandler = vi.fn().mockRejectedValue(new Error('Handler error'));
209
- const wrappedHandler = withTelemetryFlush(mockHandler);
210
-
211
- await expect(wrappedHandler({ event: 'test' }, {})).rejects.toThrow(
212
- 'Handler error',
213
- );
214
- expect(flushSpy).toHaveBeenCalled();
215
- });
216
- });
217
-
218
- describe('NoopSpanProcessor', () => {
219
- it('should implement SpanProcessor interface', () => {
220
- const processor = new NoopSpanProcessor();
221
-
222
- expect(processor.forceFlush).toBeDefined();
223
- expect(processor.onStart).toBeDefined();
224
- expect(processor.onEnd).toBeDefined();
225
- expect(processor.shutdown).toBeDefined();
226
- });
227
-
228
- it('should resolve forceFlush immediately', async () => {
229
- const processor = new NoopSpanProcessor();
230
- await processor.forceFlush();
231
- });
232
-
233
- it('should resolve shutdown immediately', async () => {
234
- const processor = new NoopSpanProcessor();
235
- await processor.shutdown();
236
- });
237
-
238
- it('should do nothing on onStart', () => {
239
- const processor = new NoopSpanProcessor();
240
- processor.onStart({} as any);
241
- });
242
-
243
- it('should do nothing on onEnd', () => {
244
- const processor = new NoopSpanProcessor();
245
- processor.onEnd({} as any);
246
- });
247
- });
248
- });
@@ -1,348 +0,0 @@
1
- import { trace } from '@opentelemetry/api';
2
- import {
3
- BasicTracerProvider,
4
- InMemorySpanExporter,
5
- SimpleSpanProcessor,
6
- } from '@opentelemetry/sdk-trace-base';
7
- import { Hono } from 'hono';
8
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
9
- import {
10
- getSpanFromContext,
11
- getTraceContextFromHono,
12
- honoTelemetryMiddleware,
13
- withHonoSpanContext,
14
- } from '../hono';
15
-
16
- describe('honoTelemetryMiddleware', () => {
17
- let app: Hono;
18
- let exporter: InMemorySpanExporter;
19
- let provider: BasicTracerProvider;
20
-
21
- beforeEach(() => {
22
- // Set up real OpenTelemetry with in-memory exporter
23
- exporter = new InMemorySpanExporter();
24
- provider = new BasicTracerProvider();
25
- provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
26
- provider.register();
27
-
28
- app = new Hono();
29
- });
30
-
31
- afterEach(async () => {
32
- exporter.reset();
33
- await provider.shutdown();
34
- trace.disable();
35
- });
36
-
37
- describe('span creation', () => {
38
- it('should create span for GET requests', async () => {
39
- app.use('*', honoTelemetryMiddleware());
40
- app.get('/api/users', (c) => {
41
- const span = getSpanFromContext(c);
42
- expect(span).toBeDefined();
43
- return c.json({ users: [] });
44
- });
45
-
46
- const response = await app.request('/api/users');
47
-
48
- expect(response.status).toBe(200);
49
- const spans = exporter.getFinishedSpans();
50
- expect(spans).toHaveLength(1);
51
- expect(spans[0].name).toBe('GET /api/users');
52
- expect(spans[0].attributes['http.request.method']).toBe('GET');
53
- });
54
-
55
- it('should create span for POST requests with attributes', async () => {
56
- app.use('*', honoTelemetryMiddleware());
57
- app.post('/api/users', (c) => c.json({ id: '123' }, 201));
58
-
59
- const response = await app.request('/api/users', {
60
- method: 'POST',
61
- headers: {
62
- 'Content-Type': 'application/json',
63
- 'User-Agent': 'test-agent',
64
- },
65
- body: JSON.stringify({ name: 'John' }),
66
- });
67
-
68
- expect(response.status).toBe(201);
69
- const spans = exporter.getFinishedSpans();
70
- expect(spans[0].attributes['http.request.method']).toBe('POST');
71
- expect(spans[0].attributes['http.response.status_code']).toBe(201);
72
- expect(spans[0].attributes['user_agent.original']).toBe('test-agent');
73
- });
74
-
75
- it('should handle errors and record error status', async () => {
76
- // Note: Hono catches errors internally before they propagate to middleware,
77
- // so we can only detect the 500 status code, not record the actual exception
78
- app.use('*', honoTelemetryMiddleware());
79
- app.get('/api/error', () => {
80
- throw new Error('Test error');
81
- });
82
-
83
- const response = await app.request('/api/error');
84
-
85
- expect(response.status).toBe(500);
86
- const spans = exporter.getFinishedSpans();
87
- expect(spans).toHaveLength(1);
88
- expect(spans[0].attributes['http.response.status_code']).toBe(500);
89
- expect(spans[0].status.code).toBe(2); // SpanStatusCode.ERROR
90
- });
91
-
92
- it('should record 4xx status codes', async () => {
93
- app.use('*', honoTelemetryMiddleware());
94
- app.get('/api/users/:id', (c) => c.json({ error: 'Not found' }, 404));
95
-
96
- const response = await app.request('/api/users/999');
97
-
98
- expect(response.status).toBe(404);
99
- const spans = exporter.getFinishedSpans();
100
- expect(spans[0].attributes['http.response.status_code']).toBe(404);
101
- expect(spans[0].status.code).toBe(2); // SpanStatusCode.ERROR
102
- });
103
- });
104
-
105
- describe('path filtering', () => {
106
- it('should skip paths in ignorePaths (exact match)', async () => {
107
- app.use(
108
- '*',
109
- honoTelemetryMiddleware({
110
- ignorePaths: ['/health', '/metrics'],
111
- }),
112
- );
113
- app.get('/health', (c) => c.json({ status: 'ok' }));
114
-
115
- await app.request('/health');
116
-
117
- expect(exporter.getFinishedSpans()).toHaveLength(0);
118
- });
119
-
120
- it('should skip paths matching wildcard patterns', async () => {
121
- app.use(
122
- '*',
123
- honoTelemetryMiddleware({
124
- ignorePaths: ['/__telescope/*'],
125
- }),
126
- );
127
- app.get('/__telescope/requests', (c) => c.json({ requests: [] }));
128
-
129
- await app.request('/__telescope/requests');
130
-
131
- expect(exporter.getFinishedSpans()).toHaveLength(0);
132
- });
133
-
134
- it('should trace paths not in ignorePaths', async () => {
135
- app.use(
136
- '*',
137
- honoTelemetryMiddleware({
138
- ignorePaths: ['/health'],
139
- }),
140
- );
141
- app.get('/api/users', (c) => c.json({ users: [] }));
142
-
143
- await app.request('/api/users');
144
-
145
- expect(exporter.getFinishedSpans()).toHaveLength(1);
146
- });
147
- });
148
-
149
- describe('custom shouldSkip', () => {
150
- it('should skip requests when shouldSkip returns true', async () => {
151
- app.use(
152
- '*',
153
- honoTelemetryMiddleware({
154
- shouldSkip: (c) => c.req.header('x-skip-trace') === 'true',
155
- }),
156
- );
157
- app.get('/api/users', (c) => c.json({ users: [] }));
158
-
159
- await app.request('/api/users', {
160
- headers: { 'x-skip-trace': 'true' },
161
- });
162
-
163
- expect(exporter.getFinishedSpans()).toHaveLength(0);
164
- });
165
-
166
- it('should trace requests when shouldSkip returns false', async () => {
167
- app.use(
168
- '*',
169
- honoTelemetryMiddleware({
170
- shouldSkip: (c) => c.req.header('x-skip-trace') === 'true',
171
- }),
172
- );
173
- app.get('/api/users', (c) => c.json({ users: [] }));
174
-
175
- await app.request('/api/users');
176
-
177
- expect(exporter.getFinishedSpans()).toHaveLength(1);
178
- });
179
- });
180
-
181
- describe('user ID extraction', () => {
182
- it('should extract user ID using custom function', async () => {
183
- app.use(
184
- '*',
185
- honoTelemetryMiddleware({
186
- getUserId: (c) => c.req.header('x-user-id'),
187
- }),
188
- );
189
- app.get('/api/profile', (c) => c.json({ name: 'John' }));
190
-
191
- await app.request('/api/profile', {
192
- headers: { 'x-user-id': 'user-123' },
193
- });
194
-
195
- const spans = exporter.getFinishedSpans();
196
- expect(spans[0].attributes['enduser.id']).toBe('user-123');
197
- });
198
-
199
- it('should not add user attribute when getUserId returns undefined', async () => {
200
- app.use(
201
- '*',
202
- honoTelemetryMiddleware({
203
- getUserId: () => undefined,
204
- }),
205
- );
206
- app.get('/api/users', (c) => c.json({ users: [] }));
207
-
208
- await app.request('/api/users');
209
-
210
- const spans = exporter.getFinishedSpans();
211
- expect(spans[0].attributes['enduser.id']).toBeUndefined();
212
- });
213
- });
214
-
215
- describe('client IP extraction', () => {
216
- it('should extract IP from x-forwarded-for header', async () => {
217
- app.use('*', honoTelemetryMiddleware());
218
- app.get('/api/users', (c) => c.json({ users: [] }));
219
-
220
- await app.request('/api/users', {
221
- headers: { 'x-forwarded-for': '192.168.1.1, 10.0.0.1' },
222
- });
223
-
224
- const spans = exporter.getFinishedSpans();
225
- expect(spans[0].attributes['client.address']).toBe('192.168.1.1');
226
- });
227
-
228
- it('should extract IP from x-real-ip header', async () => {
229
- app.use('*', honoTelemetryMiddleware());
230
- app.get('/api/users', (c) => c.json({ users: [] }));
231
-
232
- await app.request('/api/users', {
233
- headers: { 'x-real-ip': '10.0.0.5' },
234
- });
235
-
236
- const spans = exporter.getFinishedSpans();
237
- expect(spans[0].attributes['client.address']).toBe('10.0.0.5');
238
- });
239
-
240
- it('should extract IP from cf-connecting-ip header (Cloudflare)', async () => {
241
- app.use('*', honoTelemetryMiddleware());
242
- app.get('/api/users', (c) => c.json({ users: [] }));
243
-
244
- await app.request('/api/users', {
245
- headers: { 'cf-connecting-ip': '203.0.113.195' },
246
- });
247
-
248
- const spans = exporter.getFinishedSpans();
249
- expect(spans[0].attributes['client.address']).toBe('203.0.113.195');
250
- });
251
- });
252
-
253
- describe('trace context helpers', () => {
254
- it('should provide trace context via getTraceContextFromHono', async () => {
255
- let traceContext: any;
256
-
257
- app.use('*', honoTelemetryMiddleware());
258
- app.get('/api/users', (c) => {
259
- traceContext = getTraceContextFromHono(c);
260
- return c.json({ users: [] });
261
- });
262
-
263
- await app.request('/api/users');
264
-
265
- expect(traceContext).toBeDefined();
266
- });
267
-
268
- it('should run function within span context via withHonoSpanContext', async () => {
269
- let result: string | undefined;
270
-
271
- app.use('*', honoTelemetryMiddleware());
272
- app.get('/api/users', async (c) => {
273
- result = await withHonoSpanContext(c, async () => 'executed');
274
- return c.json({ users: [] });
275
- });
276
-
277
- await app.request('/api/users');
278
-
279
- expect(result).toBe('executed');
280
- });
281
- });
282
-
283
- describe('request ID extraction', () => {
284
- it('should extract request ID from x-request-id header', async () => {
285
- app.use('*', honoTelemetryMiddleware());
286
- app.get('/api/users', (c) => c.json({ users: [] }));
287
-
288
- await app.request('/api/users', {
289
- headers: { 'x-request-id': 'req-abc123' },
290
- });
291
-
292
- const spans = exporter.getFinishedSpans();
293
- expect(spans[0].attributes['http.request.id']).toBe('req-abc123');
294
- });
295
-
296
- it('should extract request ID from x-amzn-requestid header', async () => {
297
- app.use('*', honoTelemetryMiddleware());
298
- app.get('/api/users', (c) => c.json({ users: [] }));
299
-
300
- await app.request('/api/users', {
301
- headers: { 'x-amzn-requestid': 'amzn-req-456' },
302
- });
303
-
304
- const spans = exporter.getFinishedSpans();
305
- expect(spans[0].attributes['http.request.id']).toBe('amzn-req-456');
306
- });
307
- });
308
-
309
- describe('URL attributes', () => {
310
- it('should capture full URL and path', async () => {
311
- app.use('*', honoTelemetryMiddleware());
312
- app.get('/api/users', (c) => c.json({ users: [] }));
313
-
314
- await app.request('http://localhost/api/users?page=1');
315
-
316
- const spans = exporter.getFinishedSpans();
317
- expect(spans[0].attributes['url.full']).toContain('/api/users');
318
- expect(spans[0].attributes['url.path']).toBe('/api/users');
319
- expect(spans[0].attributes['server.address']).toBe('localhost');
320
- });
321
-
322
- it('should capture scheme', async () => {
323
- app.use('*', honoTelemetryMiddleware());
324
- app.get('/api/users', (c) => c.json({ users: [] }));
325
-
326
- await app.request('https://api.example.com/api/users');
327
-
328
- const spans = exporter.getFinishedSpans();
329
- expect(spans[0].attributes['url.scheme']).toBe('https');
330
- });
331
- });
332
-
333
- describe('multiple requests', () => {
334
- it('should create separate spans for each request', async () => {
335
- app.use('*', honoTelemetryMiddleware());
336
- app.get('/api/users', (c) => c.json({ users: [] }));
337
- app.post('/api/users', (c) => c.json({ id: '1' }, 201));
338
-
339
- await app.request('/api/users');
340
- await app.request('/api/users', { method: 'POST' });
341
-
342
- const spans = exporter.getFinishedSpans();
343
- expect(spans).toHaveLength(2);
344
- expect(spans[0].attributes['http.request.method']).toBe('GET');
345
- expect(spans[1].attributes['http.request.method']).toBe('POST');
346
- });
347
- });
348
- });