// 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.
//
// The levels, the filtering and the sink are `moonlog`'s. What is here is what
// belongs to this server: uvicorn's line shape, and the access log, which is
// switched separately because a service behind a proxy that already logs
// requests wants the server's own messages and not a second copy.
///|
/// How much the server says, and where it goes.
pub struct Logger {
out : @moonlog.Logger
access : Bool
}
///|
/// One line in uvicorn's shape: the level, a colon, and five spaces.
fn uvicorn(record : @moonlog.Record) -> String {
record.level.name() + ": " + record.message
}
///|
/// 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? : @moonlog.Level = Info,
access? : Bool = true,
write? : (String) -> Unit = line => println(line),
) -> Logger {
{
out: @moonlog.Logger::new(
@moonlog.Lines::new(write, format=uvicorn),
level~,
),
access,
}
}
///|
/// 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 {
{ out: @moonlog.Logger::silent(), access: false, }
}
///|
/// Whether a line at `level` would be written.
pub fn Logger::enabled(self : Logger, level : @moonlog.Level) -> Bool {
self.out.enabled(level)
}
///|
/// 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 : @moonlog.Level,
message : String,
) -> Unit {
self.out.log(level, message)
}
///|
/// Write the one-line-per-request access log (uvicorn's `uvicorn.access`), in
/// the same shape: peer, request line, status.
pub fn Logger::access_line(
self : Logger,
client : String,
verb : String,
target : String,
http_version : String,
status : Int,
) -> Unit {
if self.access {
self.out.info(
client +
" - \"" +
verb +
" " +
target +
" HTTP/" +
http_version +
"\" " +
status.to_string() +
" " +
reason(status),
)
}
}
///|
/// The reason phrase an access line pairs with a status. Lives here rather than
/// beside the native server because the access line is written on every backend.
fn reason(code : Int) -> String {
match code {
200 => "OK"
201 => "Created"
204 => "No Content"
301 => "Moved Permanently"
302 => "Found"
304 => "Not Modified"
400 => "Bad Request"
401 => "Unauthorized"
403 => "Forbidden"
404 => "Not Found"
405 => "Method Not Allowed"
500 => "Internal Server Error"
_ => ""
}
}