8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

log.zigを散歩する

Last updated at Posted at 2022-07-24

はじめに

Standard Libraryのlog.zigのソースコードを読んで、使い方を理解するだけのお話です。

環境

  • MacOS 11.6.6
  • Zig 0.9.1

読んでみましょう

ソースはこちらです。

1. 最初の70行程度

コメントが書かれていますね。要約するとこんな感じです。

  1. ロギングを可能にするライブラリです。
  2. スコープの設定がないと.defaultでセットされる。
    • 後述します。
  3. std.log.scoped 関数で任意のスコープパラメータを設定できる。
    • scopedで説明します。
  4. root.logで上書きする方法について。
    • root: 平易に言うと実行ファイルのこと(main.zigとか)
    • root.log: 実行ファイルのlog関数のこと

2. ログレベルやスコープレベルの処理

ここで覚えておきたい点は2点ありますね。

  • log_levelはrootで宣言したものが優先される

    pub const level: Level = if (@hasDecl(root, "log_level"))
        root.log_level
    else
        default_level;
    
  • scope_levelsもrootで宣言したものが優先される

    const scope_levels = if (@hasDecl(root, "scope_levels"))
        root.scope_levels
    else
        [0]ScopeLevel{};
    

例えば、main.ziglog_levelを下記のように宣言すると

main.zig
pub const log_level: std.log.Level = switch (builtin.mode) {
    .Debug, .ReleaseSafe => .debug,
    .ReleaseFast, .ReleaseSmall => .err,
};

pub fn main() anyerror!void {
    std.log.debug("All your codebase are belong to us.", .{});
}

info ではなく、 debug でログが出力されます。

$ zig build run

debug: All your codebase are belong to us.

scope_levelsの変更例も挙げます。

main.zig
pub const scope_levels = [1]std.log.ScopeLevel{
    std.log.ScopeLevel{ .scope = .default, .level = .err },
};

pub fn main() anyerror!void {
    std.log.info("All your codebase are belong to us.", .{});
}

本来であれば、infoレベルの結果が返されますが、errレベルの結果のみを返すように変更したため、下記のように何も返りません。

$ zig build run

3. log関数の処理

気になった処理に絞って説明します。

const effective_log_level = blk: {
    inline for (scope_levels) |scope_level| {
        if (scope_level.scope == scope) break :blk scope_level.level;
    }
    break :blk level;
};

上記は、先程のscope_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);
    }

ここが、先ほどのroot.logを上書きする処理になります。
上書き例を下記に記載します。

main.zig
pub fn log(
    comptime level: std.log.Level,
    comptime scope: @TypeOf(.EnumLiteral),
    comptime format: []const u8,
    args: anytype,
) void {
    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 = "[" ++ level.asText() ++ "] " ++ scope_prefix;

    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() anyerror!void {
    std.log.info("All your codebase are belong to us.", .{});
}

defaultLogと異なるフォーマットのログが出力されます。

$ zig build run
[info] (default): All your codebase are belong to us.

defaultLogについては解説しませんが、root.logで上書きする際に参考になるので一読することをおすすめします。

4. scoped関数の処理

面白い書き方だなと思いました。例えば、スコープをmainとした時に、
std.log.scoped(.main).info("hoge", .{});
となり、関数型の組み上げ方を意識されているのかなと思いました。

こちらも気になった処理について説明します。

pub const emerg = @compileError("deprecated; use err instead of emerg");
pub const alert = @compileError("deprecated; use err instead of alert");
pub const crit = @compileError("deprecated; use err instead of crit");
...
pub const notice = @compileError("deprecated; use info instead of notice");

うーん、他言語使いへの配慮ですかね…?インテリセンスが整ってきたら逆に邪魔になるような気がしてしまうのですが、どうなんですかね?

pub fn info(
    comptime format: []const u8,
    args: anytype,
) void {
    log(.info, scope, format, args);
}

基本的に、scopedの戻り値はこれらのstructになります。
実際にスコープを変えてみましょう。

main.zig
pub fn main() anyerror!void {
    const hoge_log = std.log.scoped(.hoge);
    hoge_log.info("All your codebase are belong to us.", .{});
}

結果は下記のようになります。

$ zig build run
info(hoge): All your codebase are belong to us.

ちなみに、ここの.hogeに相当する@Type(.EnumLiteral)は「タグ値が名前に対応しない場合、安全が確認された未定義の動作を呼び出す」らしい。つまり、誤字っても通るので注意。(詳細はtagNameからどうぞ)

4. default設定

スコープの設定がないと.defaultでセットされる話をします。

pub const default = scoped(.default);
...
pub const info = default.info;

このように各種ログレベルはscopedに変更がない場合、.defaultという未定義のタグ値が勝手に入ります。個人的に、defaultって何?って感じなのでもっと分かりやすい命名にならないかなと思っています。

おわりに

Zigのコードを読むのは、今回が初めてだったのですがなかなか読みやすいですね。
ただ、logの機能としては他言語に比べると弱いなと感じました。
moduleのトレースやdatetimeなどが私が探した限りなかったので、handlerやformatter等の機能が十分に作れていないように感じられました。今後に期待しています。

参考文献

zig org: https://ziglang.org/documentation/master/
log.zig: https://github.com/ziglang/zig/blob/master/lib/std/log.zig

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?