@stacksjs/bun-router 0.0.5 → 0.0.7
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.
- package/dist/auth.d.ts +134 -0
- package/dist/cache/lru-cache.d.ts +139 -0
- package/dist/cache/middleware-memoization.d.ts +151 -0
- package/dist/cache/route-cache-warmer.d.ts +161 -0
- package/dist/cache/sqlite-cache.d.ts +209 -0
- package/dist/cache/streaming-cache.d.ts +132 -0
- package/dist/cli/colors.d.ts +15 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/middleware.d.ts +31 -0
- package/dist/cli/openapi.d.ts +17 -0
- package/dist/cli/router.d.ts +15 -0
- package/dist/cli/routes.d.ts +29 -0
- package/dist/cli/utils.d.ts +74 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/container/container.d.ts +273 -0
- package/dist/container/contextual-binding.d.ts +240 -0
- package/dist/container/decorators.d.ts +141 -0
- package/dist/container/service-provider.d.ts +285 -0
- package/dist/development/hot-reload.d.ts +187 -0
- package/dist/development/index.d.ts +167 -0
- package/dist/development/performance-profiler.d.ts +217 -0
- package/dist/development/route-debugger.d.ts +154 -0
- package/dist/development/route-inspector.d.ts +211 -0
- package/dist/development/typescript-utilities.d.ts +209 -0
- package/dist/docs.d.ts +11 -0
- package/dist/errors/circuit-breaker.d.ts +195 -0
- package/dist/errors/error-handler.d.ts +88 -0
- package/dist/errors/error-reporting.d.ts +144 -0
- package/dist/errors/exceptions.d.ts +184 -0
- package/dist/errors/graceful-degradation.d.ts +154 -0
- package/dist/errors/index.d.ts +9 -0
- package/dist/errors/router-errors.d.ts +110 -0
- package/dist/file-serving/static-files.d.ts +143 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +375 -227
- package/dist/middleware/auth.d.ts +63 -0
- package/dist/middleware/content_security_policy.d.ts +53 -0
- package/dist/middleware/cors.d.ts +5 -0
- package/dist/middleware/csrf.d.ts +7 -0
- package/dist/middleware/ddos_protection.d.ts +39 -0
- package/dist/middleware/file_security.d.ts +26 -0
- package/dist/middleware/file_upload.d.ts +38 -0
- package/dist/middleware/helmet.d.ts +54 -0
- package/dist/middleware/index.d.ts +44 -0
- package/dist/middleware/input_validation.d.ts +45 -0
- package/dist/middleware/json_body.d.ts +4 -0
- package/dist/middleware/performance_alerting.d.ts +87 -0
- package/dist/middleware/performance_dashboard.d.ts +87 -0
- package/dist/middleware/performance_monitor.d.ts +209 -0
- package/dist/middleware/pipeline.d.ts +131 -0
- package/dist/middleware/rate_limit.d.ts +36 -0
- package/dist/middleware/request_id.d.ts +4 -0
- package/dist/middleware/request_signing.d.ts +153 -0
- package/dist/middleware/request_tracer.d.ts +72 -0
- package/dist/middleware/response_cache.d.ts +97 -0
- package/dist/middleware/security.d.ts +69 -0
- package/dist/middleware/security_suite.d.ts +46 -0
- package/dist/middleware/session.d.ts +23 -0
- package/dist/model-binding/index.d.ts +2 -0
- package/dist/model-binding/model-middleware.d.ts +118 -0
- package/dist/model-binding/model-registry.d.ts +164 -0
- package/dist/model-binding.d.ts +185 -0
- package/dist/model-resolver-factory.d.ts +31 -0
- package/dist/observability/correlation.d.ts +187 -0
- package/dist/observability/health-checks.d.ts +186 -0
- package/dist/observability/index.d.ts +60 -0
- package/dist/observability/integration.d.ts +147 -0
- package/dist/observability/metrics.d.ts +184 -0
- package/dist/observability/tracing.d.ts +187 -0
- package/dist/optimization/bun-utilities.d.ts +224 -0
- package/dist/query-builder-integration.d.ts +36 -0
- package/dist/request/context.d.ts +34 -0
- package/dist/request/enhanced-request.d.ts +213 -0
- package/dist/request/macros.d.ts +300 -0
- package/dist/response/macros.d.ts +245 -0
- package/dist/response/response-factory.d.ts +133 -0
- package/dist/router/api-routes.d.ts +5 -0
- package/dist/router/file-based-routing.d.ts +118 -0
- package/dist/router/file-streaming.d.ts +5 -0
- package/dist/router/fluent-router.d.ts +315 -0
- package/dist/router/fluent-routing.d.ts +271 -0
- package/dist/router/group-organization.d.ts +5 -0
- package/dist/router/handler-resolver.d.ts +24 -0
- package/dist/router/http-methods.d.ts +10 -0
- package/dist/router/index.d.ts +113 -0
- package/dist/router/middleware-groups.d.ts +94 -0
- package/dist/router/middleware-integration.d.ts +112 -0
- package/dist/router/middleware.d.ts +5 -0
- package/dist/router/model-binding.d.ts +5 -0
- package/dist/router/optimized-route-matching.d.ts +6 -0
- package/dist/router/route-building.d.ts +5 -0
- package/dist/router/route-compiler.d.ts +153 -0
- package/dist/router/route-matching.d.ts +5 -0
- package/dist/router/route-trie.d.ts +114 -0
- package/dist/router/router.d.ts +276 -0
- package/dist/router/server.d.ts +5 -0
- package/dist/router/validation-integration.d.ts +170 -0
- package/dist/router/view-rendering.d.ts +5 -0
- package/dist/router/websocket.d.ts +5 -0
- package/dist/routing/route-caching.d.ts +129 -0
- package/dist/routing/route-throttling.d.ts +150 -0
- package/dist/routing/subdomain-routing.d.ts +206 -0
- package/dist/session/database-store.d.ts +51 -0
- package/dist/session/file-store.d.ts +22 -0
- package/dist/session/index.d.ts +106 -0
- package/dist/session/memory-store.d.ts +21 -0
- package/dist/session/redis-store.d.ts +31 -0
- package/dist/streaming/index.d.ts +3 -0
- package/dist/streaming/sse-handler.d.ts +138 -0
- package/dist/streaming/stream-handler.d.ts +114 -0
- package/dist/testing/auth-testing.d.ts +156 -0
- package/dist/testing/file-upload-testing.d.ts +190 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/middleware-testing.d.ts +138 -0
- package/dist/testing/model-binding-testing.d.ts +186 -0
- package/dist/testing/performance-testing.d.ts +235 -0
- package/dist/testing/test-client.d.ts +117 -0
- package/dist/testing/test-request.d.ts +85 -0
- package/dist/testing/test-response.d.ts +90 -0
- package/dist/testing/types.d.ts +207 -0
- package/dist/testing/websocket-testing.d.ts +227 -0
- package/dist/types/controller-types.d.ts +208 -0
- package/dist/types/core.d.ts +543 -0
- package/dist/types/middleware-types.d.ts +227 -0
- package/dist/types/request-response-augmentation.d.ts +261 -0
- package/dist/types/route-inference.d.ts +168 -0
- package/dist/types.d.ts +1776 -0
- package/dist/url.d.ts +56 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/query-preservation.d.ts +48 -0
- package/dist/utils.d.ts +69 -0
- package/dist/validation/validator.d.ts +140 -0
- package/dist/websocket/clustering.d.ts +185 -0
- package/package.json +3 -2
- package/src/auth.ts +469 -0
- package/src/cache/lru-cache.ts +457 -0
- package/src/cache/middleware-memoization.ts +531 -0
- package/src/cache/route-cache-warmer.ts +486 -0
- package/src/cache/sqlite-cache.ts +783 -0
- package/src/cache/streaming-cache.ts +572 -0
- package/src/cli/colors.ts +29 -0
- package/src/cli/index.ts +287 -0
- package/src/cli/middleware.ts +291 -0
- package/src/cli/openapi.ts +407 -0
- package/src/cli/router.ts +188 -0
- package/src/cli/routes.ts +265 -0
- package/src/cli/utils.ts +531 -0
- package/src/cli.ts +5 -0
- package/src/config.ts +322 -0
- package/src/container/container.ts +740 -0
- package/src/container/contextual-binding.ts +603 -0
- package/src/container/decorators.ts +359 -0
- package/src/container/service-provider.ts +596 -0
- package/src/development/hot-reload.ts +673 -0
- package/src/development/index.ts +499 -0
- package/src/development/performance-profiler.ts +717 -0
- package/src/development/route-debugger.ts +527 -0
- package/src/development/route-inspector.ts +749 -0
- package/src/development/typescript-utilities.ts +678 -0
- package/src/docs.ts +397 -0
- package/src/errors/circuit-breaker.ts +732 -0
- package/src/errors/error-handler.ts +569 -0
- package/src/errors/error-reporting.ts +672 -0
- package/src/errors/exceptions.ts +536 -0
- package/src/errors/graceful-degradation.ts +621 -0
- package/src/errors/index.ts +21 -0
- package/src/errors/router-errors.ts +632 -0
- package/src/file-serving/static-files.ts +581 -0
- package/src/index.ts +14 -0
- package/src/middleware/auth.ts +219 -0
- package/src/middleware/content_security_policy.ts +215 -0
- package/src/middleware/cors.ts +74 -0
- package/src/middleware/csrf.ts +108 -0
- package/src/middleware/ddos_protection.ts +255 -0
- package/src/middleware/file_security.ts +191 -0
- package/src/middleware/file_upload.ts +264 -0
- package/src/middleware/helmet.ts +268 -0
- package/src/middleware/index.ts +117 -0
- package/src/middleware/input_validation.ts +449 -0
- package/src/middleware/json_body.ts +37 -0
- package/src/middleware/performance_alerting.ts +538 -0
- package/src/middleware/performance_dashboard.ts +661 -0
- package/src/middleware/performance_monitor.ts +943 -0
- package/src/middleware/pipeline.ts +489 -0
- package/src/middleware/rate_limit.ts +245 -0
- package/src/middleware/request_id.ts +36 -0
- package/src/middleware/request_signing.ts +636 -0
- package/src/middleware/request_tracer.ts +636 -0
- package/src/middleware/response_cache.ts +743 -0
- package/src/middleware/security.ts +482 -0
- package/src/middleware/security_suite.ts +257 -0
- package/src/middleware/session.ts +91 -0
- package/src/model-binding/index.ts +18 -0
- package/src/model-binding/model-middleware.ts +425 -0
- package/src/model-binding/model-registry.ts +550 -0
- package/src/model-binding.ts +370 -0
- package/src/model-resolver-factory.ts +106 -0
- package/src/observability/correlation.ts +691 -0
- package/src/observability/health-checks.ts +729 -0
- package/src/observability/index.ts +184 -0
- package/src/observability/integration.ts +548 -0
- package/src/observability/metrics.ts +753 -0
- package/src/observability/tracing.ts +638 -0
- package/src/optimization/bun-utilities.ts +778 -0
- package/src/query-builder-integration.ts +137 -0
- package/src/request/context.ts +62 -0
- package/src/request/enhanced-request.ts +857 -0
- package/src/request/macros.ts +688 -0
- package/src/response/macros.ts +612 -0
- package/src/response/response-factory.ts +588 -0
- package/src/router/api-routes.ts +243 -0
- package/src/router/file-based-routing.ts +670 -0
- package/src/router/file-streaming.ts +372 -0
- package/src/router/fluent-router.ts +927 -0
- package/src/router/fluent-routing.ts +797 -0
- package/src/router/group-organization.ts +213 -0
- package/src/router/handler-resolver.ts +291 -0
- package/src/router/http-methods.ts +377 -0
- package/src/router/index.ts +185 -0
- package/src/router/middleware-groups.ts +222 -0
- package/src/router/middleware-integration.ts +399 -0
- package/src/router/middleware.ts +231 -0
- package/src/router/model-binding.ts +215 -0
- package/src/router/optimized-route-matching.ts +253 -0
- package/src/router/route-building.ts +212 -0
- package/src/router/route-compiler.ts +702 -0
- package/src/router/route-matching.ts +349 -0
- package/src/router/route-trie.ts +464 -0
- package/src/router/router.ts +1524 -0
- package/src/router/server.ts +429 -0
- package/src/router/validation-integration.ts +443 -0
- package/src/router/view-rendering.ts +233 -0
- package/src/router/websocket.ts +100 -0
- package/src/routing/route-caching.ts +402 -0
- package/src/routing/route-throttling.ts +469 -0
- package/src/routing/subdomain-routing.ts +492 -0
- package/src/session/database-store.ts +109 -0
- package/src/session/file-store.ts +148 -0
- package/src/session/index.ts +244 -0
- package/src/session/memory-store.ts +88 -0
- package/src/session/redis-store.ts +93 -0
- package/src/streaming/index.ts +17 -0
- package/src/streaming/sse-handler.ts +482 -0
- package/src/streaming/stream-handler.ts +552 -0
- package/src/testing/auth-testing.ts +446 -0
- package/src/testing/file-upload-testing.ts +543 -0
- package/src/testing/index.ts +10 -0
- package/src/testing/middleware-testing.ts +322 -0
- package/src/testing/model-binding-testing.ts +645 -0
- package/src/testing/performance-testing.ts +738 -0
- package/src/testing/test-client.ts +310 -0
- package/src/testing/test-request.ts +315 -0
- package/src/testing/test-response.ts +332 -0
- package/src/testing/types.ts +224 -0
- package/src/testing/websocket-testing.ts +590 -0
- package/src/types/controller-types.ts +385 -0
- package/src/types/core.ts +683 -0
- package/src/types/middleware-types.ts +419 -0
- package/src/types/request-response-augmentation.ts +489 -0
- package/src/types/route-inference.ts +357 -0
- package/src/types.ts +2049 -0
- package/src/url.ts +126 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/query-preservation.ts +201 -0
- package/src/utils.ts +327 -0
- package/src/validation/validator.ts +685 -0
- package/src/websocket/clustering.ts +762 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability & Monitoring - Distributed Tracing with OpenTelemetry
|
|
3
|
+
*
|
|
4
|
+
* Enterprise-grade distributed tracing for request correlation and performance monitoring
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EnhancedRequest } from '../types'
|
|
8
|
+
|
|
9
|
+
export interface TraceConfig {
|
|
10
|
+
serviceName: string
|
|
11
|
+
serviceVersion?: string
|
|
12
|
+
environment?: string
|
|
13
|
+
endpoint?: string
|
|
14
|
+
headers?: Record<string, string>
|
|
15
|
+
sampleRate?: number
|
|
16
|
+
enableConsoleExporter?: boolean
|
|
17
|
+
enableJaegerExporter?: boolean
|
|
18
|
+
enableOTLPExporter?: boolean
|
|
19
|
+
jaegerEndpoint?: string
|
|
20
|
+
otlpEndpoint?: string
|
|
21
|
+
attributes?: Record<string, any>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SpanContext {
|
|
25
|
+
traceId: string
|
|
26
|
+
spanId: string
|
|
27
|
+
parentSpanId?: string
|
|
28
|
+
flags: number
|
|
29
|
+
baggage?: Record<string, string>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Span {
|
|
33
|
+
traceId: string
|
|
34
|
+
spanId: string
|
|
35
|
+
parentSpanId?: string
|
|
36
|
+
operationName: string
|
|
37
|
+
startTime: number
|
|
38
|
+
endTime?: number
|
|
39
|
+
duration?: number
|
|
40
|
+
tags: Record<string, any>
|
|
41
|
+
logs: Array<{
|
|
42
|
+
timestamp: number
|
|
43
|
+
fields: Record<string, any>
|
|
44
|
+
}>
|
|
45
|
+
status: 'ok' | 'error' | 'timeout'
|
|
46
|
+
statusMessage?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface TraceData {
|
|
50
|
+
traceId: string
|
|
51
|
+
spans: Span[]
|
|
52
|
+
startTime: number
|
|
53
|
+
endTime?: number
|
|
54
|
+
duration?: number
|
|
55
|
+
serviceName: string
|
|
56
|
+
environment: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Distributed tracing implementation
|
|
61
|
+
*/
|
|
62
|
+
export class DistributedTracer {
|
|
63
|
+
private config: TraceConfig
|
|
64
|
+
private activeSpans: Map<string, Span> = new Map()
|
|
65
|
+
private traces: Map<string, TraceData> = new Map()
|
|
66
|
+
private spanCounter = 0
|
|
67
|
+
|
|
68
|
+
constructor(config: TraceConfig) {
|
|
69
|
+
this.config = {
|
|
70
|
+
sampleRate: 1.0,
|
|
71
|
+
environment: 'development',
|
|
72
|
+
enableConsoleExporter: true,
|
|
73
|
+
...config,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Generate trace ID
|
|
79
|
+
*/
|
|
80
|
+
generateTraceId(): string {
|
|
81
|
+
return Array.from({ length: 32 }, () =>
|
|
82
|
+
Math.floor(Math.random() * 16).toString(16)).join('')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Generate span ID
|
|
87
|
+
*/
|
|
88
|
+
generateSpanId(): string {
|
|
89
|
+
return Array.from({ length: 16 }, () =>
|
|
90
|
+
Math.floor(Math.random() * 16).toString(16)).join('')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Start a new trace
|
|
95
|
+
*/
|
|
96
|
+
startTrace(operationName: string, parentContext?: SpanContext): Span {
|
|
97
|
+
const traceId = parentContext?.traceId || this.generateTraceId()
|
|
98
|
+
const spanId = this.generateSpanId()
|
|
99
|
+
|
|
100
|
+
const span: Span = {
|
|
101
|
+
traceId,
|
|
102
|
+
spanId,
|
|
103
|
+
parentSpanId: parentContext?.spanId,
|
|
104
|
+
operationName,
|
|
105
|
+
startTime: Date.now(),
|
|
106
|
+
tags: {
|
|
107
|
+
'service.name': this.config.serviceName,
|
|
108
|
+
'service.version': this.config.serviceVersion || '1.0.0',
|
|
109
|
+
'environment': this.config.environment,
|
|
110
|
+
...this.config.attributes,
|
|
111
|
+
},
|
|
112
|
+
logs: [],
|
|
113
|
+
status: 'ok',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.activeSpans.set(spanId, span)
|
|
117
|
+
|
|
118
|
+
// Initialize trace data if not exists
|
|
119
|
+
if (!this.traces.has(traceId)) {
|
|
120
|
+
this.traces.set(traceId, {
|
|
121
|
+
traceId,
|
|
122
|
+
spans: [],
|
|
123
|
+
startTime: Date.now(),
|
|
124
|
+
serviceName: this.config.serviceName,
|
|
125
|
+
environment: this.config.environment || 'development',
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return span
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Start a child span
|
|
134
|
+
*/
|
|
135
|
+
startChildSpan(operationName: string, parentSpan: Span): Span {
|
|
136
|
+
return this.startTrace(operationName, {
|
|
137
|
+
traceId: parentSpan.traceId,
|
|
138
|
+
spanId: parentSpan.spanId,
|
|
139
|
+
flags: 1,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Finish a span
|
|
145
|
+
*/
|
|
146
|
+
finishSpan(span: Span): void {
|
|
147
|
+
span.endTime = Date.now()
|
|
148
|
+
span.duration = span.endTime - span.startTime
|
|
149
|
+
|
|
150
|
+
// Add span to trace
|
|
151
|
+
const trace = this.traces.get(span.traceId)
|
|
152
|
+
if (trace) {
|
|
153
|
+
trace.spans.push({ ...span })
|
|
154
|
+
|
|
155
|
+
// Update trace end time and duration
|
|
156
|
+
trace.endTime = Math.max(trace.endTime || 0, span.endTime)
|
|
157
|
+
if (trace.startTime) {
|
|
158
|
+
trace.duration = trace.endTime - trace.startTime
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.activeSpans.delete(span.spanId)
|
|
163
|
+
|
|
164
|
+
// Export span if sampling allows
|
|
165
|
+
if (this.shouldSample()) {
|
|
166
|
+
this.exportSpan(span)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Add tags to span
|
|
172
|
+
*/
|
|
173
|
+
setSpanTags(span: Span, tags: Record<string, any>): void {
|
|
174
|
+
Object.assign(span.tags, tags)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Add log to span
|
|
179
|
+
*/
|
|
180
|
+
logToSpan(span: Span, fields: Record<string, any>): void {
|
|
181
|
+
span.logs.push({
|
|
182
|
+
timestamp: Date.now(),
|
|
183
|
+
fields,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Set span status
|
|
189
|
+
*/
|
|
190
|
+
setSpanStatus(span: Span, status: 'ok' | 'error' | 'timeout', message?: string): void {
|
|
191
|
+
span.status = status
|
|
192
|
+
if (message) {
|
|
193
|
+
span.statusMessage = message
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Extract trace context from headers
|
|
199
|
+
*/
|
|
200
|
+
extractTraceContext(headers: Headers): SpanContext | null {
|
|
201
|
+
// Support for multiple trace header formats
|
|
202
|
+
const traceParent = headers.get('traceparent')
|
|
203
|
+
const jaegerTrace = headers.get('uber-trace-id')
|
|
204
|
+
const b3Trace = headers.get('x-b3-traceid')
|
|
205
|
+
|
|
206
|
+
if (traceParent) {
|
|
207
|
+
return this.parseTraceParent(traceParent)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (jaegerTrace) {
|
|
211
|
+
return this.parseJaegerTrace(jaegerTrace)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (b3Trace) {
|
|
215
|
+
return this.parseB3Trace(headers)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Inject trace context into headers
|
|
223
|
+
*/
|
|
224
|
+
injectTraceContext(span: Span, headers: Record<string, string>): void {
|
|
225
|
+
// W3C Trace Context format
|
|
226
|
+
const traceParent = `00-${span.traceId}-${span.spanId}-01`
|
|
227
|
+
headers.traceparent = traceParent
|
|
228
|
+
|
|
229
|
+
// Add correlation ID
|
|
230
|
+
headers['x-correlation-id'] = span.traceId
|
|
231
|
+
headers['x-span-id'] = span.spanId
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Parse W3C traceparent header
|
|
236
|
+
*/
|
|
237
|
+
private parseTraceParent(traceParent: string): SpanContext | null {
|
|
238
|
+
const parts = traceParent.split('-')
|
|
239
|
+
if (parts.length !== 4)
|
|
240
|
+
return null
|
|
241
|
+
|
|
242
|
+
const [version, traceId, spanId, flags] = parts
|
|
243
|
+
if (version !== '00')
|
|
244
|
+
return null
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
traceId,
|
|
248
|
+
spanId,
|
|
249
|
+
flags: Number.parseInt(flags, 16),
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Parse Jaeger trace header
|
|
255
|
+
*/
|
|
256
|
+
private parseJaegerTrace(jaegerTrace: string): SpanContext | null {
|
|
257
|
+
const parts = jaegerTrace.split(':')
|
|
258
|
+
if (parts.length < 4)
|
|
259
|
+
return null
|
|
260
|
+
|
|
261
|
+
const [traceId, spanId, parentSpanId, flags] = parts
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
traceId: traceId.padStart(32, '0'),
|
|
265
|
+
spanId: spanId.padStart(16, '0'),
|
|
266
|
+
parentSpanId: parentSpanId !== '0' ? parentSpanId.padStart(16, '0') : undefined,
|
|
267
|
+
flags: Number.parseInt(flags, 16),
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Parse B3 trace headers
|
|
273
|
+
*/
|
|
274
|
+
private parseB3Trace(headers: Headers): SpanContext | null {
|
|
275
|
+
const traceId = headers.get('x-b3-traceid')
|
|
276
|
+
const spanId = headers.get('x-b3-spanid')
|
|
277
|
+
const parentSpanId = headers.get('x-b3-parentspanid')
|
|
278
|
+
const flags = headers.get('x-b3-flags') || headers.get('x-b3-sampled')
|
|
279
|
+
|
|
280
|
+
if (!traceId || !spanId)
|
|
281
|
+
return null
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
traceId: traceId.padStart(32, '0'),
|
|
285
|
+
spanId: spanId.padStart(16, '0'),
|
|
286
|
+
parentSpanId: parentSpanId?.padStart(16, '0'),
|
|
287
|
+
flags: flags ? Number.parseInt(flags, 16) : 0,
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Check if span should be sampled
|
|
293
|
+
*/
|
|
294
|
+
private shouldSample(): boolean {
|
|
295
|
+
return Math.random() < (this.config.sampleRate || 1.0)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Export span to configured exporters
|
|
300
|
+
*/
|
|
301
|
+
private exportSpan(span: Span): void {
|
|
302
|
+
if (this.config.enableConsoleExporter) {
|
|
303
|
+
this.exportToConsole(span)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (this.config.enableJaegerExporter && this.config.jaegerEndpoint) {
|
|
307
|
+
this.exportToJaeger(span)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (this.config.enableOTLPExporter && this.config.otlpEndpoint) {
|
|
311
|
+
this.exportToOTLP(span)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Export to console
|
|
317
|
+
*/
|
|
318
|
+
private exportToConsole(span: Span): void {
|
|
319
|
+
console.log(`[TRACE] ${span.operationName}`, {
|
|
320
|
+
traceId: span.traceId,
|
|
321
|
+
spanId: span.spanId,
|
|
322
|
+
duration: span.duration,
|
|
323
|
+
status: span.status,
|
|
324
|
+
tags: span.tags,
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Export to Jaeger (simplified)
|
|
330
|
+
*/
|
|
331
|
+
private async exportToJaeger(span: Span): Promise<void> {
|
|
332
|
+
try {
|
|
333
|
+
const jaegerSpan = {
|
|
334
|
+
traceID: span.traceId,
|
|
335
|
+
spanID: span.spanId,
|
|
336
|
+
parentSpanID: span.parentSpanId,
|
|
337
|
+
operationName: span.operationName,
|
|
338
|
+
startTime: span.startTime * 1000, // microseconds
|
|
339
|
+
duration: (span.duration || 0) * 1000,
|
|
340
|
+
tags: Object.entries(span.tags).map(([key, value]) => ({
|
|
341
|
+
key,
|
|
342
|
+
type: typeof value === 'string' ? 'string' : 'number',
|
|
343
|
+
value: String(value),
|
|
344
|
+
})),
|
|
345
|
+
logs: span.logs.map(log => ({
|
|
346
|
+
timestamp: log.timestamp * 1000,
|
|
347
|
+
fields: Object.entries(log.fields).map(([key, value]) => ({
|
|
348
|
+
key,
|
|
349
|
+
value: String(value),
|
|
350
|
+
})),
|
|
351
|
+
})),
|
|
352
|
+
process: {
|
|
353
|
+
serviceName: this.config.serviceName,
|
|
354
|
+
tags: [
|
|
355
|
+
{ key: 'service.version', value: this.config.serviceVersion || '1.0.0' },
|
|
356
|
+
{ key: 'environment', value: this.config.environment || 'development' },
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
await fetch(`${this.config.jaegerEndpoint}/api/traces`, {
|
|
362
|
+
method: 'POST',
|
|
363
|
+
headers: {
|
|
364
|
+
'Content-Type': 'application/json',
|
|
365
|
+
...this.config.headers,
|
|
366
|
+
},
|
|
367
|
+
body: JSON.stringify({
|
|
368
|
+
data: [{
|
|
369
|
+
traceID: span.traceId,
|
|
370
|
+
spans: [jaegerSpan],
|
|
371
|
+
}],
|
|
372
|
+
}),
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
console.error('Failed to export to Jaeger:', error)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Export to OTLP endpoint
|
|
382
|
+
*/
|
|
383
|
+
private async exportToOTLP(span: Span): Promise<void> {
|
|
384
|
+
try {
|
|
385
|
+
const otlpSpan = {
|
|
386
|
+
traceId: this.hexToBytes(span.traceId),
|
|
387
|
+
spanId: this.hexToBytes(span.spanId),
|
|
388
|
+
parentSpanId: span.parentSpanId ? this.hexToBytes(span.parentSpanId) : undefined,
|
|
389
|
+
name: span.operationName,
|
|
390
|
+
kind: 1, // SPAN_KIND_SERVER
|
|
391
|
+
startTimeUnixNano: span.startTime * 1000000,
|
|
392
|
+
endTimeUnixNano: (span.endTime || span.startTime) * 1000000,
|
|
393
|
+
attributes: Object.entries(span.tags).map(([key, value]) => ({
|
|
394
|
+
key,
|
|
395
|
+
value: {
|
|
396
|
+
stringValue: String(value),
|
|
397
|
+
},
|
|
398
|
+
})),
|
|
399
|
+
events: span.logs.map(log => ({
|
|
400
|
+
timeUnixNano: log.timestamp * 1000000,
|
|
401
|
+
name: 'log',
|
|
402
|
+
attributes: Object.entries(log.fields).map(([key, value]) => ({
|
|
403
|
+
key,
|
|
404
|
+
value: { stringValue: String(value) },
|
|
405
|
+
})),
|
|
406
|
+
})),
|
|
407
|
+
status: {
|
|
408
|
+
code: span.status === 'ok' ? 1 : 2,
|
|
409
|
+
message: span.statusMessage,
|
|
410
|
+
},
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
await fetch(`${this.config.otlpEndpoint}/v1/traces`, {
|
|
414
|
+
method: 'POST',
|
|
415
|
+
headers: {
|
|
416
|
+
'Content-Type': 'application/x-protobuf',
|
|
417
|
+
...this.config.headers,
|
|
418
|
+
},
|
|
419
|
+
body: JSON.stringify({
|
|
420
|
+
resourceSpans: [{
|
|
421
|
+
resource: {
|
|
422
|
+
attributes: [
|
|
423
|
+
{ key: 'service.name', value: { stringValue: this.config.serviceName } },
|
|
424
|
+
{ key: 'service.version', value: { stringValue: this.config.serviceVersion || '1.0.0' } },
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
instrumentationLibrarySpans: [{
|
|
428
|
+
instrumentationLibrary: {
|
|
429
|
+
name: 'bun-router',
|
|
430
|
+
version: '1.0.0',
|
|
431
|
+
},
|
|
432
|
+
spans: [otlpSpan],
|
|
433
|
+
}],
|
|
434
|
+
}],
|
|
435
|
+
}),
|
|
436
|
+
})
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
console.error('Failed to export to OTLP:', error)
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Convert hex string to bytes
|
|
445
|
+
*/
|
|
446
|
+
private hexToBytes(hex: string): Uint8Array {
|
|
447
|
+
const bytes = new Uint8Array(hex.length / 2)
|
|
448
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
449
|
+
bytes[i / 2] = Number.parseInt(hex.substr(i, 2), 16)
|
|
450
|
+
}
|
|
451
|
+
return bytes
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get active spans
|
|
456
|
+
*/
|
|
457
|
+
getActiveSpans(): Span[] {
|
|
458
|
+
return Array.from(this.activeSpans.values())
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Get trace by ID
|
|
463
|
+
*/
|
|
464
|
+
getTrace(traceId: string): TraceData | undefined {
|
|
465
|
+
return this.traces.get(traceId)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Get all traces
|
|
470
|
+
*/
|
|
471
|
+
getAllTraces(): TraceData[] {
|
|
472
|
+
return Array.from(this.traces.values())
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Clear old traces (cleanup)
|
|
477
|
+
*/
|
|
478
|
+
cleanup(maxAge = 3600000): void { // 1 hour default
|
|
479
|
+
const cutoff = Date.now() - maxAge
|
|
480
|
+
|
|
481
|
+
for (const [traceId, trace] of this.traces.entries()) {
|
|
482
|
+
if (trace.startTime < cutoff) {
|
|
483
|
+
this.traces.delete(traceId)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Global tracer instance
|
|
491
|
+
*/
|
|
492
|
+
let globalTracer: DistributedTracer | null = null
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Initialize global tracer
|
|
496
|
+
*/
|
|
497
|
+
export function initializeTracer(config: TraceConfig): DistributedTracer {
|
|
498
|
+
globalTracer = new DistributedTracer(config)
|
|
499
|
+
return globalTracer
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Get global tracer
|
|
504
|
+
*/
|
|
505
|
+
export function getTracer(): DistributedTracer | null {
|
|
506
|
+
return globalTracer
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Tracing middleware factory
|
|
511
|
+
*/
|
|
512
|
+
export function createTracingMiddleware(_config?: Partial<TraceConfig>) {
|
|
513
|
+
return async (req: EnhancedRequest, next: () => Promise<Response>): Promise<Response> => {
|
|
514
|
+
const tracer = getTracer()
|
|
515
|
+
if (!tracer) {
|
|
516
|
+
return await next()
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Extract parent context from headers
|
|
520
|
+
const parentContext = tracer.extractTraceContext(req.headers)
|
|
521
|
+
|
|
522
|
+
// Start span for this request
|
|
523
|
+
const span = tracer.startTrace(`${req.method} ${new URL(req.url).pathname}`, parentContext || undefined)
|
|
524
|
+
|
|
525
|
+
// Add request tags
|
|
526
|
+
tracer.setSpanTags(span, {
|
|
527
|
+
'http.method': req.method,
|
|
528
|
+
'http.url': req.url,
|
|
529
|
+
'http.user_agent': req.headers.get('user-agent') || '',
|
|
530
|
+
'http.remote_addr': req.headers.get('x-forwarded-for') || req.headers.get('x-real-ip') || 'unknown',
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
// Add span to request for access in handlers
|
|
534
|
+
;(req as any).span = span
|
|
535
|
+
;(req as any).tracer = tracer
|
|
536
|
+
|
|
537
|
+
let response: Response
|
|
538
|
+
try {
|
|
539
|
+
response = await next()
|
|
540
|
+
|
|
541
|
+
// Add response tags
|
|
542
|
+
tracer.setSpanTags(span, {
|
|
543
|
+
'http.status_code': response.status,
|
|
544
|
+
'http.response.size': response.headers.get('content-length') || '0',
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
// Set span status based on response
|
|
548
|
+
if (response.status >= 400) {
|
|
549
|
+
tracer.setSpanStatus(span, 'error', `HTTP ${response.status}`)
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
tracer.setSpanStatus(span, 'ok')
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
// Log error and set span status
|
|
557
|
+
tracer.logToSpan(span, {
|
|
558
|
+
level: 'error',
|
|
559
|
+
message: error instanceof Error ? error.message : String(error),
|
|
560
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
tracer.setSpanStatus(span, 'error', error instanceof Error ? error.message : String(error))
|
|
564
|
+
throw error
|
|
565
|
+
}
|
|
566
|
+
finally {
|
|
567
|
+
// Finish span
|
|
568
|
+
tracer.finishSpan(span)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Inject trace context into response headers
|
|
572
|
+
const responseHeaders = new Headers(response.headers)
|
|
573
|
+
const traceHeaders: Record<string, string> = {}
|
|
574
|
+
tracer.injectTraceContext(span, traceHeaders)
|
|
575
|
+
|
|
576
|
+
Object.entries(traceHeaders).forEach(([key, value]) => {
|
|
577
|
+
responseHeaders.set(key, value)
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
return new Response(response.body, {
|
|
581
|
+
status: response.status,
|
|
582
|
+
statusText: response.statusText,
|
|
583
|
+
headers: responseHeaders,
|
|
584
|
+
})
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Trace helper functions
|
|
590
|
+
*/
|
|
591
|
+
export const TraceHelpers = {
|
|
592
|
+
/**
|
|
593
|
+
* Start a span within a request context
|
|
594
|
+
*/
|
|
595
|
+
startSpan: (req: EnhancedRequest, operationName: string): Span | null => {
|
|
596
|
+
const tracer = (req as any).tracer as DistributedTracer
|
|
597
|
+
const parentSpan = (req as any).span as Span
|
|
598
|
+
|
|
599
|
+
if (!tracer || !parentSpan)
|
|
600
|
+
return null
|
|
601
|
+
|
|
602
|
+
return tracer.startChildSpan(operationName, parentSpan)
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Finish a span
|
|
607
|
+
*/
|
|
608
|
+
finishSpan: (req: EnhancedRequest, span: Span): void => {
|
|
609
|
+
const tracer = (req as any).tracer as DistributedTracer
|
|
610
|
+
if (tracer) {
|
|
611
|
+
tracer.finishSpan(span)
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Add tags to current span
|
|
617
|
+
*/
|
|
618
|
+
addTags: (req: EnhancedRequest, tags: Record<string, any>): void => {
|
|
619
|
+
const tracer = (req as any).tracer as DistributedTracer
|
|
620
|
+
const span = (req as any).span as Span
|
|
621
|
+
|
|
622
|
+
if (tracer && span) {
|
|
623
|
+
tracer.setSpanTags(span, tags)
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Log to current span
|
|
629
|
+
*/
|
|
630
|
+
log: (req: EnhancedRequest, fields: Record<string, any>): void => {
|
|
631
|
+
const tracer = (req as any).tracer as DistributedTracer
|
|
632
|
+
const span = (req as any).span as Span
|
|
633
|
+
|
|
634
|
+
if (tracer && span) {
|
|
635
|
+
tracer.logToSpan(span, fields)
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
}
|