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_msns_per_ss_per_min 之类的常量用于转换。 - 高精度定时器:Instant(快速,不是严格单调)和 Timer(通过饱和实现单调行为)。

一般来说,对于测量经过的持续时间,优先使用 Timer。仅当您需要更快的采样并且可以容忍来自奇怪操作系统/固件环境的偶发非单调性时,才使用 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();
    // 确保我们至少睡了 50ms
    if (ns < 50 * std.time.ns_per_ms) return error.TimerResolutionTooLow;
    // 打印稳定的消息
    std.debug.print("Timer OK\n", .{});
}
运行
Shell
$ zig run time_timer_sleep.zig
预期输出
Timer OK

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

Instant 采样与排序

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", .{});
}
运行
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 });
}
运行
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, // 隐藏调试信息
    .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", .{});
}
运行
Shell
$ zig run logging_basic.zig 2>&1 | cat
预期输出
info: starting
warning: high temperature
info(app): running

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

使用 std.Progress 报告进度

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

下述演示在练习 API(根节点、子节点、completeOneend)的同时,禁用打印以获得确定性输出。在真实工具中,去掉disable_printing即可呈现动态进度视图。

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

pub fn main() void {
    // 进度可以绘制到 stderr;在此演示中禁用打印以获得确定性输出。
    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();
}
运行
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 或库。[TBD]

练习

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

开放问题

  • 标准库中的时区助手:未来 std.tz 或等效模块的状态和路线图?[TBD]

Help make this chapter better.

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