Commit 0bd067d19a

Isaac Freund <ifreund@ifreund.xyz>
2020-05-15 17:10:56
Introduce std.log
std.log provides 8 log levels and corresponding logging functions. It allows the user to override the logging "backend" by defining root.log and to override the default log level by defining root.log_level. Logging functions accept a scope parameter which allows the implementer of the logging "backend" to filter logging by library as well as level. Using the standardized syslog [1] log levels ensures that std.log will be flexible enough to work for as many use-cases as possible. If we were to stick with only 3/4 log levels, std.log would be insufficient for large and/or complex projects such as a kernel or display server. [1]: https://tools.ietf.org/html/rfc5424#section-6.2.1
1 parent 9781342
Changed files (2)
lib/std/log.zig
@@ -0,0 +1,202 @@
+const std = @import("std.zig");
+const builtin = std.builtin;
+const root = @import("root");
+
+//! std.log is 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.
+//!
+//! The scope parameter should be used to give context to the logging. For
+//! example, a library called 'libfoo' might use .libfoo as its scope.
+//!
+//! An example root.log might look something like this:
+//!
+//! ```
+//! const std = @import("std");
+//!
+//! // Set the log level to warning
+//! pub const log_level: std.log.Level = .warn;
+//!
+//! // 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: var,
+//! ) void {
+//!     // Ignore all non-critical logging from sources other than
+//!     // .my_project and .nice_library
+//!     const scope_prefix = "(" ++ switch (scope) {
+//!         .my_project, .nice_library => @tagName(scope),
+//!         else => if (@enumToInt(level) <= @enumToInt(std.log.Level.crit))
+//!             @tagName(scope)
+//!         else
+//!             return,
+//!     } ++ "): ";
+//!
+//!     const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
+//!
+//!     // Print the message to stderr, silently ignoring any errors
+//!     const held = std.debug.getStderrMutex().acquire();
+//!     defer held.release();
+//!     const stderr = std.debug.getStderrStream();
+//!     nosuspend stderr.print(prefix ++ format, args) catch return;
+//! }
+//!
+//! pub fn main() void {
+//!     // Won't be printed as log_level is .warn
+//!     std.log.info(.my_project, "Starting up.\n", .{});
+//!     std.log.err(.nice_library, "Something went very wrong, sorry.\n", .{});
+//!     // Won't be printed as it gets filtered out by our log function
+//!     std.log.err(.lib_that_logs_too_much, "Added 1 + 1\n", .{});
+//! }
+//! ```
+//! Which produces the following output:
+//! ```
+//! [err] (nice_library): Something went very wrong, sorry.
+//! ```
+
+pub const Level = enum {
+    /// Emergency: a condition that cannot be handled, usually followed by a
+    /// panic.
+    emerg,
+    /// Alert: a condition that should be corrected immediately (e.g. database
+    /// corruption).
+    alert,
+    /// Critical: A bug has been detected or something has gone wrong and it
+    /// will have an effect on the operation of the program.
+    crit,
+    /// Error: A bug has been detected or something has gone wrong but it is
+    /// recoverable.
+    err,
+    /// Warning: it is uncertain if something has gone wrong or not, but the
+    /// circumstances would be worth investigating.
+    warn,
+    /// Notice: non-error but significant conditions.
+    notice,
+    /// Informational: general messages about the state of the program.
+    info,
+    /// Debug: messages only useful for debugging.
+    debug,
+};
+
+/// The default log level is based on build mode. Note that in ReleaseSmall
+/// builds the default level is emerg but no messages will be stored/logged
+/// by the default logger to save space.
+pub const default_level: Level = switch (builtin.mode) {
+    .Debug => .debug,
+    .ReleaseSafe => .notice,
+    .ReleaseFast => .err,
+    .ReleaseSmall => .emerg,
+};
+
+/// 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;
+
+fn log(
+    comptime message_level: Level,
+    comptime scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: var,
+) void {
+    if (@enumToInt(message_level) <= @enumToInt(level)) {
+        if (@hasDecl(root, "log")) {
+            root.log(message_level, scope, format, args);
+        } else if (builtin.mode != .ReleaseSmall) {
+            const held = std.debug.getStderrMutex().acquire();
+            defer held.release();
+            const stderr = io.getStdErr().writer();
+            nosuspend stderr.print(format, args) catch return;
+        }
+    }
+}
+
+/// Log an emergency message to stderr. This log level is intended to be used
+/// for conditions that cannot be handled and is usually followed by a panic.
+pub fn emerg(
+    comptime scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: var,
+) void {
+    @setCold(true);
+    log(.emerg, scope, format, args);
+}
+
+/// Log an alert message to stderr. This log level is intended to be used for
+/// conditions that should be corrected immediately (e.g. database corruption).
+pub fn alert(
+    comptime scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: var,
+) void {
+    @setCold(true);
+    log(.alert, scope, format, args);
+}
+
+/// Log a critical message to stderr. This log level is intended to be used
+/// when a bug has been detected or something has gone wrong and it will have
+/// an effect on the operation of the program.
+pub fn crit(
+    comptime scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: var,
+) void {
+    @setCold(true);
+    log(.crit, scope, format, args);
+}
+
+/// Log an error message to stderr. This log level is intended to be used when
+/// a bug has been detected or something has gone wrong but it is recoverable.
+pub fn err(
+    comptime scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: var,
+) void {
+    @setCold(true);
+    log(.err, scope, format, args);
+}
+
+/// Log a warning message to stderr. 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 scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: var,
+) void {
+    log(.warn, scope, format, args);
+}
+
+/// Log a notice message to stderr. This log level is intended to be used for
+/// non-error but significant conditions.
+pub fn notice(
+    comptime scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: var,
+) void {
+    log(.notice, scope, format, args);
+}
+
+/// Log an info message to stderr. This log level is intended to be used for
+/// general messages about the state of the program.
+pub fn info(
+    comptime scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: var,
+) void {
+    log(.info, scope, format, args);
+}
+
+/// Log a debug message to stderr. This log level is intended to be used for
+/// messages which are only useful for debugging.
+pub fn debug(
+    comptime scope: @Type(.EnumLiteral),
+    comptime format: []const u8,
+    args: var,
+) void {
+    log(.debug, scope, format, args);
+}
lib/std/std.zig
@@ -49,6 +49,7 @@ pub const heap = @import("heap.zig");
 pub const http = @import("http.zig");
 pub const io = @import("io.zig");
 pub const json = @import("json.zig");
+pub const log = @import("log.zig");
 pub const macho = @import("macho.zig");
 pub const math = @import("math.zig");
 pub const mem = @import("mem.zig");