Skip to content

Introduce std.log #5348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 19, 2020
Merged

Introduce std.log #5348

merged 3 commits into from
Jun 19, 2020

Conversation

ifreund
Copy link
Member

@ifreund ifreund commented May 15, 2020

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.

TODO:

  • Implement std.log
  • Deprecate std.debug.warn
  • Update documentation
  • Update standard library code using std.debug.warn (perhaps in a follow-up PR)

Will close #2586

lib/std/log.zig Outdated
/// 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 format: []const u8, args: var) void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn log(comptime message_level: Level, comptime format: []const u8, args: var) void {
pub fn log(comptime message_level: Level, comptime format: []const u8, args: var) void {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that making this public would violate the "Only one obvious way to do things" bullet point of the zig zen. I prefer

std.log.warn("message", .{});

over

std.log.log(.warn, "message", {});

personally so I'm inclined to keep this as is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pub fn log will support custom level if Level become non-exhaustive enum.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frankly I'm not a fan of allowing custom log levels when using std.log. It is imo unnecessary complexity and will reduce compatibility between libraries and programs which is the whole point of a unified std.log interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lack of flexibility forces developers to reinvent their own wheels.

@data-man
Copy link
Contributor

Inspired by other libraries (in C++, Rust, Nim):

pub const Level = enum(u32) {
none, //or off, disabled, etc.
trace,
debug,
info,
notice,
warn,
err,
fatal,
_, //for custom level
};

@daurnimator
Copy link
Contributor

Inspired by other libraries (in C++, Rust, Nim):

pub const Level = enum(u32) {
none, //or off, disabled, etc.
trace,
debug,
info,
notice,
warn,
err,
fatal,
_, //for custom level
};

These levels come from the syslog protocol and is only a u3:

           Numerical         Severity
             Code

              0       Emergency: system is unusable
              1       Alert: action must be taken immediately
              2       Critical: critical conditions
              3       Error: error conditions
              4       Warning: warning conditions
              5       Notice: normal but significant condition
              6       Informational: informational messages
              7       Debug: debug-level messages

              Table 2. Syslog Message Severities

@data-man
Copy link
Contributor

Also would be nice supports the log function in structs if it's declared.

@ifreund
Copy link
Member Author

ifreund commented May 16, 2020

Also would be nice supports the log function in structs if it's declared.

@data-man I'm not quite clear on what you're picturing here. Maybe you could give an example?

@data-man
Copy link
Contributor

@ifreund
By analogy with fmt.format.

@ifreund
Copy link
Member Author

ifreund commented May 16, 2020

@data-man this already leverages fmt.format internally. Are you suggesting that there be a way to format things differently if they are passed to log as format arguments? I'm not sure I see the point in that.

@data-man
Copy link
Contributor

@ifreund

Are you suggesting that there be a way to format things differently if they are passed to log as format arguments?

No.
E.g. File hasn't format.
With my proposal you can add log fn to File and trace various things.

Copy link
Contributor

@daurnimator daurnimator left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest we also have a "component"/"facility" comptime argument.

@ifreund
Copy link
Member Author

ifreund commented May 16, 2020

I would suggest we also have a "component"/"facility" comptime argument.

I've been considering adding a scope argument in the form of a non-exhaustive enum to allow the implementer of root.log to filter, for example, debug level messages from a library being used. This seems like a pretty important feature and a non-exhaustive enum would be a good way to add it.

I don't plan on adding additional log levels as I think the current 4 are sufficient and keeping this count low will simplify filtering and make choices about what level to use easier.

@ifreund
Copy link
Member Author

ifreund commented May 16, 2020

I think removing the panic from fatal is the right thing to do, however once the panic is removed it is no longer different enough from err and so I removed the log level completely. I also added a new silent log level as it is a cleaner way to disable logging for release small builds completely without special casing, and also allows the disabling logging for other build types.

@data-man
Copy link
Contributor

@ifreund
What is the visual difference between info, warn and err messages?
How can a user figure out what he sees?
E.g.:

info("Enter the Matrix", .{}); //Should be printed "[info] Enter the Matrix"?
warn("Enter the ", .{"Matrix"}); //Should be printed "[warning] Enter the Matrix"?
err("{} the {}", .{"Enter", "Matrix"}); //Should be printed "[error] Enter the Matrix"?

@ifreund
Copy link
Member Author

ifreund commented May 16, 2020

That's up to the user to implement. For example root.log might look like

pub fn log(comptime level: std.log.Level, comptime format: []const u8, args: var) void {
    const prefix = switch (level) {
        .info => "[INFO] ",
        .warn => "[WARN] ",
        .err => "[ERROR] ",
        .silent => return,
    };
    const held = std.debug.getStderrMutex().acquire();
    defer held.release();
    const stderr = std.debug.getStderrStream();
    nosuspend stderr.print(prefix ++ format, args) catch return;
}

I expect that most non-trivial programs will define root.log in a similar way. I don't think the standard library should force any particular formatting choice though.

If we opt to go with a more "batteries included" approach then something like this example could be made default, perhaps with some pretty colors if run from a tty.

@data-man
Copy link
Contributor

That's up to the user to implement.

Sure.

I don't think the standard library should force any particular formatting choice though.

If my previous example is inserted into the standard library, the user will see three identical messages.

@daurnimator
Copy link
Contributor

I've been considering adding a scope argument in the form of a non-exhaustive enum to allow the implementer of root.log to filter, for example, debug level messages from a library being used. This seems like a pretty important feature and a non-exhaustive enum would be a good way to add it.

I don't see why you'd need a enum: just take a comptime var and let the user provide... anything.
Or if you wanted to reduce the options a little, accept a string. or an enum literal?

@daurnimator
Copy link
Contributor

I think removing the panic from fatal is the right thing to do, however once the panic is removed it is no longer different enough from err and so I removed the log level completely. I also added a new silent log level as it is a cleaner way to disable logging for release small builds completely without special casing, and also allows the disabling logging for other build types.

I would advise following the syslog protocol levels I quoted earlier: they are pervasive across all logging systems. Sure it's hard to know when to use ALERT vs CRIT vs ERR.... but that's already a choice people make elsewhere.

@ifreund
Copy link
Member Author

ifreund commented May 17, 2020

I added a scope parameter in the form of an enum literal. I think that this gives the right amount of flexibility while still allowing for simple filtering with a switch statement.

This now depends on #5203. You can also test by applying this simple patch

diff --git a/src/ir.cpp b/src/ir.cpp
index 6bd4e9b80..0bd44ee1f 100644
--- a/src/ir.cpp
+++ b/src/ir.cpp
@@ -25464,10 +25464,11 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI
             ZigType *child_type = get_const_field_meta_type_optional(ira, source_instr->source_node, payload, "child", 0);
             return get_any_frame_type(ira->codegen, child_type);
         }
+        case ZigTypeIdEnumLiteral:
+            return ira->codegen->builtin_types.entry_enum_literal;
         case ZigTypeIdErrorSet:
         case ZigTypeIdEnum:
         case ZigTypeIdFnFrame:
-        case ZigTypeIdEnumLiteral:
             ir_add_error(ira, source_instr, buf_sprintf(
                 "TODO implement @Type for 'TypeInfo.%s': see https://github.com/ziglang/zig/issues/2907", type_id_name(tagTypeId)));
             return ira->codegen->invalid_inst_gen->value->type;

Still thinking about whether we want to follow the syslog protocol or keep things simple. The simplicity is appealing for smaller projects, though the added granularity of the syslog levels would certainly be useful at scale. I know that zig is intended to work well with massive code bases, so perhaps that is the way to go. It also has the advantage of being widely used and familiar already.

//! // Won't be printed as it gets filtered out by our log function
//! std.log.warn(.lib_that_logs_too_much, "Added 1 + 1\n", .{});
//! }
//! ```
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't sure how example code should be formatted in doc comments, so I just defaulted to some markdown backticks

@daurnimator daurnimator added the standard library This issue involves writing Zig code for the standard library. label May 17, 2020
@ifreund
Copy link
Member Author

ifreund commented May 26, 2020

After spending some time thinking on this, I've decided that @daurnimator is right and we should use the syslog logging levels. I've implement them in this new commit and tweaked the rest of the code accordingly. The example the toplevel doc comment has also been updated. I will edit the PR description with the new commit message.

@ifreund
Copy link
Member Author

ifreund commented Jun 2, 2020

Deprecated std.debug.warn and added a std.debug.print function. I think it makes sense to have something like this for "printf debugging." Last thing this needs before merging is updated documentation if all the code changes look good.

ifreund added a commit to riverwm/river that referenced this pull request Jun 17, 2020
this implements a modified version of the logging interface proposed
here: ziglang/zig#5348
ifreund added 2 commits June 17, 2020 02:14
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
@ifreund
Copy link
Member Author

ifreund commented Jun 17, 2020

Rebased to resolve conflicts.

@ifreund ifreund marked this pull request as ready for review June 17, 2020 16:40
@ifreund
Copy link
Member Author

ifreund commented Jun 18, 2020

Note: this currently depends on @Type() for enum literals from #5203, though this could be avoided by using @TypeOf(.foobar) instead until that gets merged.

@andrewrk andrewrk merged commit 0d18eda into ziglang:master Jun 19, 2020
@ifreund ifreund deleted the std-log branch June 19, 2020 01:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
standard library This issue involves writing Zig code for the standard library.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

introduce the concept of "logging" to the standard library
7 participants