///|
/// A record describing a single request/response round-trip, passed to an
/// observer for logging, metrics, or debugging.
pub(all) struct RequestLog {
/// The endpoint path (e.g. `/chat/completions`).
path : String
/// The request body as a JSON string.
request_body : String
/// The HTTP status code, or -1 if the request never got a response.
status : Int
/// The response body text (may be truncated by the caller).
response_body : String
/// Whether the call ultimately succeeded (2xx).
ok : Bool
}
///|
/// A log severity level.
pub(all) enum LogLevel {
Debug
Info
Warn
Error
} derive(Eq, Debug)
///|
/// The numeric rank of a level, for threshold comparisons.
pub fn LogLevel::rank(self : LogLevel) -> Int {
match self {
Debug => 0
Info => 1
Warn => 2
Error => 3
}
}
///|
/// A short label for a level.
pub fn LogLevel::label(self : LogLevel) -> String {
match self {
Debug => "DEBUG"
Info => "INFO"
Warn => "WARN"
Error => "ERROR"
}
}
///|
/// A simple collecting logger that buffers formatted lines in memory — useful
/// for tests and for surfacing a request trace in a UI. A production app would
/// swap in its own observer that writes to a real logging backend.
pub struct MemoryLogger {
min_level : LogLevel
lines : Array[String]
}
///|
/// Create a memory logger that keeps entries at or above `min_level`.
pub fn MemoryLogger::new(min_level? : LogLevel = Info) -> MemoryLogger {
{ min_level, lines: [] }
}
///|
/// Log a message at a level (dropped if below the threshold).
pub fn MemoryLogger::log(
self : MemoryLogger,
level : LogLevel,
message : String,
) -> Unit {
if level.rank() >= self.min_level.rank() {
self.lines.push("[\{level.label()}] \{message}")
}
}
///|
/// Log a completed request round-trip at an appropriate level.
pub fn MemoryLogger::log_request(self : MemoryLogger, log : RequestLog) -> Unit {
let level = if log.ok { Info } else { Error }
self.log(
level,
"\{log.path} -> \{log.status} (\{log.request_body.length()} req bytes, \{log.response_body.length()} resp bytes)",
)
}
///|
/// The number of buffered log lines.
pub fn MemoryLogger::count(self : MemoryLogger) -> Int {
self.lines.length()
}
///|
/// All buffered lines joined by newlines.
pub fn MemoryLogger::dump(self : MemoryLogger) -> String {
let out = StringBuilder::new()
for i, line in self.lines {
if i > 0 {
out.write_string("\n")
}
out.write_string(line)
}
out.to_string()
}
///|
/// Whether any line at `level` or above was recorded.
pub fn MemoryLogger::has_at_least(
self : MemoryLogger,
level : LogLevel,
) -> Bool {
for line in self.lines {
if line.has_prefix("[\{level.label()}]") {
return true
}
}
false
}
///|
/// Clear all buffered lines.
pub fn MemoryLogger::clear(self : MemoryLogger) -> Unit {
self.lines.clear()
}