Chapter 47Time Logging And Progress

Time, Logging, and Progress

Introduction

This chapter rounds out everyday operational tools in Zig: precise time measurement (std.time), structured logging (std.log), and terminal-friendly progress reporting (std.Progress). Here we make pipelines observable, measurable, and user-friendly. time.ziglog.zigProgress.zig

We’ll focus on deterministic snippets that work across platforms under Zig 0.15.2, highlighting gotchas, performance notes, and best practices.

Timekeeping with std.time

Zig’s std.time provides: - Calendar timestamps: timestamp(), milliTimestamp(), microTimestamp(), nanoTimestamp(). - Duration/units: constants like ns_per_ms, ns_per_s, s_per_min for conversions. - High-precision timers: Instant (fast, not strictly monotonic) and Timer (monotonic behavior by saturation).

In general, prefer Timer for measuring elapsed durations. Reach for Instant only when you need faster sampling and can tolerate occasional non-monotonicity from quirky OS/firmware environments.

Measuring elapsed time (Timer)

Timer yields monotonic readings (saturating on regressions) and is ideal for benchmarking and timeouts. 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
Expected output
Timer OK

Sleeping uses std.Thread.sleep(ns). On most OSes the granularity is ~1ms or worse; timers are as precise as the underlying clocks permit. Thread.zig

Instant sampling and ordering

Instant.now() gives a fast, high-precision timestamp for the current process. It tries to advance during suspend and can be compared or differenced. It is not guaranteed strictly monotonic everywhere. Use Timer when you need that property enforced.

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
Expected output
Instant OK

Time unit conversions

Prefer the provided unit constants over hand-rolled math. They improve clarity and prevent mistakes in mixed units.

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
Expected output
2 min = 120 s
1 h = 3600000000000 ns

For calendar computations (year, month, day), see std.time.epoch helpers; for file timestamp metadata, see std.fs.File APIs. 28, epoch.zig, File.zig

Logging with std.log

std.log is a small, composable logging façade. You can: - Control log level via std_options (defaults are build-mode dependent). - Use scopes (namespaces) to categorize messages. - Provide a custom logFn to change formatting or redirects.

Below, we set .log_level = .info so debug logs are suppressed, and demonstrate both default and scoped logging.

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
Expected output
info: starting
warning: high temperature
info(app): running

NOTE: - The default logger writes to stderr, so we use 2>&1 above to show it inline in this book. - In Debug builds the default level is .debug. Override via std_options to make examples stable across optimize modes.

Progress reporting with std.Progress

std.Progress draws a small tree of tasks to the terminal, updating periodically from another thread. It is non-allocating and aims to be portable across terminals and Windows consoles. Use it to indicate long-running work such as builds, downloads, or analysis passes.

The following demo disables printing for deterministic output while exercising the API (root node, children, completeOne, end). In real tools, omit disable_printing to render a dynamic progress view.

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
Expected output
no output

TIP: - Use Options.estimated_total_items to show counts (“[3/10] compile”); - Update names with setName; - Signal success/failure via std.Progress.setStatus.

Notes and caveats

  • Timer vs. Instant: prefer Timer for elapsed time and monotonic behavior; use Instant for fast samples when occasional non-monotonicity is acceptable.
  • Sleep resolution is OS-dependent. Don’t assume sub-millisecond precision.
  • Logging filters apply per scope. Use scoped(.your_component) to gate noisy subsystems cleanly.
  • std.Progress output adapts to terminal capabilities. On CI/non-TTY or disabled printing, nothing is written.
  • Timezone support: stdlib does not yet have a stable std.tz module in 0.15.2. Use platform APIs or a library if you need timezone math. [TBD]

Exercises

  • Write a micro-benchmark using Timer to compare two formatting routines. Print the faster one and by how many microseconds.
  • Wrap std.log with a custom logFn that prefixes timestamps from nanoTimestamp(). Ensure it remains non-allocating.
  • Create a small build simulator with std.Progress showing three phases. Make the second phase dynamically increase estimated_total_items.

Open Questions

  • Timezone helpers in std: status and roadmap for a future std.tz or equivalent? [TBD]

Help make this chapter better.

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