///|
/// Handler that discards all log entries (null pattern).
pub(all) struct NullHandler {
level : Level
}
///|
/// Creates a NullHandler that discards all entries below the given level.
pub fn NullHandler::new(level : Level) -> NullHandler {
{ level, }
}
///|
/// Discards the entry (no-op).
pub fn NullHandler::log(self : NullHandler, _entry : LogEntry) -> Unit {
let _ = self
}
///|
/// No-op flush.
pub fn NullHandler::flush(self : NullHandler) -> Unit {
let _ = self
}
///|
/// No-op close.
pub fn NullHandler::close(self : NullHandler) -> Unit {
let _ = self
}
///|
/// Handler that only logs when a predicate condition is met.
pub(all) struct ConditionHandler {
inner : HandlerFn
condition : (LogEntry) -> Bool
}
///|
/// Creates a ConditionHandler that only passes entries matching the predicate.
pub fn ConditionHandler::new(
inner : HandlerFn,
condition : (LogEntry) -> Bool,
) -> ConditionHandler {
{ inner, condition }
}
///|
/// Logs the entry only if the condition predicate returns true.
pub fn ConditionHandler::log(self : ConditionHandler, entry : LogEntry) -> Unit {
if (self.condition)(entry) {
self.inner.log(entry)
}
}
///|
/// Flushes the inner handler.
pub fn ConditionHandler::flush(self : ConditionHandler) -> Unit {
self.inner.flush()
}
///|
/// Closes the inner handler.
pub fn ConditionHandler::close(self : ConditionHandler) -> Unit {
self.inner.close()
}
///|
/// Handler that limits log output to at most one entry per unique message.
pub(all) struct ThrottledHandler {
inner : HandlerFn
mut seen : Array[String]
}
///|
/// Creates a ThrottledHandler that only logs each unique message once.
pub fn ThrottledHandler::new(inner : HandlerFn) -> ThrottledHandler {
{ inner, seen: [] }
}
///|
/// Logs the entry only if its message has not been seen before.
pub fn ThrottledHandler::log(self : ThrottledHandler, entry : LogEntry) -> Unit {
let mut already_seen = false
let n = self.seen.length()
let mut i = 0
while i < n {
if self.seen[i] == entry.message {
already_seen = true
i = n
}
i = i + 1
}
if !already_seen {
self.seen.push(entry.message)
self.inner.log(entry)
}
}
///|
/// Flushes the inner handler.
pub fn ThrottledHandler::flush(self : ThrottledHandler) -> Unit {
self.inner.flush()
}
///|
/// Closes the inner handler and clears seen messages.
pub fn ThrottledHandler::close(self : ThrottledHandler) -> Unit {
self.seen = []
self.inner.close()
}
///|
/// Handler that counts entries by level, useful for monitoring.
pub(all) struct CountingHandler {
inner : HandlerFn
mut debug_count : Int
mut info_count : Int
mut warn_count : Int
mut error_count : Int
mut fatal_count : Int
}
///|
/// Creates a CountingHandler that tracks log counts per level.
pub fn CountingHandler::new(inner : HandlerFn) -> CountingHandler {
{
inner,
debug_count: 0,
info_count: 0,
warn_count: 0,
error_count: 0,
fatal_count: 0,
}
}
///|
/// Logs the entry and increments the level counter.
pub fn CountingHandler::log(self : CountingHandler, entry : LogEntry) -> Unit {
match entry.level {
Debug => self.debug_count = self.debug_count + 1
Info => self.info_count = self.info_count + 1
Warn => self.warn_count = self.warn_count + 1
Error => self.error_count = self.error_count + 1
Fatal => self.fatal_count = self.fatal_count + 1
}
self.inner.log(entry)
}
///|
/// Flushes the inner handler.
pub fn CountingHandler::flush(self : CountingHandler) -> Unit {
self.inner.flush()
}
///|
/// Closes the inner handler.
pub fn CountingHandler::close(self : CountingHandler) -> Unit {
self.inner.close()
}
///|
/// Returns the count of debug-level entries.
pub fn CountingHandler::debug_count(self : CountingHandler) -> Int {
self.debug_count
}
///|
/// Returns the count of info-level entries.
pub fn CountingHandler::info_count(self : CountingHandler) -> Int {
self.info_count
}
///|
/// Returns the count of warn-level entries.
pub fn CountingHandler::warn_count(self : CountingHandler) -> Int {
self.warn_count
}
///|
/// Returns the count of error-level entries.
pub fn CountingHandler::error_count(self : CountingHandler) -> Int {
self.error_count
}
///|
/// Returns the count of fatal-level entries.
pub fn CountingHandler::fatal_count(self : CountingHandler) -> Int {
self.fatal_count
}
///|
/// Returns the total count across all levels.
pub fn CountingHandler::total_count(self : CountingHandler) -> Int {
self.debug_count +
self.info_count +
self.warn_count +
self.error_count +
self.fatal_count
}
///|
/// Resets all level counters to zero.
pub fn CountingHandler::reset(self : CountingHandler) -> Unit {
self.debug_count = 0
self.info_count = 0
self.warn_count = 0
self.error_count = 0
self.fatal_count = 0
}
///|
/// Formats a log entry in key=value format with a timestamp prefix.
pub fn kv_formatter() -> (LogEntry) -> String {
fn(entry : LogEntry) -> String {
let sb = StringBuilder()
sb.write_string("ts=")
sb.write_string(entry.timestamp.to_string())
sb.write_string(" level=")
sb.write_string(entry.level.to_string())
sb.write_string(" msg=")
sb.write_string(kv_esc(entry.message))
if entry.mod_name.length() > 0 {
sb.write_string(" module=")
sb.write_string(kv_esc(entry.mod_name))
}
let n = entry.fields.length()
let mut i = 0
while i < n {
let (k, v) = entry.fields[i]
sb.write_string(" ")
sb.write_string(k)
sb.write_string("=")
sb.write_string(kv_esc(v))
i = i + 1
}
sb.to_string()
}
}
///|
fn kv_esc(s : String) -> String {
if !s.contains(" ") && !s.contains("=") {
return s
}
let sb = StringBuilder()
sb.write_string("\"")
for c in s.iter() {
if c == '\\' {
sb.write_string("\\\\")
} else if c == '"' {
sb.write_string("\\\"")
} else if c == '\n' {
sb.write_string("\\n")
} else {
sb.write_char(c)
}
}
sb.write_string("\"")
sb.to_string()
}
///|
/// Formats a log entry as a full-width banner for emphasis.
pub fn banner_formatter(width : Int) -> (LogEntry) -> String {
fn(entry : LogEntry) -> String {
let border = String::make(width, '=')
let sb = StringBuilder()
sb.write_string(border)
sb.write_string("\n")
sb.write_string(" ")
sb.write_string(entry.level.to_string())
if entry.mod_name.length() > 0 {
sb.write_string(" (")
sb.write_string(entry.mod_name)
sb.write_string(")")
}
sb.write_string("\n")
sb.write_string(" ")
sb.write_string(entry.message)
sb.write_string("\n")
sb.write_string(border)
sb.to_string()
}
}
///|
/// Formats a log entry in CSV-like format.
pub fn csv_formatter(
separator : String,
_include_header : Bool,
) -> (LogEntry) -> String {
fn(entry : LogEntry) -> String {
let sb = StringBuilder()
sb.write_string(entry.timestamp.to_string())
sb.write_string(separator)
sb.write_string(entry.level.to_string())
sb.write_string(separator)
sb.write_string(csv_esc(entry.message))
sb.write_string(separator)
sb.write_string(csv_esc(entry.mod_name))
sb.write_string(separator)
sb.write_string(entry.file)
sb.write_string(separator)
sb.write_string(entry.line.to_string())
sb.to_string()
}
}
///|
fn csv_esc(s : String) -> String {
if !s.contains(",") && !s.contains("\"") && !s.contains("\n") {
return s
}
let sb = StringBuilder()
sb.write_string("\"")
for c in s.iter() {
if c == '"' {
sb.write_string("\"\"")
} else {
sb.write_char(c)
}
}
sb.write_string("\"")
sb.to_string()
}
///|
/// Returns a summary string of all entries in an array.
pub fn entries_summary(entries : Array[LogEntry]) -> String {
let sb = StringBuilder()
let n = entries.length()
sb.write_string("Entries: ")
sb.write_string(n.to_string())
sb.write_string("\n")
if n > 0 {
let level_counts = [0, 0, 0, 0, 0]
let mut i = 0
while i < n {
let idx = entries[i].level.to_int()
level_counts[idx] = level_counts[idx] + 1
i = i + 1
}
sb.write_string(" DEBUG: ")
sb.write_string(level_counts[0].to_string())
sb.write_string("\n")
sb.write_string(" INFO: ")
sb.write_string(level_counts[1].to_string())
sb.write_string("\n")
sb.write_string(" WARN: ")
sb.write_string(level_counts[2].to_string())
sb.write_string("\n")
sb.write_string(" ERROR: ")
sb.write_string(level_counts[3].to_string())
sb.write_string("\n")
sb.write_string(" FATAL: ")
sb.write_string(level_counts[4].to_string())
}
sb.to_string()
}
///|
/// Returns only entries of the specified level.
pub fn entries_by_level(
entries : Array[LogEntry],
target : Level,
) -> Array[LogEntry] {
let result : Array[LogEntry] = []
let n = entries.length()
let mut i = 0
while i < n {
if entries[i].level == target {
result.push(entries[i])
}
i = i + 1
}
result
}
///|
/// Returns the entry with the earliest timestamp, or None if empty.
pub fn entries_earliest(entries : Array[LogEntry]) -> LogEntry? {
let n = entries.length()
if n == 0 {
return None
}
let mut best = entries[0]
let mut i = 1
while i < n {
if entries[i].timestamp < best.timestamp {
best = entries[i]
}
i = i + 1
}
Some(best)
}
///|
/// Returns the entry with the latest timestamp, or None if empty.
pub fn entries_latest(entries : Array[LogEntry]) -> LogEntry? {
let n = entries.length()
if n == 0 {
return None
}
let mut best = entries[0]
let mut i = 1
while i < n {
if entries[i].timestamp > best.timestamp {
best = entries[i]
}
i = i + 1
}
Some(best)
}
///|
/// Returns a new array without entries that match the predicate.
pub fn entries_reject(
entries : Array[LogEntry],
predicate : (LogEntry) -> Bool,
) -> Array[LogEntry] {
let result : Array[LogEntry] = []
for e in entries {
if !predicate(e) {
result.push(e)
}
}
result
}