// Logging, in the shape of uvicorn's two loggers: `uvicorn.error` for the server's
// own lifecycle and failures, `uvicorn.access` for one line per request. A server
// that says nothing is a server nobody can operate — a request that 500s, a port
// that was already taken, a lifespan that refused to start all look identical from
// outside.
///|
/// How much the server says. Ordered, so a level is enabled when it is at least as
/// severe as the configured one — the same arrangement as Python's `logging`.
pub(all) enum LogLevel {
Trace
Debug
Info
Warning
Error
Critical
} derive(Eq)
///|
/// The level's rank, low to high, for the "at least as severe" comparison.
fn LogLevel::rank(self : LogLevel) -> Int {
match self {
Trace => 0
Debug => 1
Info => 2
Warning => 3
Error => 4
Critical => 5
}
}
///|
/// The level's name as it appears in a log line.
fn LogLevel::name(self : LogLevel) -> String {
match self {
Trace => "TRACE"
Debug => "DEBUG"
Info => "INFO"
Warning => "WARNING"
Error => "ERROR"
Critical => "CRITICAL"
}
}
///|
/// Where log lines go. Writing through a sink rather than straight to stdout is
/// what lets a test read what the server said, and what lets an embedder route
/// the lines into its own logging.
pub(all) struct Logger {
level : LogLevel
access : Bool
write : (String) -> Unit
}
///|
/// A logger printing to stdout at `level`, with the access log on — uvicorn's
/// defaults. `access` off silences the per-request lines while keeping the
/// server's own.
pub fn Logger::new(
level? : LogLevel = Info,
access? : Bool = true,
write? : (String) -> Unit = line => println(line),
) -> Logger {
{ level, access, write, }
}
///|
/// A logger that says nothing, for an embedder that does its own reporting or a
/// test that would rather not have output.
pub fn Logger::silent() -> Logger {
{ level: Critical, access: false, write: _line => (), }
}
///|
/// Whether a line at `level` would be written.
pub fn Logger::enabled(self : Logger, level : LogLevel) -> Bool {
level.rank() >= self.level.rank()
}
///|
/// Write one server-lifecycle line (uvicorn's `uvicorn.error` logger, which carries
/// ordinary startup messages as well as failures).
pub fn Logger::log(self : Logger, level : LogLevel, message : String) -> Unit {
if self.enabled(level) {
(self.write)(level.name() + ": " + message)
}
}
///|
/// Write the one-line-per-request access log (uvicorn's `uvicorn.access`), in the
/// same shape: peer, request line, status.
///
/// Kept separate from `log` because it is switched separately: a service behind a
/// proxy that already logs requests wants the server's own messages and not a
/// second copy of the access log.
pub fn Logger::access_line(
self : Logger,
client : String,
verb : String,
target : String,
http_version : String,
status : Int,
) -> Unit {
if self.access && self.enabled(Info) {
(self.write)(
"INFO: " +
client +
" - \"" +
verb +
" " +
target +
" HTTP/" +
http_version +
"\" " +
status.to_string() +
" " +
reason(status),
)
}
}