@geekmidas/telescope 9.0.2 → 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 (128) hide show
  1. package/README.md +14 -4
  2. package/dist/{http-D5kn3_Mt.d.mts → http-DULM1u2L.d.cts} +3 -3
  3. package/dist/{http-Dvca6fC3.d.cts.map → http-DULM1u2L.d.cts.map} +1 -1
  4. package/dist/{http-Dvca6fC3.d.cts → http-GB-Nlvwc.d.mts} +3 -3
  5. package/dist/{http-D5kn3_Mt.d.mts.map → http-GB-Nlvwc.d.mts.map} +1 -1
  6. package/dist/instrumentation/http.d.cts +1 -1
  7. package/dist/instrumentation/http.d.mts +1 -1
  8. package/dist/instrumentation/index.cjs +2 -2
  9. package/dist/instrumentation/index.d.cts +3 -3
  10. package/dist/instrumentation/index.d.mts +3 -3
  11. package/dist/instrumentation/index.mjs +2 -2
  12. package/dist/instrumentation/middleware.cjs +1 -1
  13. package/dist/instrumentation/middleware.d.cts +1 -1
  14. package/dist/instrumentation/middleware.d.mts +1 -1
  15. package/dist/instrumentation/middleware.mjs +1 -1
  16. package/dist/instrumentation/otel.cjs +1 -1
  17. package/dist/instrumentation/otel.d.cts +1 -1
  18. package/dist/instrumentation/otel.d.mts +1 -1
  19. package/dist/instrumentation/otel.mjs +1 -1
  20. package/dist/logger/console.d.cts.map +1 -1
  21. package/dist/logger/console.d.mts.map +1 -1
  22. package/dist/logger/pino.d.cts +2 -2
  23. package/dist/{middleware-DqPQpcup.cjs → middleware-Bcq6XXGG.cjs} +2 -2
  24. package/dist/middleware-Bcq6XXGG.cjs.map +1 -0
  25. package/dist/{middleware-4-GrQlOT.mjs → middleware-D1XEHvyy.mjs} +2 -2
  26. package/dist/middleware-D1XEHvyy.mjs.map +1 -0
  27. package/dist/{middleware-CA84nsNN.d.cts → middleware-PeyS-9Ny.d.cts} +2 -2
  28. package/dist/{middleware-CA84nsNN.d.cts.map → middleware-PeyS-9Ny.d.cts.map} +1 -1
  29. package/dist/{middleware-DAceVpfw.d.mts → middleware-oHvo_ZgU.d.mts} +2 -2
  30. package/dist/{middleware-DAceVpfw.d.mts.map → middleware-oHvo_ZgU.d.mts.map} +1 -1
  31. package/dist/{otel-40udNP5g.mjs → otel-B9bNoeZ2.mjs} +3 -3
  32. package/dist/otel-B9bNoeZ2.mjs.map +1 -0
  33. package/dist/{otel-1bJPb2Z7.d.mts → otel-Bk89b1lX.d.cts} +3 -3
  34. package/dist/{otel-1bJPb2Z7.d.mts.map → otel-Bk89b1lX.d.cts.map} +1 -1
  35. package/dist/{otel-COrpz_GZ.cjs → otel-BzjOtVxZ.cjs} +3 -3
  36. package/dist/otel-BzjOtVxZ.cjs.map +1 -0
  37. package/dist/{otel-SBVoVEHr.d.cts → otel-lPMsM26e.d.mts} +3 -3
  38. package/dist/{otel-SBVoVEHr.d.cts.map → otel-lPMsM26e.d.mts.map} +1 -1
  39. package/dist/storage/kysely.cjs +48 -38
  40. package/dist/storage/kysely.cjs.map +1 -1
  41. package/dist/storage/kysely.d.cts +29 -9
  42. package/dist/storage/kysely.d.cts.map +1 -1
  43. package/dist/storage/kysely.d.mts +29 -9
  44. package/dist/storage/kysely.d.mts.map +1 -1
  45. package/dist/storage/kysely.mjs +48 -38
  46. package/dist/storage/kysely.mjs.map +1 -1
  47. package/dist/tracing-BX8uY2nR.d.cts.map +1 -1
  48. package/dist/types-B1EIa_cQ.d.mts.map +1 -1
  49. package/dist/types-l7Rlb5Mg.d.cts.map +1 -1
  50. package/package.json +6 -3
  51. package/CHANGELOG.md +0 -72
  52. package/dist/middleware-4-GrQlOT.mjs.map +0 -1
  53. package/dist/middleware-DqPQpcup.cjs.map +0 -1
  54. package/dist/otel-40udNP5g.mjs.map +0 -1
  55. package/dist/otel-COrpz_GZ.cjs.map +0 -1
  56. package/src/Telescope.ts +0 -561
  57. package/src/__tests__/Telescope.spec.ts +0 -438
  58. package/src/__tests__/pino-transport.spec.ts +0 -115
  59. package/src/__tests__/redact.spec.ts +0 -336
  60. package/src/adapters/__tests__/lambda.spec.ts +0 -318
  61. package/src/adapters/__tests__/middy.spec.ts +0 -741
  62. package/src/adapters/index.ts +0 -22
  63. package/src/adapters/lambda.ts +0 -432
  64. package/src/adapters/middy.ts +0 -402
  65. package/src/adapters/types.ts +0 -181
  66. package/src/core.ts +0 -51
  67. package/src/index.ts +0 -34
  68. package/src/instrumentation/__tests__/core.spec.ts +0 -248
  69. package/src/instrumentation/__tests__/hono.spec.ts +0 -348
  70. package/src/instrumentation/__tests__/middleware.spec.ts +0 -528
  71. package/src/instrumentation/__tests__/otel.spec.ts +0 -291
  72. package/src/instrumentation/__tests__/tracing.spec.ts +0 -208
  73. package/src/instrumentation/core.ts +0 -228
  74. package/src/instrumentation/hono.ts +0 -270
  75. package/src/instrumentation/http.ts +0 -306
  76. package/src/instrumentation/index.ts +0 -90
  77. package/src/instrumentation/middleware.ts +0 -297
  78. package/src/instrumentation/otel.ts +0 -271
  79. package/src/instrumentation/setup.ts +0 -246
  80. package/src/instrumentation/tracing.ts +0 -145
  81. package/src/logger/__tests__/console.spec.ts +0 -366
  82. package/src/logger/__tests__/pino.spec.ts +0 -217
  83. package/src/logger/console.ts +0 -230
  84. package/src/logger/pino.ts +0 -201
  85. package/src/metrics/MetricsAggregator.ts +0 -555
  86. package/src/metrics/__tests__/MetricsAggregator.spec.ts +0 -298
  87. package/src/metrics/index.ts +0 -2
  88. package/src/metrics/storage/InMemoryMetricsStorage.ts +0 -380
  89. package/src/metrics/storage/MetricsStorage.ts +0 -74
  90. package/src/metrics/storage/index.ts +0 -3
  91. package/src/metrics/storage/types.ts +0 -243
  92. package/src/otlp/__tests__/receiver.spec.ts +0 -367
  93. package/src/otlp/__tests__/transformer.spec.ts +0 -455
  94. package/src/otlp/hono.ts +0 -120
  95. package/src/otlp/index.ts +0 -22
  96. package/src/otlp/receiver.ts +0 -162
  97. package/src/otlp/transformer.ts +0 -367
  98. package/src/otlp/types.ts +0 -364
  99. package/src/redact.ts +0 -116
  100. package/src/server/__tests__/hono.spec.ts +0 -814
  101. package/src/server/hono.ts +0 -442
  102. package/src/storage/__tests__/kysely.spec.ts +0 -715
  103. package/src/storage/__tests__/memory.spec.ts +0 -694
  104. package/src/storage/kysely.ts +0 -599
  105. package/src/storage/memory.ts +0 -204
  106. package/src/types.ts +0 -447
  107. package/src/ui-assets.ts +0 -40
  108. package/tsconfig.json +0 -9
  109. package/ui/CHANGELOG.md +0 -7
  110. package/ui/index.html +0 -12
  111. package/ui/node_modules/.bin/tsc +0 -21
  112. package/ui/node_modules/.bin/tsserver +0 -21
  113. package/ui/node_modules/.bin/vite +0 -21
  114. package/ui/package.json +0 -24
  115. package/ui/src/App.tsx +0 -595
  116. package/ui/src/api.ts +0 -88
  117. package/ui/src/components/ExceptionDetail.tsx +0 -100
  118. package/ui/src/components/FilterBar.tsx +0 -162
  119. package/ui/src/components/LogDetail.tsx +0 -91
  120. package/ui/src/components/RequestDetail.tsx +0 -143
  121. package/ui/src/main.tsx +0 -10
  122. package/ui/src/styles.css +0 -11
  123. package/ui/src/types.ts +0 -63
  124. package/ui/src/vite-env.d.ts +0 -1
  125. package/ui/src/vite-plugin-gkm-config.ts +0 -54
  126. package/ui/tsconfig.json +0 -20
  127. package/ui/tsconfig.tsbuildinfo +0 -1
  128. 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
- });