98 lines
4.2 KiB
Zig
98 lines
4.2 KiB
Zig
//! The per-process logger: std.log wired to the tagged kernel log ring.
|
|
//!
|
|
//! A program just calls `std.log.info("mounted {s}", .{path})` (or a scoped
|
|
//! logger); this backend formats the line into a fixed buffer and emits ONE
|
|
//! `debug_write` record carrying the level. The kernel stamps the record with
|
|
//! the sender's pid and task name (its binary path) — the process does NOT put
|
|
//! its own name in the payload; attribution is the kernel's, structural and
|
|
//! unforgeable. Serial shows the kernel-rendered `<path>: message` line, and
|
|
//! the logger service demultiplexes the ring into one file per process.
|
|
//!
|
|
//! Installed for every user binary by the root shim (library/runtime/root.zig)
|
|
//! via `std_options`; a program can override by declaring its own
|
|
//! `pub const std_options`.
|
|
|
|
const std = @import("std");
|
|
const abi = @import("abi");
|
|
const sc = @import("system-call");
|
|
|
|
// --- the tagged log ring: raw wrappers + record types, formerly in the system.zig dump ---
|
|
|
|
/// A log record's level and the ring's framing types (re-exported from the shared ABI so
|
|
/// callers and the logger service don't import `abi` themselves).
|
|
pub const KlogLevel = abi.KlogLevel;
|
|
pub const KlogStatus = abi.KlogStatus;
|
|
pub const KlogRecordHeader = abi.KlogRecordHeader;
|
|
pub const klog_record_header_size = abi.klog_record_header_size;
|
|
pub const klog_record_alignment = abi.klog_record_alignment;
|
|
pub const klog_record_magic = abi.klog_record_magic;
|
|
pub const klog_flag_truncated = abi.klog_flag_truncated;
|
|
pub const klog_maximum_message = abi.klog_maximum_message;
|
|
pub const maximum_process_name = abi.maximum_process_name;
|
|
|
|
/// Write raw bytes to the kernel log (bring-up/panic diagnostics; ordinary output goes
|
|
/// through std.log -> writeRecord). The kernel stamps the record with this process's id
|
|
/// and name. Returns the byte count, or a wrapped -1.
|
|
pub fn write(message: []const u8) usize {
|
|
return writeRecord(.raw, message);
|
|
}
|
|
|
|
/// Emit one leveled record into the tagged kernel log ring. The kernel stamps
|
|
/// pid/name/sequence/timestamp; the payload should be a single line.
|
|
pub fn writeRecord(level: KlogLevel, message: []const u8) usize {
|
|
return sc.systemCall3(.debug_write, @intFromPtr(message.ptr), message.len, @intFromEnum(level));
|
|
}
|
|
|
|
/// Copy framed records out of the tagged kernel log ring starting at stream `offset` into
|
|
/// `out`. Returns the byte count (0 = caught up), or null when `offset` fell behind the
|
|
/// ring's tail or lies past its head (re-sync via `klogStatus`).
|
|
pub fn klogRead(offset: u64, out: []u8) ?usize {
|
|
const r = sc.systemCall3(.klog_read, offset, @intFromPtr(out.ptr), out.len);
|
|
if (@as(isize, @bitCast(r)) < 0) return null;
|
|
return r;
|
|
}
|
|
|
|
/// The log ring's live cursors (oldest retained offset, end of stream, next sequence)
|
|
/// plus the wall-clock time of boot — how a log reader starts, detects loss, and names a
|
|
/// per-boot log directory.
|
|
pub fn klogStatus() ?KlogStatus {
|
|
var status: KlogStatus = undefined;
|
|
if (@as(isize, @bitCast(sc.systemCall1(.klog_status, @intFromPtr(&status)))) != 0) return null;
|
|
return status;
|
|
}
|
|
|
|
fn levelOf(comptime level: std.log.Level) KlogLevel {
|
|
return switch (level) {
|
|
.err => .err,
|
|
.warn => .warn,
|
|
.info => .info,
|
|
.debug => .debug,
|
|
};
|
|
}
|
|
|
|
pub fn logFn(
|
|
comptime level: std.log.Level,
|
|
comptime scope: @EnumLiteral(),
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) void {
|
|
// One record = one line = at most klog_maximum_message bytes of payload.
|
|
// On overflow keep what fits and end with "~" so the record is still a
|
|
// whole line (the kernel would split an embedded rest anyway).
|
|
var buffer: [256]u8 = undefined;
|
|
const prefix = if (scope == .default) "" else "(" ++ @tagName(scope) ++ ") ";
|
|
const line = std.fmt.bufPrint(&buffer, prefix ++ format, args) catch truncated: {
|
|
buffer[buffer.len - 1] = '~';
|
|
break :truncated buffer[0..];
|
|
};
|
|
_ = writeRecord(levelOf(level), line);
|
|
}
|
|
|
|
/// The std.Options the root shim installs unless the program overrides it.
|
|
/// Debug level: filtering is the log *reader's* job here — the ring is cheap,
|
|
/// serial is a dev convenience, and the logger service keeps everything.
|
|
pub const default_options: std.Options = .{
|
|
.log_level = .debug,
|
|
.logFn = logFn,
|
|
};
|