Serial Log System

In the Logging chapter, we enabled serial output. In this chapter, we will implement Zig’s logging system using that serial output. Essentially, this will be the same as what we did with Surtr’s logging output. So, this chapter will be over in no time. Great, you can get to bed early today.

important

The source code for this branch is in whiz-ymir-serial_logsystem branch.

Table of Contents

Overriding Default Implementation

First, let's define all the necessary structs and functions at once:

ymir/log.zig
const Writer = std.io.Writer(
    void,
    LogError,
    write,
);

pub const default_log_options = std.Options{
    .log_level = switch (option.log_level) {
        .debug => .debug,
        .info => .info,
        .warn => .warn,
        .err => .err,
    },
    .logFn = log,
};

fn log(
    comptime level: stdlog.Level,
    comptime scope: @Type(.EnumLiteral),
    comptime fmt: []const u8,
    args: anytype,
) void {
    const level_str = comptime switch (level) {
        .debug => "[DEBUG]",
        .info => "[INFO ]",
        .warn => "[WARN ]",
        .err => "[ERROR]",
    };

    const scope_str = if (@tagName(scope).len <= 7) b: {
        break :b std.fmt.comptimePrint("{s: <7} | ", .{@tagName(scope)});
    } else b: {
        break :b std.fmt.comptimePrint("{s: <7}-| ", .{@tagName(scope)[0..7]});
    };

    std.fmt.format(
        Writer{ .context = {} },
        level_str ++ " " ++ scope_str ++ fmt ++ "\n",
        args,
    ) catch {};
}

As with Surtr, we define default_log_options to override the default std_options. The log_level can be specified at build time, and logFn is set to the log function. The log() function is basically the same as Surtr’s. One difference is that the scope string is limited to a maximum length of 7 characters: if it’s shorter, it’s padded with spaces; if longer, it’s truncated with -.

Add the following to build.zig to use option module:

zig
ymir_module.addOptions("option", options);
ymir.root_module.addOptions("option", options);

To make ymir/log.zig available from main.zig, export it from ymir/ymir.zig. Since exporting it as log could cause confusion with std.log, export it as klog instead:

ymir/ymir.zig
pub const klog = @import("log.zig");

Override the default values by using the defined default_log_options:

ymir/main.zig
const klog = ymir.klog;
pub const std_options = klog.default_log_options;

Initializing Serial

This logging system depends entirely on serial output. Therefore, you need to initialize the serial port first, then pass the Serial instance to the logging system for initialization:

ymir/main.zig
const sr = serial.init();
klog.init(sr);
log.info("Booting Ymir...", .{});

The passed Serial instance is stored in a variable inside log.zig and used for output during logging:

ymir/log.zig
var serial: Serial = undefined;

pub fn init(ser: Serial) void {
    serial = ser;
}

fn write(_: void, bytes: []const u8) LogError!usize {
    serial.writeString(bytes);
    return bytes.len;
}

Summary

With this, the setup for serial output is complete. From now on, you can use serial log output like std.log.info() in any file without needing to import ymir/log.zig. Convenient, isn’t it? When you run it, you should see the scope and log level output together as shown below:

txt
[INFO ] main    | Booting Ymir...

In the Booting Kernel chapter, we validated the BootInfo argument from Surtr. At that time, since we didn't have a logging system set up, we silently returned on validation failure. Now that logging is available, let's enable error output as shown below:

ymir/main.zig
validateBootInfo(boot_info) catch {
    log.err("Invalid boot info", .{});
    return error.InvalidBootInfo;
};

In this chapter, we implemented Zig’s logging system using serial output. From now on, you can use Zig’s logging system without worrying about the serial output working behind the scenes. With this, the groundwork for implementing Ymir is complete. Starting from the next chapter, we will begin replacing various data structures provided by UEFI with Ymir’s own implementation.