//! std.log is a standardized interface for logging which allows for the logging //! of programs and libraries using this interface to be formatted and filtered //! by the implementer of the root.log function. //! //! Each log message has an associated scope enum, which can be used to give //! context to the logging. The logging functions in std.log implicitly use a //! scope of .default. //! //! A logging namespace using a custom scope can be created using the //! std.log.scoped function, passing the scope as an argument; the logging //! functions in the resulting struct use the provided scope parameter. //! For example, a library called 'libfoo' might use //! `const log = std.log.scoped(.libfoo);` to use .libfoo as the scope of its //! log messages. //! //! An example root.log might look something like this: //! //! ``` //! const std = @import("std"); //! //! // Set the log level to info //! pub const log_level: std.log.Level = .info; //! //! // Define root.log to override the std implementation //! pub fn log( //! comptime level: std.log.Level, //! comptime scope: @TypeOf(.EnumLiteral), //! comptime format: []const u8, //! args: anytype, //! ) void { //! // Ignore all non-error logging from sources other than //! // .my_project, .nice_library and .default //! const scope_prefix = "(" ++ switch (scope) { //! .my_project, .nice_library, .default => @tagName(scope), //! else => if (@enumToInt(level) <= @enumToInt(std.log.Level.err)) //! @tagName(scope) //! else //! return, //! } ++ "): "; //! //! const prefix = "[" ++ comptime level.asText() ++ "] " ++ scope_prefix; //! //! // Print the message to stderr, silently ignoring any errors //! std.debug.getStderrMutex().lock(); //! defer std.debug.getStderrMutex().unlock(); //! const stderr = std.io.getStdErr().writer(); //! nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return; //! } //! //! pub fn main() void { //! // Using the default scope: //! std.log.debug("A borderline useless debug log message", .{}); // Won't be printed as log_level is .info //! std.log.info("Flux capacitor is starting to overheat", .{}); //! //! // Using scoped logging: //! const my_project_log = std.log.scoped(.my_project); //! const nice_library_log = std.log.scoped(.nice_library); //! const verbose_lib_log = std.log.scoped(.verbose_lib); //! //! my_project_log.debug("Starting up", .{}); // Won't be printed as log_level is .info //! nice_library_log.warn("Something went very wrong, sorry", .{}); //! verbose_lib_log.warn("Added 1 + 1: {}", .{1 + 1}); // Won't be printed as it gets filtered out by our log function //! } //! ``` //! Which produces the following output: //! ``` //! [info] (default): Flux capacitor is starting to overheat //! [warning] (nice_library): Something went very wrong, sorry //! ``` const std = @import("std.zig"); const builtin = @import("builtin"); const root = @import("root"); pub const Level = enum { /// Error: something has gone wrong. This might be recoverable or might /// be followed by the program exiting. err, /// Warning: it is uncertain if something has gone wrong or not, but the /// circumstances would be worth investigating. warn, /// Info: general messages about the state of the program. info, /// Debug: messages only useful for debugging. debug, /// Returns a string literal of the given level in full text form. pub fn asText(comptime self: Level) []const u8 { return switch (self) { .err => "error", .warn => "warning", .info => "info", .debug => "debug", }; } }; /// The default log level is based on build mode. pub const default_level: Level = switch (builtin.mode) { .Debug => .debug, .ReleaseSafe => .info, .ReleaseFast, .ReleaseSmall => .err, }; /// The current log level. This is set to root.log_level if present, otherwise /// log.default_level. pub const level: Level = if (@hasDecl(root, "log_level")) root.log_level else default_level; pub const ScopeLevel = struct { scope: @Type(.EnumLiteral), level: Level, }; const scope_levels = if (@hasDecl(root, "scope_levels")) root.scope_levels else [0]ScopeLevel{}; fn log( comptime message_level: Level, comptime scope: @Type(.EnumLiteral), comptime format: []const u8, args: anytype, ) void { const effective_log_level = blk: { inline for (scope_levels) |scope_level| { if (scope_level.scope == scope) break :blk scope_level.level; } break :blk level; }; if (@enumToInt(message_level) <= @enumToInt(effective_log_level)) { if (@hasDecl(root, "log")) { if (@typeInfo(@TypeOf(root.log)) != .Fn) @compileError("Expected root.log to be a function"); root.log(message_level, scope, format, args); } else { defaultLog(message_level, scope, format, args); } } } /// The default implementation for root.log. root.log may forward log messages /// to this function. pub fn defaultLog( comptime message_level: Level, comptime scope: @Type(.EnumLiteral), comptime format: []const u8, args: anytype, ) void { if (builtin.os.tag == .freestanding) @compileError( \\freestanding targets do not have I/O configured; \\please provide at least an empty `log` function declaration ); const level_txt = comptime message_level.asText(); const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; const stderr = std.io.getStdErr().writer(); std.debug.getStderrMutex().lock(); defer std.debug.getStderrMutex().unlock(); nosuspend stderr.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return; } /// Returns a scoped logging namespace that logs all messages using the scope /// provided here. pub fn scoped(comptime scope: @Type(.EnumLiteral)) type { return struct { /// Log an error message. This log level is intended to be used /// when something has gone wrong. This might be recoverable or might /// be followed by the program exiting. pub fn err( comptime format: []const u8, args: anytype, ) void { @setCold(true); log(.err, scope, format, args); } /// Log a warning message. This log level is intended to be used if /// it is uncertain whether something has gone wrong or not, but the /// circumstances would be worth investigating. pub fn warn( comptime format: []const u8, args: anytype, ) void { log(.warn, scope, format, args); } /// Log an info message. This log level is intended to be used for /// general messages about the state of the program. pub fn info( comptime format: []const u8, args: anytype, ) void { log(.info, scope, format, args); } /// Log a debug message. This log level is intended to be used for /// messages which are only useful for debugging. pub fn debug( comptime format: []const u8, args: anytype, ) void { log(.debug, scope, format, args); } }; } /// The default scoped logging namespace. pub const default = scoped(.default); /// Log an error message using the default scope. This log level is intended to /// be used when something has gone wrong. This might be recoverable or might /// be followed by the program exiting. pub const err = default.err; /// Log a warning message using the default scope. This log level is intended /// to be used if it is uncertain whether something has gone wrong or not, but /// the circumstances would be worth investigating. pub const warn = default.warn; /// Log an info message using the default scope. This log level is intended to /// be used for general messages about the state of the program. pub const info = default.info; /// Log a debug message using the default scope. This log level is intended to /// be used for messages which are only useful for debugging. pub const debug = default.debug;