Skip to content

Trace System

Observability is essential for any LLM gateway. GodeX's trace system captures every request, token usage event, streaming event, and error across the entire request lifecycle, persisting them to SQLite for offline analysis. The system is designed for production throughput: an AsyncTraceRecorder batches events in a queue and flushes them periodically or when the batch size threshold is reached, keeping the hot path free from disk I/O. When tracing is disabled, a NoopTraceRecorder replaces the real one at zero cost.

The trace system is attached to the ResponsesContext via TraceRecordingContext, so any code that has access to the context can emit trace records without knowing about the storage backend.

At a Glance

ComponentFilePurpose
TraceRecorderrecorder.ts:5-8Core interface (record, close)
AsyncTraceRecorderrecorder.ts:30-110Queue-based batching recorder
NoopTraceRecorderrecorder.ts:25-28Zero-op recorder when tracing is disabled
SQLiteTraceStoresqlite.ts:69-297SQLite storage with four tables
TraceRecordEventtypes.ts:70-74Union of all record kinds
mapTraceRecordToRowrow-mapper.ts:16-98Converts events to storage rows
summarizePayloadpayload.ts:10-35SHA-256 hash, byte count, optional JSON capture
TraceRecordingContextcontext.ts:4-12Context attached to every request
createTraceServicestrace-services.ts:15-34Factory from config

Architecture Overview

TraceRecorder Interface

The TraceRecorder interface (recorder.ts:5-8) is minimal by design:

MethodDescription
record(event)Enqueue a trace event for persistence
close()Flush remaining events and release resources

AsyncTraceRecorder

The production recorder (recorder.ts:30-110) uses an in-memory queue with two flush triggers:

Configuration Options

OptionTypeDescription
maxQueueSizenumberMaximum events in queue; overflow is dropped
batchSizenumberNumber of events per flush
flushIntervalMsnumberTimer interval for automatic flushing
storeTraceStoreWriterStorage backend (typically SQLiteTraceStore)
loggerTraceRecorderLoggerFor warning about drops and errors
capturePayloadbooleanWhether to store full JSON payloads
payloadMaxBytesnumberByte limit for stored payloads

SQLiteTraceStore

The SQLite store (sqlite.ts:69-297) auto-migrates four tables on construction:

Schema

Batch inserts are wrapped in a transaction (sqlite.ts:90-95) for atomicity. Indexes are created on request_id, response_id, event_name, and code for common query patterns.

Trace Record Types

The TraceRecordEvent union (types.ts:70-74) has four variants:

KindInterfaceKey Fields
requestTraceRequestRecordEventstream, requested_prompt_cache_key, payload
usageTraceUsageRecordEventusage (input_tokens, output_tokens, total_tokens, cached_tokens, reasoning_tokens, cache_hit_ratio)
eventTraceEventRecordEventevent_name, sequence, payload
errorTraceErrorRecordEventevent_name, error_type, domain, code, message, status, payload

All variants share a TraceRecordBase (types.ts:19-25) with request_id, response_id, provider, model, and created_at.

Event Names

The TraceEventRecordEvent restricts event_name (types.ts:50-54) to:

Event NameWhen Recorded
provider.request.preparedFinal provider request has been patched and is about to enter the provider client; no request body is stored on this event
provider.response.bodySync response body received from upstream
upstream.stream.event.rawRaw SSE chunk from upstream
upstream.stream.event.transformedAfter bridge transforms are applied

Provider request payloads are stored on trace_requests, not duplicated in trace_events. Join the request row with provider.request.prepared to see both the final patched request summary and the lifecycle point that preceded the provider client call. The prepared event is recorded after provider patchRequest hooks run and before request() or stream() invokes the provider client's HTTP operation; it does not prove that the network send succeeded.

Payload Capture

summarizePayload (payload.ts:10-35) controls how much data is stored:

ModecapturePayloadpayload_jsonpayload_hash
Summary onlyfalsenullSHA-256 hex of the full JSON
Full capturetrueFull JSON string (up to payloadMaxBytes)SHA-256 hex
Truncated capturetrueTruncated JSON stringSHA-256 hex

The payload_bytes field always records the original byte length regardless of truncation (payload.ts:23-24). The hash is computed with Bun.CryptoHasher("sha256") (payload.ts:6-8).

Row Mapping

mapTraceRecordToRow (row-mapper.ts:16-98) dispatches on event.kind:

KindTarget TablePayload Handling
requesttrace_requestsSummarized with summarizePayload
usagetrace_usageFields extracted directly from TraceUsageSnapshot
eventtrace_eventsSummarized with summarizePayload
errortrace_errorsSummarized with summarizePayload

If serialization fails for any event, the mapper returns null and logs a warning instead of crashing the flush (row-mapper.ts:91-97).

Recording Helpers

Four helper functions attach to the TraceRecordingContext and provide ergonomic recording:

recordTraceRequest

request-recorder.ts:4-22 records the final patched provider request after provider hooks have run, including whether it is streaming, the optional prompt_cache_key, and an optional captured payload summary for the provider request body. The paired provider.request.prepared trace event marks the lifecycle point without carrying the request body again.

recordTraceUsage

usage-recorder.ts:6-22 converts a ResponseUsage into a TraceUsageSnapshot via traceUsageFromResponseUsage (usage.ts:4-23), which also computes cache_hit_ratio as cached_tokens / input_tokens when both are available.

recordTraceEvent

event-recorder.ts:5-25 records a named event with an optional payload and sequence number for ordering within a stream.

recordTraceError

error-recorder.ts:5-27 extracts error metadata (type, domain, code, message, status) from GodeXError or generic errors and records it with the full error context as payload.

Service Wiring

createTraceServices (trace-services.ts:15-34) reads the TraceConfig and creates either an AsyncTraceRecorder backed by SQLiteTraceStore (when config.enabled is true) or a NoopTraceRecorder (when false):

Cross-references

  • Session Stores -- the session store system uses a similar SQLite persistence pattern
  • ProviderSpec Contract -- the provider and model fields in trace records come from the resolved spec

References