logex
(log extensions) is a minimal, extensible logging library for Zig that enhances std.log
with additional features while maintaining a simple, drop-in interface.
- Drop-in Extension: Extends
std.log
with extra features - easy to add and remove without changing logging calls throughout your project - Extensible Appenders: Comes with console and file appenders, with the ability to implement custom appenders for any logging destination
- Customize Formatting: Multiple format options:
- Text (compatible with
std.log
default format) - JSON
- Custom (implement your own formatting function)
- Text (compatible with
- Runtime filtering: Extends scope/log level filtering with runtime options, this allows for environment variable based filtering similar to
env_logger
from the Rust logging ecosystem - Minimal Impact:
logex
aims to add minimal overhead by remaining comptime as much as possible like the defaultstd.log
implementation
Add logex
as a dependency to your zig project like so:
zig fetch --save git+https://github.com/ross-weir/logex.git
And configure it in your build.zig
:
// .. snip
const logex = b.dependency("logex", .{
.target = target,
.optimize = optimize,
});
const exe_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{
.name = "logex",
.module = logex.module("logex"),
},
},
});
const std = @import("std");
const logex = @import("logex");
// Create appender types
// Log to the console at debug and above levels, using default text formatting
const ConsoleAppender = logex.appenders.Console(.debug, .{});
// Log to file at info and above levels, using JSON formatting
const FileAppender = logex.appenders.File(.info, .{
.format = .json,
});
// Create logger type with both appender types
const Logger = logex.Logex(.{ ConsoleAppender, FileAppender });
// Use in std_options
pub const std_options: std.Options = .{
.logFn = Logger.logFn,
};
pub fn main() !void {
// Initialize appender instances
const console_appender = ConsoleAppender.init;
const file_appender = try FileAppender.init("app.log");
// Initialize logger
try Logger.init(.{}, .{ console_appender, file_appender });
// Use std.log as usual
// Debug message will only be displayed on console
std.log.debug("Debug message", .{});
// Info message will be logged to file and console
std.log.info("Info message", .{});
}
Removing logex
is as simple as removing Logger.logFn
and deleting initialzation.
logex
comes with two built-in appenders:
- Writer Appender: A generic threadsafe appender that writes to an underlying
AnyWriter
- Console Appender: Logs to
stderr
, works the same as the defaultlogFn
fromstd.log
- File Appender: Logs to file
You can create custom appenders by implementing the Appender
interface:
const logex = @import("logex");
const MyCustomAppender = struct {
pub fn log(
self: *@This(),
comptime record: *const logex.Record,
context: *const logex.Context,
) !void {
// Implement your logging logic
}
};
See a more complete example here.
logex
supports multiple output formats:
- Text: Default format compatible with
std.log
- JSON: Structured logging in JSON format
- Custom: Implement your own formatting function
See a complete example using a custom formatter here.
logex
provides filtering at runtime that works alongside comptime filtering (std.options.log_scope_levels
/ std.options.log_level
).
An environment variable based filter is provided out of the box with capabilities similar to the Rust ecosystem's env_logger
. This allows you to configure log levels for different scopes at runtime through environment variables, without recompiling your application.
By default, logex
uses the ZIG_LOG
environment variable for configuration. The format is:
scope1=level1,scope2=level2,level3
Where:
scope
is the logging scope (e.g., "my_module")level
is one of: debug, info, warn, err- A level without a scope sets the default level
Examples:
# Set default level to info
export ZIG_LOG=info
# Set specific scopes
export ZIG_LOG=my_module=debug,other_module=warn
# Mix of scoped and default levels
export ZIG_LOG=info,my_module=debug,other_module=warn
See a complete example of runtime filtering here.
Check out the example directory for complete usage examples.