Zig: remove the "scope" parameter from std.log functions; introduce "std.log.scoped" functions

Created on 28 Jul 2020  路  9Comments  路  Source: ziglang/zig

Accepted Proposal


In effort to make logging more mainstream and ergonomic, this is a proposal to simplify the logging API, yet still provide the advanced functionality.

It's difficult to justify the "scope" parameter, for example, in Hello World. Here I propose to do this:

diff --git a/lib/std/log.zig b/lib/std/log.zig
index d8bcba38c..3bd62e3c0 100644
--- a/lib/std/log.zig
+++ b/lib/std/log.zig
@@ -90,6 +90,8 @@ pub const default_level: Level = switch (builtin.mode) {
     .ReleaseSmall => .emerg,
 };

+const default_scope = .main;
+
 /// 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"))
@@ -117,7 +119,7 @@ fn log(

 /// 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(
+pub fn emergScope(
     comptime scope: @Type(.EnumLiteral),
     comptime format: []const u8,
     args: anytype,
@@ -126,9 +128,16 @@ pub fn emerg(
     log(.emerg, scope, format, args);
 }

+/// 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 format: []const u8, args: anytype) void {
+    @setCold(true);
+    log(.emerg, default_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(
+pub fn alertScope(
     comptime scope: @Type(.EnumLiteral),
     comptime format: []const u8,
     args: anytype,
@@ -137,10 +146,17 @@ pub fn alert(
     log(.alert, 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 format: []const u8, args: anytype) void {
+    @setCold(true);
+    log(.alert, default_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(
+pub fn critScope(
     comptime scope: @Type(.EnumLiteral),
     comptime format: []const u8,
     args: anytype,
@@ -149,9 +165,17 @@ pub fn crit(
     log(.crit, 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 format: []const u8, args: anytype) void {
+    @setCold(true);
+    log(.crit, default_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(
+pub fn errScope(
     comptime scope: @Type(.EnumLiteral),
     comptime format: []const u8,
     args: anytype,
@@ -160,10 +184,17 @@ pub fn err(
     log(.err, 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 format: []const u8, args: anytype) void {
+    @setCold(true);
+    log(.err, default_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(
+pub fn warnScope(
     comptime scope: @Type(.EnumLiteral),
     comptime format: []const u8,
     args: anytype,
@@ -171,9 +202,16 @@ pub fn warn(
     log(.warn, 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 format: []const u8, args: anytype) void {
+    log(.warn, default_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(
+pub fn noticeScope(
     comptime scope: @Type(.EnumLiteral),
     comptime format: []const u8,
     args: anytype,
@@ -181,9 +219,15 @@ pub fn notice(
     log(.notice, 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 format: []const u8, args: anytype) void {
+    log(.notice, default_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(
+pub fn infoScope(
     comptime scope: @Type(.EnumLiteral),
     comptime format: []const u8,
     args: anytype,
@@ -191,12 +235,24 @@ pub fn info(
     log(.info, 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 format: []const u8, args: anytype) void {
+    log(.info, default_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(
+pub fn debugScope(
     comptime scope: @Type(.EnumLiteral),
     comptime format: []const u8,
     args: anytype,
 ) void {
     log(.debug, 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 format: []const u8, args: anytype) void {
+    log(.debug, default_scope, format, args);
+}

Hello World becomes:

const std = @import("std");

pub fn main() void {
    std.log.info("application start: {}", .{"Hello, World!"});
}

A potential future improvement would be if we had package-local configurations and therefore could swap out the default log scope per-package. So packages that didn't need multiple scopes would be able to use the simpler log functions, and then get assigned an optional scope override by the application that used the package.

cc @ifreund

accepted breaking contributor friendly proposal standard library

Most helpful comment

how about a _s suffix instead of Scope suffix? related: #1097

I don't think log.warn_s() is terribly clear. What if we put the scoped versions in std.log.scoped or similar? Then one could do either of the following depending on the type of project:

const log = std.log;
log.err("oops", .{}); // Uses the default_scope
const log = std.log.scoped;
log.err(.main, "oops", .{});

All 9 comments

I think that in its current state this proposal makes the API more ergonomic for trivial use-cases like hello world but less ergonomic for larger codebases and all libraries (which should always use a custom scope to allow for filtering). Having some kind of package-local configuration would make this a win for libraries however, and being able to override the config of imported packages would put the power in the hands of the library user as well as solving the issue of potential scope conflicts.

I'd actually argue that hello world should just use std.debug.print (which is what is currently done in the docs for master). Both scope and logging levels are irrelevant to the goal of just printing a message. However if the goal is to introduce users to std.log early, using it in hello world would make sense and I can definitely see how the scope parameter would be confusing to those new to the language. I also think it makes sense to have zig init-exe use std.log as the goal is not to simply print a message but to provide a stub that will be expanded into an actual program.

I agree with @ifreund on the scope (heh) of std.log here, but if this proposal is accepted anyway, I think it should be done the other way around: preserve the short std.log.info for the "correct", more desirable logging with scopes, and add new symbols like std.log.infoNoScope/std.log.infoDefaultScope for the "quick and dirty" approach.

I think the case where someone's going to want to call log functions with different scopes in the same file will be pretty unusual. As such, maybe the right thing to do is to provide the default scope on the const log = line. We could keep the scope variants for the unusual case where a log message needs different scope from the rest of the file.

As for init-exe, this isn't a library so it makes sense to pick a reasonable default scope like ".root" or ".app".

less ergonomic for larger codebases and all libraries

how about a _s suffix instead of Scope suffix? related: #1097

how about a _s suffix instead of Scope suffix? related: #1097

I don't think log.warn_s() is terribly clear. What if we put the scoped versions in std.log.scoped or similar? Then one could do either of the following depending on the type of project:

const log = std.log;
log.err("oops", .{}); // Uses the default_scope
const log = std.log.scoped;
log.err(.main, "oops", .{});

Ooh, I like that scoped namespace idea. Brilliant.

I would prefer a function in std.log that closes over the scope:

pub fn scope(comptime s: any) type {
    return struct {
        pub fn err(comptime fmt: []const u8, args: any) void {
            return log(.err, s, fmt, any);
        }
        // and other levels
    };
}
pub const default = scope(.default);

to be used as:

const log = std.log.scope(.main);
log.err("oops", .{});

or otherwise:

const log = std.log.default;
log.err("oops", .{});

I think I second this one.

-- Email domain proudly hosted at https://migadu.com

I agree, @daurnimator's suggestion is better. Even if you want to mix scopes in the same file you can do something like

const log = std.log;
log.scope(.main).err("oops", .{});
log.scope(.linker).crit("oof", .{});
Was this page helpful?
0 / 5 - 0 ratings