Chapter 47Time Logging And Progress

时间、日志和进度

引言

本章完善了Zig中日常操作工具:精确时间测量(std.time)、结构化日志(std.log)和终端友好的进度报告(std.Progress)。在这里,我们使管道可观察、可测量且用户友好。time.ziglog.zigProgress.zig

我们将专注于在Zig 0.15.2下跨平台工作的确定性片段,突出陷阱、性能注意事项和最佳实践。

使用 std.time 进行时间管理

Zig的 std.time 提供: - 日历时间戳:timestamp(), milliTimestamp(), microTimestamp(), nanoTimestamp()。 - 持续时间/单位:诸如 ns_per_ms, ns_per_s, s_per_min 等常量用于转换。 - 高精度计时器:Instant(快速,不严格单调)和 Timer(通过饱和实现单调行为)。

一般来说,在测量经过的持续时间时更喜欢使用 Timer。只有当您需要更快的采样并能容忍来自 quirky OS/固件环境的偶尔非单调性时,才使用 Instant

测量经过的时间 (Timer)

Timer 产生单调读数(在回归时饱和),是基准测试和超时的理想选择。39

Zig
const std = @import("std");

pub fn main() !void {
    var t = try std.time.Timer.start();
    std.Thread.sleep(50 * std.time.ns_per_ms);
    const ns = t.read();
    // Ensure we slept at least 50ms
    if (ns < 50 * std.time.ns_per_ms) return error.TimerResolutionTooLow;
    // Print a stable message
    std.debug.print("Timer OK\n", .{});
}
Run
Shell
$ zig run time_timer_sleep.zig
预期输出
Timer OK

睡眠使用 std.Thread.sleep(ns)。在大多数操作系统上,粒度约为1ms或更差;计时器与底层时钟一样精确。Thread.zig

瞬时采样和排序

Instant.now() 为当前进程提供快速、高精度的时间戳。它尝试在暂停期间前进,可以比较或求差。它不能保证在所有地方都严格单调。当您需要强制执行该属性时,请使用 Timer

Zig
const std = @import("std");

pub fn main() !void {
    const a = try std.time.Instant.now();
    std.Thread.sleep(1 * std.time.ns_per_ms);
    const b = try std.time.Instant.now();
    if (b.order(a) == .lt) return error.InstantNotMonotonic;
    std.debug.print("Instant OK\n", .{});
}
Run
Shell
$ zig run time_instant_order.zig
预期输出
Instant OK

时间单位转换

更喜欢使用提供的单位常量而不是手工编写的数学运算。它们提高清晰度并防止混合单位中的错误。

Zig
const std = @import("std");

pub fn main() !void {
    const two_min_s = 2 * std.time.s_per_min;
    const hour_ns = std.time.ns_per_hour;
    std.debug.print("2 min = {d} s\n1 h = {d} ns\n", .{ two_min_s, hour_ns });
}
Run
Shell
$ zig run time_units.zig
预期输出
2 min = 120 s
1 h = 3600000000000 ns

对于日历计算(年、月、日),请参见 std.time.epoch 辅助函数;对于文件时间戳元数据,请参见 std.fs.File API。28epoch.zigFile.zig

使用 std.log 进行日志记录

std.log 是一个小型、可组合的日志外观。您可以: - 通过 std_options 控制日志级别(默认值取决于构建模式)。 - 使用作用域(命名空间)来分类消息。 - 提供自定义 logFn 来更改格式或重定向。

在下面,我们设置 .log_level = .info 以抑制调试日志,并演示默认和作用域日志记录。

Zig
const std = @import("std");

// Configure logging for this program
pub const std_options: std.Options = .{
    .log_level = .info, // hide debug
    .logFn = std.log.defaultLog,
};

pub fn main() void {
    std.log.debug("debug hidden", .{});
    std.log.info("starting", .{});
    std.log.warn("high temperature", .{});

    const app = std.log.scoped(.app);
    app.info("running", .{});
}
Run
Shell
$ zig run logging_basic.zig 2>&1 | cat
预期输出
info: starting
warning: high temperature
info(app): running

注意: - 默认日志记录器写入stderr,因此我们在上面使用 2>&1 以在本书中内联显示。 - 在Debug构建中,默认级别是 .debug。通过 std_options 覆盖以使示例在优化模式间保持稳定。

使用 std.Progress 进行进度报告

std.Progress 在终端上绘制一个小任务树,定期从另一个线程更新。它不分配内存,旨在跨终端和Windows控制台可移植。使用它来表示长时间运行的工作,如构建、下载或分析过程。

以下演示在练习API(根节点、子节点、completeOneend)时禁用打印以获得确定性输出。在实际工具中,省略 disable_printing 以呈现动态进度视图。

Zig
const std = @import("std");

pub fn main() void {
    // Progress can draw to stderr; disable printing in this demo for deterministic output.
    const root = std.Progress.start(.{ .root_name = "build", .estimated_total_items = 3, .disable_printing = true });
    var compile = root.start("compile", 2);
    compile.completeOne();
    compile.completeOne();
    compile.end();

    var link = root.start("link", 1);
    link.completeOne();
    link.end();

    root.end();
}
Run
Shell
$ zig run progress_basic.zig
预期输出
no output

提示: - 使用 Options.estimated_total_items 显示计数("[3/10] compile"); - 使用 setName 更新名称; - 通过 std.Progress.setStatus 发出成功/失败信号。

注意事项和警告

  • Timer 与 Instant:对于经过时间和单调行为更喜欢 Timer;当偶尔的非单调性可接受,对于快速采样使用 Instant
  • 睡眠分辨率取决于操作系统。不要假设亚毫秒精度。
  • 日志过滤器按作用域应用。使用 scoped(.your_component) 干净地屏蔽嘈杂的子系统。
  • std.Progress 输出适应终端功能。在CI/非TTY或禁用打印时,不写入任何内容。
  • 时区支持:stdlib在0.15.2中尚没有稳定的 std.tz 模块。如果您需要时区数学,请使用平台API或库。[待定]

练习

  • 编写一个使用 Timer 的微基准测试来比较两种格式化例程。打印更快的一种以及快了多少微秒。
  • 用自定义 logFn 包装 std.log,该函数用 nanoTimestamp() 前缀时间戳。确保它保持无分配。
  • 使用 std.Progress 创建一个显示三个阶段的小型构建模拟器。使第二阶段动态增加 estimated_total_items

Open Questions

  • std中的时区辅助函数:未来 std.tz 或等效物的状态和路线图?[待定]

Help make this chapter better.

Found a typo, rough edge, or missing explanation? Open an issue or propose a small improvement on GitHub.