@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,228 +0,0 @@
1
- import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';
2
- import {
3
- BatchSpanProcessor,
4
- type ReadableSpan,
5
- SimpleSpanProcessor,
6
- type Span,
7
- type SpanExporter,
8
- type SpanProcessor,
9
- } from '@opentelemetry/sdk-trace-base';
10
- import type {
11
- SpanProcessorOptions,
12
- SpanProcessorStrategy,
13
- } from '../adapters/types';
14
-
15
- /**
16
- * Global telemetry state for flush operations
17
- */
18
- interface TelemetryState {
19
- spanProcessor: SpanProcessor | null;
20
- logProcessor: unknown | null;
21
- initialized: boolean;
22
- }
23
-
24
- const state: TelemetryState = {
25
- spanProcessor: null,
26
- logProcessor: null,
27
- initialized: false,
28
- };
29
-
30
- /**
31
- * Create a span processor based on the strategy
32
- */
33
- export function createSpanProcessor(
34
- exporter: SpanExporter,
35
- options: SpanProcessorOptions,
36
- ): SpanProcessor {
37
- const { strategy, ...config } = options;
38
-
39
- if (strategy === 'simple') {
40
- return new SimpleSpanProcessor(exporter);
41
- }
42
-
43
- return new BatchSpanProcessor(exporter, {
44
- maxQueueSize: config.maxQueueSize ?? 2048,
45
- scheduledDelayMillis: config.scheduledDelayMillis ?? 5000,
46
- exportTimeoutMillis: config.exportTimeoutMillis ?? 30000,
47
- maxExportBatchSize: config.maxExportBatchSize ?? 512,
48
- });
49
- }
50
-
51
- /**
52
- * Get the recommended span processor strategy for an environment
53
- */
54
- export function getRecommendedStrategy(
55
- environment: 'server' | 'lambda' | 'edge' | 'custom',
56
- ): SpanProcessorStrategy {
57
- switch (environment) {
58
- case 'lambda':
59
- case 'edge':
60
- // Use simple processor for serverless - immediate export
61
- return 'simple';
62
- default:
63
- // Use batch processor for long-running servers - efficient batching
64
- return 'batch';
65
- }
66
- }
67
-
68
- /**
69
- * Set the global span processor for flush operations
70
- */
71
- export function setGlobalSpanProcessor(processor: SpanProcessor): void {
72
- state.spanProcessor = processor;
73
- state.initialized = true;
74
- }
75
-
76
- /**
77
- * Set the global log processor for flush operations
78
- */
79
- export function setGlobalLogProcessor(processor: unknown): void {
80
- state.logProcessor = processor;
81
- }
82
-
83
- /**
84
- * Check if telemetry is initialized
85
- */
86
- export function isTelemetryInitialized(): boolean {
87
- return state.initialized;
88
- }
89
-
90
- /**
91
- * Flush all pending telemetry data.
92
- * This is critical for serverless environments where the process may freeze.
93
- *
94
- * @example
95
- * ```typescript
96
- * // In a Lambda handler
97
- * export const handler = async (event, context) => {
98
- * try {
99
- * const result = await processRequest(event);
100
- * return result;
101
- * } finally {
102
- * // Flush before Lambda freezes
103
- * await flushTelemetry();
104
- * }
105
- * };
106
- * ```
107
- */
108
- export async function flushTelemetry(timeoutMs = 30000): Promise<void> {
109
- const promises: Promise<void>[] = [];
110
-
111
- // Flush span processor
112
- if (state.spanProcessor) {
113
- promises.push(
114
- Promise.race([
115
- state.spanProcessor.forceFlush(),
116
- new Promise<void>((_, reject) =>
117
- setTimeout(() => reject(new Error('Span flush timeout')), timeoutMs),
118
- ),
119
- ]).catch((error) => {
120
- if (diag.debug) {
121
- diag.debug(`Span flush error: ${error.message}`);
122
- }
123
- }),
124
- );
125
- }
126
-
127
- // Flush log processor if it has forceFlush method
128
- if (
129
- state.logProcessor &&
130
- typeof (state.logProcessor as { forceFlush?: () => Promise<void> })
131
- .forceFlush === 'function'
132
- ) {
133
- promises.push(
134
- Promise.race([
135
- (
136
- state.logProcessor as { forceFlush: () => Promise<void> }
137
- ).forceFlush(),
138
- new Promise<void>((_, reject) =>
139
- setTimeout(() => reject(new Error('Log flush timeout')), timeoutMs),
140
- ),
141
- ]).catch((error) => {
142
- if (diag.debug) {
143
- diag.debug(`Log flush error: ${error.message}`);
144
- }
145
- }),
146
- );
147
- }
148
-
149
- await Promise.all(promises);
150
- }
151
-
152
- /**
153
- * Shutdown all telemetry processors
154
- */
155
- export async function shutdownTelemetry(): Promise<void> {
156
- const promises: Promise<void>[] = [];
157
-
158
- if (state.spanProcessor) {
159
- promises.push(state.spanProcessor.shutdown());
160
- state.spanProcessor = null;
161
- }
162
-
163
- if (
164
- state.logProcessor &&
165
- typeof (state.logProcessor as { shutdown?: () => Promise<void> })
166
- .shutdown === 'function'
167
- ) {
168
- promises.push(
169
- (state.logProcessor as { shutdown: () => Promise<void> }).shutdown(),
170
- );
171
- state.logProcessor = null;
172
- }
173
-
174
- state.initialized = false;
175
- await Promise.all(promises);
176
- }
177
-
178
- /**
179
- * Enable debug logging for telemetry
180
- */
181
- export function enableTelemetryDebug(): void {
182
- diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
183
- }
184
-
185
- /**
186
- * Wraps a handler function to ensure telemetry is flushed before returning.
187
- * Useful for Lambda handlers.
188
- *
189
- * @example
190
- * ```typescript
191
- * export const handler = withTelemetryFlush(async (event, context) => {
192
- * // Your Lambda logic here
193
- * return { statusCode: 200, body: 'OK' };
194
- * });
195
- * ```
196
- */
197
- export function withTelemetryFlush<TEvent, TResult>(
198
- handler: (event: TEvent, context: unknown) => Promise<TResult>,
199
- ): (event: TEvent, context: unknown) => Promise<TResult> {
200
- return async (event: TEvent, context: unknown): Promise<TResult> => {
201
- try {
202
- return await handler(event, context);
203
- } finally {
204
- await flushTelemetry();
205
- }
206
- };
207
- }
208
-
209
- /**
210
- * Noop span processor for testing or when telemetry is disabled
211
- */
212
- export class NoopSpanProcessor implements SpanProcessor {
213
- forceFlush(): Promise<void> {
214
- return Promise.resolve();
215
- }
216
-
217
- onStart(_span: Span): void {
218
- // noop
219
- }
220
-
221
- onEnd(_span: ReadableSpan): void {
222
- // noop
223
- }
224
-
225
- shutdown(): Promise<void> {
226
- return Promise.resolve();
227
- }
228
- }
@@ -1,270 +0,0 @@
1
- import { context, type Span, trace } from '@opentelemetry/api';
2
- /**
3
- * OpenTelemetry instrumentation middleware for Hono
4
- *
5
- * This middleware automatically creates spans for HTTP requests,
6
- * extracts trace context from incoming headers, and records
7
- * request/response metadata as span attributes.
8
- */
9
- import type { Context, MiddlewareHandler } from 'hono';
10
- import {
11
- createHttpServerSpan,
12
- endHttpSpan,
13
- extractTraceContext,
14
- type HttpSpanAttributes,
15
- } from './http';
16
-
17
- /**
18
- * Options for the Hono telemetry middleware
19
- */
20
- export interface HonoTelemetryMiddlewareOptions {
21
- /**
22
- * Whether to record request body as span attribute
23
- * @default false
24
- */
25
- recordBody?: boolean;
26
-
27
- /**
28
- * Whether to record response body as span attribute
29
- * @default false
30
- */
31
- recordResponseBody?: boolean;
32
-
33
- /**
34
- * Custom function to extract user ID from context
35
- */
36
- getUserId?: (c: Context) => string | undefined;
37
-
38
- /**
39
- * Paths to skip tracing (supports wildcards)
40
- */
41
- ignorePaths?: string[];
42
-
43
- /**
44
- * Whether to skip tracing for this request
45
- */
46
- shouldSkip?: (c: Context) => boolean;
47
- }
48
-
49
- // Key for storing span on context
50
- const SPAN_KEY = 'telemetry.span';
51
- const CONTEXT_KEY = 'telemetry.context';
52
-
53
- /**
54
- * Get the current span from Hono context
55
- */
56
- export function getSpanFromContext(c: Context): Span | undefined {
57
- return c.get(SPAN_KEY);
58
- }
59
-
60
- /**
61
- * Get the current trace context from Hono context
62
- */
63
- export function getTraceContextFromHono(c: Context): any {
64
- return c.get(CONTEXT_KEY);
65
- }
66
-
67
- /**
68
- * Create a telemetry middleware for Hono applications
69
- *
70
- * @example
71
- * ```typescript
72
- * import { honoTelemetryMiddleware } from '@geekmidas/telescope/instrumentation';
73
- *
74
- * const app = new Hono();
75
- * app.use('*', honoTelemetryMiddleware());
76
- * ```
77
- */
78
- export function honoTelemetryMiddleware(
79
- options: HonoTelemetryMiddlewareOptions = {},
80
- ): MiddlewareHandler {
81
- return async (c, next) => {
82
- // Check if path should be ignored
83
- if (
84
- options.ignorePaths &&
85
- shouldIgnorePath(c.req.path, options.ignorePaths)
86
- ) {
87
- return next();
88
- }
89
-
90
- // Skip if custom skip function returns true
91
- if (options.shouldSkip?.(c)) {
92
- return next();
93
- }
94
-
95
- // Extract trace context from headers
96
- const headers: Record<string, string | string[] | undefined> = {};
97
- c.req.raw.headers.forEach((value, key) => {
98
- headers[key] = value;
99
- });
100
- const parentContext = extractTraceContext(headers);
101
-
102
- // Build span attributes from request
103
- const attrs = buildHonoSpanAttributes(c, options);
104
-
105
- // Create the span
106
- const span = createHttpServerSpan(attrs, parentContext);
107
- const spanContext = trace.setSpan(parentContext, span);
108
-
109
- // Store span and context on Hono context
110
- c.set(SPAN_KEY, span);
111
- c.set(CONTEXT_KEY, spanContext);
112
-
113
- // Record body if requested
114
- if (options.recordBody) {
115
- try {
116
- const body = await c.req.text();
117
- if (body) {
118
- span.setAttribute(
119
- 'http.request.body',
120
- body.length > 4096 ? `${body.slice(0, 4096)}...` : body,
121
- );
122
- }
123
- } catch {
124
- // Body may have already been consumed
125
- }
126
- }
127
-
128
- try {
129
- // Execute the rest of the middleware chain within span context
130
- await context.with(spanContext, async () => {
131
- await next();
132
- });
133
-
134
- // Record response
135
- const statusCode = c.res.status;
136
-
137
- if (options.recordResponseBody) {
138
- try {
139
- const body = await c.res.clone().text();
140
- if (body) {
141
- span.setAttribute(
142
- 'http.response.body',
143
- body.length > 4096 ? `${body.slice(0, 4096)}...` : body,
144
- );
145
- }
146
- } catch {
147
- // Response body may not be available
148
- }
149
- }
150
-
151
- endHttpSpan(span, { statusCode });
152
- } catch (error) {
153
- endHttpSpan(
154
- span,
155
- { statusCode: 500 },
156
- error instanceof Error ? error : new Error(String(error)),
157
- );
158
- throw error;
159
- }
160
- };
161
- }
162
-
163
- /**
164
- * Build span attributes from Hono context
165
- */
166
- function buildHonoSpanAttributes(
167
- c: Context,
168
- options: HonoTelemetryMiddlewareOptions,
169
- ): HttpSpanAttributes {
170
- const req = c.req;
171
- const url = new URL(req.url);
172
-
173
- // Use actual path for route unless it's a specific route pattern
174
- const routePath = c.req.routePath;
175
- const route =
176
- routePath && routePath !== '*' && !routePath.includes('/*')
177
- ? routePath
178
- : url.pathname;
179
-
180
- const attrs: HttpSpanAttributes = {
181
- method: req.method,
182
- url: req.url,
183
- path: url.pathname,
184
- route,
185
- host: url.host,
186
- scheme: url.protocol.replace(':', '') as 'http' | 'https',
187
- userAgent: req.header('user-agent'),
188
- clientIp: getClientIp(c),
189
- requestId: req.header('x-request-id') || req.header('x-amzn-requestid'),
190
- };
191
-
192
- // Add user metadata if extractor provided
193
- if (options.getUserId) {
194
- const userId = options.getUserId(c);
195
- if (userId) {
196
- attrs.user = { userId };
197
- }
198
- }
199
-
200
- return attrs;
201
- }
202
-
203
- /**
204
- * Extract client IP from Hono context
205
- */
206
- function getClientIp(c: Context): string | undefined {
207
- // Try various headers in order of preference
208
- const xForwardedFor = c.req.header('x-forwarded-for');
209
- if (xForwardedFor) {
210
- const first = xForwardedFor.split(',')[0];
211
- return first?.trim();
212
- }
213
-
214
- const xRealIp = c.req.header('x-real-ip');
215
- if (xRealIp) {
216
- return xRealIp;
217
- }
218
-
219
- // Try CF-Connecting-IP (Cloudflare)
220
- const cfConnectingIp = c.req.header('cf-connecting-ip');
221
- if (cfConnectingIp) {
222
- return cfConnectingIp;
223
- }
224
-
225
- return undefined;
226
- }
227
-
228
- /**
229
- * Check if path should be ignored
230
- */
231
- function shouldIgnorePath(path: string, ignorePaths: string[]): boolean {
232
- for (const pattern of ignorePaths) {
233
- if (pattern.endsWith('*')) {
234
- // Wildcard match
235
- const prefix = pattern.slice(0, -1);
236
- if (path.startsWith(prefix)) {
237
- return true;
238
- }
239
- } else if (path === pattern) {
240
- // Exact match
241
- return true;
242
- }
243
- }
244
- return false;
245
- }
246
-
247
- /**
248
- * Run a function within the span context from Hono context
249
- *
250
- * @example
251
- * ```typescript
252
- * app.get('/api/users', async (c) => {
253
- * return withHonoSpanContext(c, async () => {
254
- * // Any spans created here will be children of the request span
255
- * const users = await fetchUsers();
256
- * return c.json(users);
257
- * });
258
- * });
259
- * ```
260
- */
261
- export async function withHonoSpanContext<T>(
262
- c: Context,
263
- fn: () => Promise<T>,
264
- ): Promise<T> {
265
- const ctx = getTraceContextFromHono(c);
266
- if (!ctx) {
267
- return fn();
268
- }
269
- return context.with(ctx, fn);
270
- }