xlog

    Native structured logging with levels and category-based filtering.

    log
    logging
    structured-logging
    Download zip
    Version
    0.4.2
    License
    Apache-2.0
    Last updated
    last month
    Downloads
    38K

    Dependencies

    #xlog

    tonyfettes/xlog is a native-only structured logger with top-level helpers, logger instances, categories, structured fields, stdout/file/multi outputs, and MOON_XLOG filtering.

    Logger calls are best-effort: output errors are ignored by Logger, while handlers can still report errors when called directly or wrapped.

    #Quick Start

    Use top-level helpers when passing a logger around would be noisy:

    @xlog.info(category="app") { "message": "server started", "port": 8080 }

    Use a Logger when you want a specific output:

    let logger = @xlog.Logger(@xlog.Stdout())

    logger.debug(category="app.db") { "message": "query plan" }

    Logger methods and top-level helpers return Event?, so <? streams the map literal straight into the event when the log site is enabled and skips evaluating its fields entirely when it is disabled.

    #Levels And Categories

    The default level is Info. Level order, from least to most verbose:

    Fatal Error Warn Info Debug Trace

    A record is emitted when its level is less than or equal to the effective level. Category overrides are hierarchical, so app.db.query falls back to app.db, then app, then the logger's level.

    Logger methods and top-level helpers return None when their level/category is disabled, or Some(event) when fields should be written:

    let logger = @xlog.Logger(@xlog.Stdout())

    logger.info(category="app") { "message": "server started" }

    Create separate logger instances when components need independent levels while sharing the same output handler.

    All fields written through an event are user fields. A field named "message" is stored under fields like any other field; it is not promoted to top-level metadata.

    #Configuration

    Config::from_env() reads MOON_XLOG by default:

    MOON_XLOG=debug MOON_XLOG='warn,app.db=debug,app.db.query=trace'

    You can also build config directly:

    let config = @xlog.Config(level=@xlog.Warn)
    config.set_category_level("app.db", @xlog.Debug)

    let logger = @xlog.Logger(@xlog.Stdout(), config~)
    logger.debug(category="app.db") { "message": "query plan" }

    Invalid levels, invalid directives, and invalid categories raise ConfigError. The package global logger catches invalid MOON_XLOG and falls back to Info.

    #Outputs

    Stdout defaults to one JSON object per line. Use Text for human-readable key-value output:

    @xlog.global().set_handler(@xlog.Stdout())
    @xlog.global().set_handler(@xlog.Stdout(format=@xlog.Text))

    File opens an append-mode file and defaults to Jsonl:

    let file = @xlog.File("_build/app.log")
    @xlog.global().set_handler(file)
    @xlog.info(category="app") { "message": "written to file" }

    File is native-only and uses a small ISO C FILE * stub. The native handle closes automatically when the handler is no longer reachable; call close() only after removing it from long-lived loggers. It does not provide multi-process atomic logging guarantees.

    Multi fans out to several handlers:

    let file = @xlog.File("_build/app.log")
    let handlers : Array[&@xlog.Handler] = [@xlog.Stdout(), file]
    @xlog.global().set_handler(@xlog.Multi(handlers))

    Multi attempts every child handler. Direct Multi.handle(entry) calls can raise MultiError(Array[Error]); ordinary logger calls swallow that error.

    #Global Logger

    Top-level helpers use @xlog.global(), a process-wide root logger:

    let config = @xlog.Config::from_env() catch { _ => @xlog.Config() }

    @xlog.global().set_config(config)
    @xlog.global().set_level(@xlog.Debug)
    @xlog.info(category="app") { "message": "top-level record" }

    global() is a root logger, not a replaceable default logger. Configure it with set_handler, set_config, and set_level.

    #Example

    The examples/lorem package demonstrates category filtering:

    moon run examples/lorem MOON_XLOG=debug moon run examples/lorem MOON_XLOG='warn,lorem.db=trace,lorem.worker=debug,lorem.parser.lexer=trace' moon run examples/lorem

    Handler

    pub(open) trait Handler {
    fn handle(Self, Entry) -> Unit raise
    }

    A sink that receives structured log entries.

    Direct handler calls may raise errors. Logger methods catch and ignore those errors so logging failures do not interrupt application code.

    ConfigError

    pub(all) suberror ConfigError {
    InvalidCategory(String)
    InvalidDirective(String)
    InvalidLevel(String)
    }

    Errors raised when parsing or updating logger configuration.

    MultiError

    pub(all) suberror MultiError {
    MultiError(Array[Error])
    }

    Error raised by Multi when one or more child handlers fail.

    Config

    type Config

    Logging configuration with a root level and category-specific overrides.

    Category overrides are hierarchical: app.db.query falls back to app.db, then app, then the logger's root level.

    Config::Config

    fn Config::Config(level? : Level) -> Config

    Create a configuration with the given root level.

    Config::from_env

    fn Config::from_env(env? : String, level? : Level) -> Config raise ConfigError

    Read configuration from an environment variable.

    env defaults to MOON_XLOG. The value is a comma-separated list of directives: either a root level such as debug, or a category override such as app.db=trace.

    Config::set_category_level

    fn Config::set_category_level(self : Config, category : String, level : Level) -> Unit raise ConfigError

    Set a level override for a category.

    Category names must be non-empty dot-separated segments, with no leading dot, trailing dot, or empty segment.

    Entry

    pub struct Entry {
    level : Level
    category : String?
    timestamp :
    ZonedDateTime

    source : SourceLoc?
    fields : Map[String, Json]
    }

    A structured log record passed to handlers.
    impl Show for Entry
    impl ToJson for Entry

    Event

    type Event

    A structured log event that is written field-by-field.

    Event values are returned by logger level methods only when the log site is enabled. Call write_object_begin, write each field, then call write_object_end to emit the entry.

    Event::write_object_begin

    fn Event::write_object_begin(self : Event) -> Unit

    Begin writing the structured object for this event.

    Event::write_object_end

    fn Event::write_object_end(self : Event) -> Unit

    Finish and emit the event.

    Event::write_object_field

    fn[T : ToJson] Event::write_object_field(self : Event, key : String, value : T) -> Unit

    Write one structured field.

    File

    type File

    A handler that appends log entries to a file.

    File is native-only. It keeps the file open until close is called or the handler becomes unreachable.
    impl Handler for File

    File::File

    fn File::File(path : String, format? : Format) -> File raise
    IOError

    Open a file handler in append mode.

    The default format is Jsonl.

    File::close

    fn File::close(self : File) -> Unit

    Close the file handle.

    Calling close more than once is safe.

    Format

    pub(all) enum Format {
    Text
    Jsonl
    }

    Output format used by built-in handlers.

    Level

    pub(all) enum Level {
    Fatal
    Error
    Warn
    Info
    Debug
    Trace
    } derive(Compare, Eq)

    Log levels ordered from most severe to most verbose.

    A record is emitted when its level is less than or equal to the effective logger level.
    impl Show for Level

    Logger

    type Logger

    A structured logger that emits entries through a Handler.

    Logger calls are best-effort: errors raised by the handler are ignored.

    Logger::Logger

    fn[H : Handler] Logger::Logger(handler : H, level? : Level, config? : Config) -> Logger

    Create a logger that writes to handler.

    The default level is Info. When config is provided, its root level is used as the logger level and category-specific overrides are consulted for each entry.

    Logger::debug

    #callsite(autofill(loc))
    fn Logger::debug(self : Logger, category? : String, loc~ : SourceLoc) -> Event?

    Start a Debug event if it is enabled.

    Logger::error

    #callsite(autofill(loc))
    fn Logger::error(self : Logger, category? : String, loc~ : SourceLoc) -> Event?

    Start an Error event if it is enabled.

    Logger::event

    #callsite(autofill(loc))
    fn Logger::event(self : Logger, level : Level, category? : String, loc~ : SourceLoc) -> Event?

    Start a log event at level if it is enabled.

    Logger::fatal

    #callsite(autofill(loc))
    fn Logger::fatal(self : Logger, category? : String, loc~ : SourceLoc) -> Event?

    Start a Fatal event if it is enabled.

    Logger::info

    #callsite(autofill(loc))
    fn Logger::info(self : Logger, category? : String, loc~ : SourceLoc) -> Event?

    Start an Info event if it is enabled.

    Logger::set_config

    fn Logger::set_config(self : Logger, config : Config) -> Unit

    Install a configuration object and reset the logger's root level from it.

    Logger::set_handler

    fn[H : Handler] Logger::set_handler(self : Logger, handler : H) -> Unit

    Replace the handler used by this logger.

    Logger::set_level

    fn Logger::set_level(self : Logger, level : Level) -> Unit

    Set the logger's root level.

    Category-specific overrides from an installed Config still take precedence for matching categories.

    Logger::trace

    #callsite(autofill(loc))
    fn Logger::trace(self : Logger, category? : String, loc~ : SourceLoc) -> Event?

    Start a Trace event if it is enabled.

    Logger::warn

    #callsite(autofill(loc))
    fn Logger::warn(self : Logger, category? : String, loc~ : SourceLoc) -> Event?

    Start a Warn event if it is enabled.

    Multi

    pub(all) struct Multi(Array[&Handler])

    A handler that fans out each entry to multiple child handlers.

    Every child handler is attempted. If any fail, MultiError is raised after all handlers have been called.
    impl Handler for Multi

    Stdout

    type Stdout

    A handler that writes log entries to standard output.
    impl Handler for Stdout

    Stdout::Stdout

    fn Stdout::Stdout(format? : Format) -> Stdout

    Create a standard-output handler.

    The default format is Jsonl.

    debug

    #callsite(autofill(loc))
    fn debug(category? : String, loc~ : SourceLoc) -> Event?

    Start a Debug event with the global logger if it is enabled.

    error

    #callsite(autofill(loc))
    fn error(category? : String, loc~ : SourceLoc) -> Event?

    Start an Error event with the global logger if it is enabled.

    event

    #callsite(autofill(loc))
    fn event(level : Level, category? : String, loc~ : SourceLoc) -> Event?

    Start a log event at level with the global logger if it is enabled.

    fatal

    #callsite(autofill(loc))
    fn fatal(category? : String, loc~ : SourceLoc) -> Event?

    Start a Fatal event with the global logger if it is enabled.

    global

    fn global() -> Logger

    Return the process-wide root logger used by top-level logging helpers.

    The returned logger can be configured with set_handler, set_config, and set_level.

    info

    #callsite(autofill(loc))
    fn info(category? : String, loc~ : SourceLoc) -> Event?

    Start an Info event with the global logger if it is enabled.

    trace

    #callsite(autofill(loc))
    fn trace(category? : String, loc~ : SourceLoc) -> Event?

    Start a Trace event with the global logger if it is enabled.

    warn

    #callsite(autofill(loc))
    fn warn(category? : String, loc~ : SourceLoc) -> Event?

    Start a Warn event with the global logger if it is enabled.

    Powered by MoonBit

    Site sourceReport issuePackagesBuild queueSkillsStatistics

    © 2026 mooncakes.io