@getvision/server 0.3.2 → 0.3.4-22facf0-develop
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/service.ts +12 -0
- package/src/types.ts +7 -0
- package/src/vision-app.ts +153 -139
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/src/service.ts
CHANGED
|
@@ -445,6 +445,18 @@ export class ServiceBuilder<
|
|
|
445
445
|
const { vision, traceId, rootSpanId } = visionCtx
|
|
446
446
|
const tracer = vision.getTracer();
|
|
447
447
|
|
|
448
|
+
// Add addContext() method to context
|
|
449
|
+
(c as any).addContext = (context: Record<string, unknown>) => {
|
|
450
|
+
const current = getVisionContext()
|
|
451
|
+
// Use current traceId from context if available (handles nested spans/async correctly)
|
|
452
|
+
const targetTraceId = current?.traceId || traceId
|
|
453
|
+
|
|
454
|
+
const visionTrace = vision.getTraceStore().getTrace(targetTraceId)
|
|
455
|
+
if (visionTrace) {
|
|
456
|
+
visionTrace.metadata = { ...(visionTrace.metadata || {}), ...context }
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
448
460
|
// Add span() method to context
|
|
449
461
|
(c as any).span = <T>(
|
|
450
462
|
name: string,
|
package/src/types.ts
CHANGED
|
@@ -39,6 +39,13 @@ export interface ExtendedContext<
|
|
|
39
39
|
attributes?: Record<string, any>,
|
|
40
40
|
fn?: () => T
|
|
41
41
|
): T
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Add context to the current active trace
|
|
45
|
+
* This is the "Wide Event" API - allowing adding high-cardinality data
|
|
46
|
+
* to the current request context.
|
|
47
|
+
*/
|
|
48
|
+
addContext(context: Record<string, unknown>): void
|
|
42
49
|
|
|
43
50
|
/**
|
|
44
51
|
* Emit an event with type-safe validation
|
package/src/vision-app.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
2
|
import type { Env, Schema, Input, MiddlewareHandler } from 'hono'
|
|
3
|
-
import { VisionCore } from '@getvision/core'
|
|
3
|
+
import { VisionCore, runInTraceContext } from '@getvision/core'
|
|
4
4
|
import type { RouteMetadata } from '@getvision/core'
|
|
5
5
|
import { AsyncLocalStorage } from 'async_hooks'
|
|
6
6
|
import { existsSync } from 'fs'
|
|
@@ -336,158 +336,172 @@ export class Vision<
|
|
|
336
336
|
rootSpanId: ''
|
|
337
337
|
},
|
|
338
338
|
async () => {
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
ctx
|
|
347
|
-
|
|
339
|
+
// Also set core trace context so VisionCore.addContext() works
|
|
340
|
+
return runInTraceContext(trace.id, async () => {
|
|
341
|
+
// Start main span
|
|
342
|
+
const tracer = this.visionCore.getTracer()
|
|
343
|
+
const rootSpan = tracer.startSpan('http.request', trace.id)
|
|
344
|
+
|
|
345
|
+
// Update context with rootSpanId
|
|
346
|
+
const ctx = visionContext.getStore()
|
|
347
|
+
if (ctx) {
|
|
348
|
+
ctx.rootSpanId = rootSpan.id
|
|
349
|
+
}
|
|
348
350
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
351
|
+
// Provide c.span and c.addContext globally for all downstream handlers (Vision/Hono sub-apps)
|
|
352
|
+
if (!(c as any).span) {
|
|
353
|
+
(c as any).addContext = (context: Record<string, unknown>) => {
|
|
354
|
+
const current = visionContext.getStore()
|
|
355
|
+
const currentTraceId = current?.traceId || trace.id
|
|
356
|
+
|
|
357
|
+
// Add context to trace metadata via VisionCore
|
|
358
|
+
const visionTrace = this.visionCore.getTraceStore().getTrace(currentTraceId)
|
|
359
|
+
if (visionTrace) {
|
|
360
|
+
visionTrace.metadata = { ...(visionTrace.metadata || {}), ...context }
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
(c as any).span = <T>(
|
|
365
|
+
name: string,
|
|
366
|
+
attributes: Record<string, any> = {},
|
|
367
|
+
fn?: () => T
|
|
368
|
+
): T => {
|
|
369
|
+
const current = visionContext.getStore()
|
|
370
|
+
const currentTraceId = current?.traceId || trace.id
|
|
371
|
+
const currentRootSpanId = current?.rootSpanId || rootSpan.id
|
|
372
|
+
const s = tracer.startSpan(name, currentTraceId, currentRootSpanId)
|
|
373
|
+
for (const [k, v] of Object.entries(attributes)) tracer.setAttribute(s.id, k, v)
|
|
374
|
+
try {
|
|
375
|
+
const result = fn ? fn() : (undefined as any)
|
|
376
|
+
const completed = tracer.endSpan(s.id)
|
|
377
|
+
if (completed) this.visionCore.getTraceStore().addSpan(currentTraceId, completed)
|
|
378
|
+
return result
|
|
379
|
+
} catch (err) {
|
|
380
|
+
tracer.setAttribute(s.id, 'error', true)
|
|
381
|
+
tracer.setAttribute(s.id, 'error.message', err instanceof Error ? err.message : String(err))
|
|
382
|
+
const completed = tracer.endSpan(s.id)
|
|
383
|
+
if (completed) this.visionCore.getTraceStore().addSpan(currentTraceId, completed)
|
|
384
|
+
throw err
|
|
385
|
+
}
|
|
372
386
|
}
|
|
373
387
|
}
|
|
374
|
-
}
|
|
375
388
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
389
|
+
// Add request attributes
|
|
390
|
+
tracer.setAttribute(rootSpan.id, 'http.method', c.req.method)
|
|
391
|
+
tracer.setAttribute(rootSpan.id, 'http.path', c.req.path)
|
|
392
|
+
tracer.setAttribute(rootSpan.id, 'http.url', c.req.url)
|
|
393
|
+
|
|
394
|
+
// Add query params if any
|
|
395
|
+
const url = new URL(c.req.url)
|
|
396
|
+
if (url.search) {
|
|
397
|
+
tracer.setAttribute(rootSpan.id, 'http.query', url.search)
|
|
398
|
+
}
|
|
386
399
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
400
|
+
// Capture request metadata
|
|
401
|
+
try {
|
|
402
|
+
const rawReq = c.req.raw
|
|
403
|
+
const headers: Record<string, string> = {}
|
|
404
|
+
rawReq.headers.forEach((v, k) => { headers[k] = v })
|
|
405
|
+
|
|
406
|
+
const urlObj = new URL(c.req.url)
|
|
407
|
+
const query: Record<string, string> = {}
|
|
408
|
+
urlObj.searchParams.forEach((v, k) => { query[k] = v })
|
|
409
|
+
|
|
410
|
+
let body: unknown = undefined
|
|
411
|
+
const ct = headers['content-type'] || headers['Content-Type']
|
|
412
|
+
if (ct && ct.includes('application/json')) {
|
|
413
|
+
try {
|
|
414
|
+
body = await rawReq.clone().json()
|
|
415
|
+
} catch {}
|
|
416
|
+
}
|
|
396
417
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
} catch {}
|
|
403
|
-
}
|
|
418
|
+
const sessionId = headers['x-vision-session']
|
|
419
|
+
if (sessionId) {
|
|
420
|
+
tracer.setAttribute(rootSpan.id, 'session.id', sessionId)
|
|
421
|
+
trace.metadata = { ...(trace.metadata || {}), sessionId }
|
|
422
|
+
}
|
|
404
423
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
424
|
+
const requestMeta = {
|
|
425
|
+
method: c.req.method,
|
|
426
|
+
url: urlObj.pathname + (urlObj.search || ''),
|
|
427
|
+
headers,
|
|
428
|
+
query: Object.keys(query).length ? query : undefined,
|
|
429
|
+
body,
|
|
430
|
+
}
|
|
431
|
+
tracer.setAttribute(rootSpan.id, 'http.request', requestMeta)
|
|
432
|
+
trace.metadata = { ...(trace.metadata || {}), request: requestMeta }
|
|
433
|
+
|
|
434
|
+
// Emit start log
|
|
435
|
+
if (logging) {
|
|
436
|
+
const parts = [
|
|
437
|
+
`method=${c.req.method}`,
|
|
438
|
+
`path=${c.req.path}`,
|
|
439
|
+
]
|
|
440
|
+
if (sessionId) parts.push(`sessionId=${sessionId}`)
|
|
441
|
+
parts.push(`traceId=${trace.id}`)
|
|
442
|
+
console.info(`INF starting request ${parts.join(' ')}`)
|
|
443
|
+
}
|
|
410
444
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
url: urlObj.pathname + (urlObj.search || ''),
|
|
414
|
-
headers,
|
|
415
|
-
query: Object.keys(query).length ? query : undefined,
|
|
416
|
-
body,
|
|
417
|
-
}
|
|
418
|
-
tracer.setAttribute(rootSpan.id, 'http.request', requestMeta)
|
|
419
|
-
trace.metadata = { ...(trace.metadata || {}), request: requestMeta }
|
|
420
|
-
|
|
421
|
-
// Emit start log
|
|
422
|
-
if (logging) {
|
|
423
|
-
const parts = [
|
|
424
|
-
`method=${c.req.method}`,
|
|
425
|
-
`path=${c.req.path}`,
|
|
426
|
-
]
|
|
427
|
-
if (sessionId) parts.push(`sessionId=${sessionId}`)
|
|
428
|
-
parts.push(`traceId=${trace.id}`)
|
|
429
|
-
console.info(`INF starting request ${parts.join(' ')}`)
|
|
430
|
-
}
|
|
445
|
+
// Execute request
|
|
446
|
+
await next()
|
|
431
447
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
tracer.setAttribute(rootSpan.id, 'http.status_code', c.res.status)
|
|
437
|
-
const resHeaders: Record<string, string> = {}
|
|
438
|
-
c.res.headers?.forEach((v, k) => { resHeaders[k] = v as unknown as string })
|
|
448
|
+
// Add response attributes
|
|
449
|
+
tracer.setAttribute(rootSpan.id, 'http.status_code', c.res.status)
|
|
450
|
+
const resHeaders: Record<string, string> = {}
|
|
451
|
+
c.res.headers?.forEach((v, k) => { resHeaders[k] = v as unknown as string })
|
|
439
452
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
453
|
+
let respBody: unknown = undefined
|
|
454
|
+
const resCt = c.res.headers?.get('content-type') || ''
|
|
455
|
+
try {
|
|
456
|
+
const clone = c.res.clone()
|
|
457
|
+
if (resCt.includes('application/json')) {
|
|
458
|
+
const txt = await clone.text()
|
|
459
|
+
if (txt && txt.length <= 65536) {
|
|
460
|
+
try { respBody = JSON.parse(txt) } catch { respBody = txt }
|
|
461
|
+
}
|
|
448
462
|
}
|
|
463
|
+
} catch {}
|
|
464
|
+
|
|
465
|
+
const responseMeta = {
|
|
466
|
+
status: c.res.status,
|
|
467
|
+
headers: Object.keys(resHeaders).length ? resHeaders : undefined,
|
|
468
|
+
body: respBody,
|
|
469
|
+
}
|
|
470
|
+
tracer.setAttribute(rootSpan.id, 'http.response', responseMeta)
|
|
471
|
+
trace.metadata = { ...(trace.metadata || {}), response: responseMeta }
|
|
472
|
+
|
|
473
|
+
} catch (error) {
|
|
474
|
+
// Track error
|
|
475
|
+
tracer.addEvent(rootSpan.id, 'error', {
|
|
476
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
477
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
tracer.setAttribute(rootSpan.id, 'error', true)
|
|
481
|
+
throw error
|
|
482
|
+
|
|
483
|
+
} finally {
|
|
484
|
+
// End span and add it to trace
|
|
485
|
+
const completedSpan = tracer.endSpan(rootSpan.id)
|
|
486
|
+
if (completedSpan) {
|
|
487
|
+
this.visionCore.getTraceStore().addSpan(trace.id, completedSpan)
|
|
449
488
|
}
|
|
450
|
-
} catch {}
|
|
451
489
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
tracer.setAttribute(rootSpan.id, 'error', true)
|
|
468
|
-
throw error
|
|
469
|
-
|
|
470
|
-
} finally {
|
|
471
|
-
// End span and add it to trace
|
|
472
|
-
const completedSpan = tracer.endSpan(rootSpan.id)
|
|
473
|
-
if (completedSpan) {
|
|
474
|
-
this.visionCore.getTraceStore().addSpan(trace.id, completedSpan)
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Complete trace
|
|
478
|
-
const duration = Date.now() - startTime
|
|
479
|
-
this.visionCore.completeTrace(trace.id, c.res.status, duration)
|
|
480
|
-
|
|
481
|
-
// Add trace ID to response headers
|
|
482
|
-
c.header('X-Vision-Trace-Id', trace.id)
|
|
483
|
-
|
|
484
|
-
// Emit completion log
|
|
485
|
-
if (logging) {
|
|
486
|
-
console.info(
|
|
487
|
-
`INF request completed code=${c.res.status} duration=${duration}ms method=${c.req.method} path=${c.req.path} traceId=${trace.id}`
|
|
488
|
-
)
|
|
490
|
+
// Complete trace
|
|
491
|
+
const duration = Date.now() - startTime
|
|
492
|
+
this.visionCore.completeTrace(trace.id, c.res.status, duration)
|
|
493
|
+
|
|
494
|
+
// Add trace ID to response headers
|
|
495
|
+
c.header('X-Vision-Trace-Id', trace.id)
|
|
496
|
+
|
|
497
|
+
// Emit completion log
|
|
498
|
+
if (logging) {
|
|
499
|
+
console.info(
|
|
500
|
+
`INF request completed code=${c.res.status} duration=${duration}ms method=${c.req.method} path=${c.req.path} traceId=${trace.id}`
|
|
501
|
+
)
|
|
502
|
+
}
|
|
489
503
|
}
|
|
490
|
-
}
|
|
504
|
+
})
|
|
491
505
|
}
|
|
492
506
|
)
|
|
493
507
|
})
|