From 497388fa08be659168e159fe1e0498d025a2b445 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 17 Feb 2025 21:19:07 +0800 Subject: [PATCH 1/2] Start adding structure logging, output to logfmt --- src/log.zig | 312 +++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 37 ++++-- src/unit_tests.zig | 1 + 3 files changed, 338 insertions(+), 12 deletions(-) create mode 100644 src/log.zig diff --git a/src/log.zig b/src/log.zig new file mode 100644 index 000000000..43f9c9210 --- /dev/null +++ b/src/log.zig @@ -0,0 +1,312 @@ +const std = @import("std"); + +const Allocator = std.mem.Allocator; + +var mutex: std.Thread.Mutex = .{}; + +const LogLevel : Log.Level = blk: { + const root = @import("root"); + break :blk if (@hasDecl(root, "LogLevel")) root.LogLevel else .info; +}; + +pub const Log = LogT(std.fs.File); + +// Generic so that we can test it against an ArrayList +fn LogT(comptime Out: type) type { + return struct { + out: Out, + inject: ?[]const u8, + allocator: Allocator, + buffer: std.ArrayListUnmanaged(u8), + + const Self = @This(); + + pub const Level = enum { + debug, + info, + warn, + err, + fatal, + }; + + // + pub fn init(allocator: Allocator) !Self { + return initTo(allocator, std.io.getStdErr()); + } + + // Used for tests + fn initTo(allocator: Allocator, out: Out) !Self { + var buffer: std.ArrayListUnmanaged(u8) = .{}; + try buffer.ensureTotalCapacity(allocator, 2048); + + return .{ + .out = out, + .inject = null, + .buffer = buffer, + .allocator = allocator, + }; + } + + pub fn deinit(self: *Self) void { + self.buffer.deinit(self.allocator); + } + + pub fn enabled(comptime level: Level) bool { + return @intFromEnum(level) >= @intFromEnum(LogLevel); + } + + pub fn debug(self: *Self, comptime ctx: []const u8, data: anytype) void { + self.log(.debug, ctx, data); + } + + pub fn info(self: *Self, comptime ctx: []const u8, data: anytype) void { + self.log(.info, ctx, data); + } + + pub fn warn(self: *Self, comptime ctx: []const u8, data: anytype) void { + self.log(.warn, ctx, data); + } + + pub fn err(self: *Self, comptime ctx: []const u8, data: anytype) void { + self.log(.err, ctx, data); + } + + pub fn fatal(self: *Self, comptime ctx: []const u8, data: anytype) void { + self.log(.fatal, ctx, data); + } + + fn log(self: *Self, comptime level: Level, comptime ctx: []const u8, data: anytype) void { + if (comptime enabled(level) == false) { + return; + } + defer self.buffer.clearRetainingCapacity(); + self._log(level, ctx, data) catch |e| { + std.debug.print("log error: {} ({s} - {s})\n", .{ e, @tagName(level), ctx }); + }; + } + + fn _log(self: *Self, comptime level: Level, comptime ctx: []const u8, data: anytype) !void { + const now = getTime(); + const allocator = self.allocator; + + // We use *AssumeCapacity here because we expect buffer to have + // a reasonable default size. We expect time + level + ctx + inject + // to fit in the initial buffer; + var buffer = &self.buffer; + std.debug.assert(buffer.capacity >= 1024); + + buffer.appendSliceAssumeCapacity("_time="); + try std.fmt.format(buffer.writer(allocator), "{d}", .{now}); + + const level_and_ctx = " _level=" ++ @tagName(level) ++ " _ctx=" ++ ctx; + buffer.appendSliceAssumeCapacity(level_and_ctx); + + if (self.inject) |inject| { + buffer.appendAssumeCapacity(' '); + buffer.appendSliceAssumeCapacity(inject); + } + + inline for (@typeInfo(@TypeOf(data)).Struct.fields) |f| { + // + 2 for the leading space and the equal sign + // + 5 to save space for null/false/true common values + const key_len = f.name.len + 7; + try buffer.ensureUnusedCapacity(allocator, key_len); + buffer.appendAssumeCapacity(' '); + buffer.appendSliceAssumeCapacity(f.name); + buffer.appendAssumeCapacity('='); + try writeValue(allocator, buffer, @field(data, f.name)); + } + try buffer.append(allocator, '\n'); + + mutex.lock(); + defer mutex.unlock(); + try self.out.writeAll(self.buffer.items); + } + }; +} + +fn writeValue(allocator: Allocator, buffer: *std.ArrayListUnmanaged(u8), value: anytype) !void { + const T = @TypeOf(value); + switch (@typeInfo(T)) { + .Optional => { + if (value) |v| { + return writeValue(allocator, buffer, v); + } + // in _log, we reserved space for a value of up to 5 bytes. + return buffer.appendSliceAssumeCapacity("null"); + }, + .ComptimeInt, .Int, .ComptimeFloat, .Float => { + return std.fmt.format(buffer.writer(allocator), "{d}", .{value}); + }, + .Bool => { + // in _log, we reserved space for a value of up to 5 bytes. + return buffer.appendSliceAssumeCapacity(if (value) "true" else "false"); + }, + .ErrorSet => return buffer.appendSlice(allocator, @errorName(value)), + .Enum => return buffer.appendSlice(allocator, @tagName(value)), + .Array => return writeValue(allocator, buffer, &value), + .Pointer => |ptr| switch (ptr.size) { + .Slice => switch (ptr.child) { + u8 => return writeString(allocator, buffer, value), + else => {}, + }, + .One => switch (@typeInfo(ptr.child)) { + .Array => |arr| if (arr.child == u8) { + return writeString(allocator, buffer, value); + }, + else => return false, + }, + else => {}, + }, + else => {}, + } + @compileError("cannot log a: " ++ @typeName(T)); +} + +fn writeString(allocator: Allocator, buffer: *std.ArrayListUnmanaged(u8), value: []const u8) !void { + var space_count: usize = 0; + var escape_count: usize = 0; + var binary_count: usize = 0; + + for (value) |b| { + switch (b) { + '\r', '\n', '"' => escape_count += 1, + ' ' => space_count += 1, + '\t', '!', '#'...'~' => {}, // printable characters + else => binary_count += 1, + } + } + + if (binary_count > 0) { + // TODO: use a different encoding if the ratio of binary data / printable + // is low + // TODO: Zig 0.14 adds an encodeWriter + return buffer.appendSlice(allocator, "\" (will be supported once we move to Zig 0.14\""); + // return std.base64.standard_no_pad.Encoder.encodeWriter(buffer.writer(allocator), value); + } + + if (escape_count == 0) { + if (space_count == 0) { + return buffer.appendSlice(allocator, value); + } + try buffer.ensureUnusedCapacity(allocator, 2 + value.len); + buffer.appendAssumeCapacity('"'); + buffer.appendSliceAssumeCapacity(value); + buffer.appendAssumeCapacity('"'); + return; + } + + // + 2 for the quotes + // + escape_count because every character that needs escaping is + 1 + try buffer.ensureUnusedCapacity(allocator, 2 + value.len + escape_count); + + buffer.appendAssumeCapacity('"'); + + var rest = value; + while (rest.len > 0) { + const pos = std.mem.indexOfAny(u8, rest, "\r\n\"") orelse { + buffer.appendSliceAssumeCapacity(rest); + break; + }; + buffer.appendSliceAssumeCapacity(rest[0..pos]); + buffer.appendAssumeCapacity('\\'); + switch (rest[pos]) { + '"' => buffer.appendAssumeCapacity('"'), + '\r' => buffer.appendAssumeCapacity('r'), + '\n' => buffer.appendAssumeCapacity('n'), + else => unreachable, + } + rest = rest[pos + 1 ..]; + } + + buffer.appendAssumeCapacity('"'); +} + +fn getTime() i64 { + if (comptime @import("builtin").is_test) { + return 1739795092929; + } + return std.time.milliTimestamp(); +} + +const testing = std.testing; +const TestLogger = LogT(std.ArrayListUnmanaged(u8).Writer); + +test "log: data" { + var buf: std.ArrayListUnmanaged(u8) = .{}; + defer buf.deinit(testing.allocator); + + var log = try TestLogger.initTo(testing.allocator, buf.writer(testing.allocator)); + defer log.deinit(); + + { + log.err("nope", .{}); + try testing.expectEqualStrings("_time=1739795092929 _level=err _ctx=nope\n", buf.items); + } + + { + buf.clearRetainingCapacity(); + const string = try testing.allocator.dupe(u8, "spice_must_flow"); + defer testing.allocator.free(string); + + log.warn("a_ctx", .{ + .cint = 5, + .cfloat = 3.43, + .int = @as(i16, -49), + .float = @as(f32, 0.0003232), + .bt = true, + .bf = false, + .nn = @as(?i32, 33), + .n = @as(?i32, null), + .lit = "over9000!", + .slice = string, + .err = error.Nope, + .level = Log.Level.warn, + }); + + try testing.expectEqualStrings( + "_time=1739795092929 _level=warn _ctx=a_ctx " ++ + "cint=5 cfloat=3.43 int=-49 float=0.0003232 bt=true bf=false " ++ + "nn=33 n=null lit=over9000! slice=spice_must_flow " ++ + "err=Nope level=warn\n" + , buf.items); + } +} + +test "log: string escape" { + var buf: std.ArrayListUnmanaged(u8) = .{}; + defer buf.deinit(testing.allocator); + + var log = try TestLogger.initTo(testing.allocator, buf.writer(testing.allocator)); + defer log.deinit(); + + const prefix = "_time=1739795092929 _level=err _ctx=test "; + { + log.err("test", .{.string = "hello world"}); + try testing.expectEqualStrings(prefix ++ "string=\"hello world\"\n", buf.items); + } + + { + buf.clearRetainingCapacity(); + log.err("test", .{.string = "\n \thi \" \" "}); + try testing.expectEqualStrings(prefix ++ "string=\"\\n \thi \\\" \\\" \"\n", buf.items); + } + + // TODO: Zig 0.14 + // { + // log.err("test", .{.string = [_]u8{0, 244, 55, 77}}); + // try testing.expectEqualStrings(prefix ++ "string=\"\\n \\thi \\\" \\\" \"\n", buf.items); + // } +} + +test "log: with inject" { + var buf: std.ArrayListUnmanaged(u8) = .{}; + defer buf.deinit(testing.allocator); + + var log = try TestLogger.initTo(testing.allocator, buf.writer(testing.allocator)); + defer log.deinit(); + + log.inject = "conn_id=339494"; + log.fatal("hit", .{.over = 9000}); + try testing.expectEqualStrings("_time=1739795092929 _level=fatal _ctx=hit conn_id=339494 over=9000\n", buf.items); +} diff --git a/src/main.zig b/src/main.zig index c5c04996c..344e707f9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -31,11 +31,8 @@ pub const Types = jsruntime.reflect(apiweb.Interfaces); pub const UserContext = apiweb.UserContext; pub const IO = @import("asyncio").Wrapper(jsruntime.Loop); -// Simple blocking websocket connection model -// ie. 1 thread per ws connection without thread pool and epoll/kqueue -pub const websocket_blocking = true; - -const log = std.log.scoped(.cli); +const Log = @import("log.zig").Log; +pub const LogLevel = Log.Level.debug; pub const std_options = .{ // Set the log level to info @@ -100,7 +97,7 @@ const CliMode = union(CliModeTag) { dump: bool = false, }; - fn init(alloc: std.mem.Allocator, args: *std.process.ArgIterator) !CliMode { + fn init(alloc: std.mem.Allocator, args: *std.process.ArgIterator, log: *Log) !CliMode { args.* = try std.process.argsWithAllocator(alloc); errdefer args.deinit(); @@ -220,9 +217,12 @@ pub fn main() !void { } } + var log = try Log.init(alloc); + defer log.deinit(); + // args var args: std.process.ArgIterator = undefined; - const cli_mode = CliMode.init(alloc, &args) catch |err| { + const cli_mode = CliMode.init(alloc, &args, &log) catch |err| { if (err == error.NoError) { std.posix.exit(0); } else { @@ -235,7 +235,11 @@ pub fn main() !void { switch (cli_mode) { .server => |opts| { const address = std.net.Address.parseIp4(opts.host, opts.port) catch |err| { - log.err("address (host:port) {any}\n", .{err}); + log.fatal("main_opts_address", .{ + .host = opts.host, + .port = opts.port, + .err = err, + }); return printUsageExit(opts.execname, 1); }; @@ -244,13 +248,16 @@ pub fn main() !void { const timeout = std.time.ns_per_s * @as(u64, opts.timeout); server.run(alloc, address, timeout, &loop) catch |err| { - log.err("Server error", .{}); + log.fatal("main_opts_server", .{.err = err}); return err; }; }, .fetch => |opts| { - log.debug("Fetch mode: url {s}, dump {any}", .{ opts.url, opts.dump }); + log.debug("main_fetch", .{ + .url = opts.url, + .dump = opts.dump, + }); // vm const vm = jsruntime.VM.init(); @@ -272,11 +279,17 @@ pub fn main() !void { _ = page.navigate(opts.url, null) catch |err| switch (err) { error.UnsupportedUriScheme, error.UriMissingHost => { - log.err("'{s}' is not a valid URL ({any})\n", .{ opts.url, err }); + log.fatal("main_fetch_invalid_url", .{ + .url = opts.url, + .err = err, + }); return printUsageExit(opts.execname, 1); }, else => { - log.err("'{s}' fetching error ({any})s\n", .{ opts.url, err }); + log.fatal("main_fetch_error", .{ + .url = opts.url, + .err = err, + }); return printUsageExit(opts.execname, 1); }, }; diff --git a/src/unit_tests.zig b/src/unit_tests.zig index 7508821e1..41433136f 100644 --- a/src/unit_tests.zig +++ b/src/unit_tests.zig @@ -344,4 +344,5 @@ test { std.testing.refAllDecls(@import("storage/storage.zig")); std.testing.refAllDecls(@import("iterator/iterator.zig")); std.testing.refAllDecls(@import("server.zig")); + std.testing.refAllDecls(@import("log.zig")); } From 1dae3fdd0630ccb9e6cb3269deed3db6aa376343 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 20 Feb 2025 17:37:08 +0800 Subject: [PATCH 2/2] add friendlier output in debug --- src/log.zig | 126 +++++++++++++++++++++++++++++++++++++++------------ src/main.zig | 24 +++++----- 2 files changed, 108 insertions(+), 42 deletions(-) diff --git a/src/log.zig b/src/log.zig index 43f9c9210..9a332e001 100644 --- a/src/log.zig +++ b/src/log.zig @@ -1,7 +1,11 @@ const std = @import("std"); +const builtin = @import("builtin"); const Allocator = std.mem.Allocator; +// synchronizes writes to the output +// in debug mode, also synchronizes the timestamp counter for a more human- +// readable time display var mutex: std.Thread.Mutex = .{}; const LogLevel : Log.Level = blk: { @@ -9,10 +13,10 @@ const LogLevel : Log.Level = blk: { break :blk if (@hasDecl(root, "LogLevel")) root.LogLevel else .info; }; -pub const Log = LogT(std.fs.File); +pub const Log = LogT(std.fs.File, builtin.mode == .Debug); // Generic so that we can test it against an ArrayList -fn LogT(comptime Out: type) type { +fn LogT(comptime Out: type, comptime enhanced_readability: bool) type { return struct { out: Out, inject: ?[]const u8, @@ -25,7 +29,7 @@ fn LogT(comptime Out: type) type { debug, info, warn, - err, + @"error", fatal, }; @@ -55,51 +59,91 @@ fn LogT(comptime Out: type) type { return @intFromEnum(level) >= @intFromEnum(LogLevel); } - pub fn debug(self: *Self, comptime ctx: []const u8, data: anytype) void { - self.log(.debug, ctx, data); + pub fn debug(self: *Self, comptime msg: []const u8, data: anytype) void { + self.log(.debug, msg, data); } - pub fn info(self: *Self, comptime ctx: []const u8, data: anytype) void { - self.log(.info, ctx, data); + pub fn info(self: *Self, comptime msg: []const u8, data: anytype) void { + self.log(.info, msg, data); } - pub fn warn(self: *Self, comptime ctx: []const u8, data: anytype) void { - self.log(.warn, ctx, data); + pub fn warn(self: *Self, comptime msg: []const u8, data: anytype) void { + self.log(.warn, msg, data); } - pub fn err(self: *Self, comptime ctx: []const u8, data: anytype) void { - self.log(.err, ctx, data); + pub fn err(self: *Self, comptime msg: []const u8, data: anytype) void { + self.log(.@"error", msg, data); } - pub fn fatal(self: *Self, comptime ctx: []const u8, data: anytype) void { - self.log(.fatal, ctx, data); + pub fn fatal(self: *Self, comptime msg: []const u8, data: anytype) void { + self.log(.fatal, msg, data); } - fn log(self: *Self, comptime level: Level, comptime ctx: []const u8, data: anytype) void { + fn log(self: *Self, comptime level: Level, comptime msg: []const u8, data: anytype) void { if (comptime enabled(level) == false) { return; } defer self.buffer.clearRetainingCapacity(); - self._log(level, ctx, data) catch |e| { - std.debug.print("log error: {} ({s} - {s})\n", .{ e, @tagName(level), ctx }); + self._log(level, msg, data) catch |e| { + std.debug.print("log error: {} ({s} - {s})\n", .{ e, @tagName(level), msg }); }; } - fn _log(self: *Self, comptime level: Level, comptime ctx: []const u8, data: anytype) !void { - const now = getTime(); + fn _log(self: *Self, comptime level: Level, comptime msg: []const u8, data: anytype) !void { const allocator = self.allocator; // We use *AssumeCapacity here because we expect buffer to have - // a reasonable default size. We expect time + level + ctx + inject + // a reasonable default size. We expect time + level + msg + inject // to fit in the initial buffer; var buffer = &self.buffer; - std.debug.assert(buffer.capacity >= 1024); - buffer.appendSliceAssumeCapacity("_time="); - try std.fmt.format(buffer.writer(allocator), "{d}", .{now}); + comptime { + if (msg.len > 512) { + @compileError("log msg cannot be greater than 512 characters: '" ++ msg ++ "'"); + } + for (msg) |b| { + switch (b) { + 'A'...'Z', 'a'...'z', ' ', '0'...'9', '_', '-', '.', '{', '}' => {}, + else => @compileError("log msg contains an invalid character '" ++ msg ++ "'"), + } + } + } - const level_and_ctx = " _level=" ++ @tagName(level) ++ " _ctx=" ++ ctx; - buffer.appendSliceAssumeCapacity(level_and_ctx); + std.debug.assert(buffer.capacity >= 1024); + + if (comptime enhanced_readability) { + // used when developing, and we prefer readability over having + // the output in logfmt + switch (level) { + .warn => buffer.appendSliceAssumeCapacity("\x1b[33m"), + .@"error" => buffer.appendSliceAssumeCapacity("\x1b[31m"), + .fatal => buffer.appendSliceAssumeCapacity("\x1b[41m"), + else => {}, + } + + const level_and_msg = @tagName(level) ++ "\x1b[0m | " ++ msg ++ " | "; + buffer.appendSliceAssumeCapacity(level_and_msg); + const since_last_log = msSinceLastLog(); + + if (since_last_log > 1000) { + buffer.appendSliceAssumeCapacity("\x1b[35m"); + } + try std.fmt.format(buffer.writer(allocator), "{d}\x1b[0m |", .{since_last_log}); + + } else { + buffer.appendSliceAssumeCapacity("_time="); + try std.fmt.format(buffer.writer(allocator), "{d}", .{getTime()}); + + const level_and_msg = comptime blk: { + // only wrap msg in quotes if it contains a space + const lm = " _level=" ++ @tagName(level) ++ " _msg="; + if (std.mem.indexOfScalar(u8, msg, ' ') == null) { + break :blk lm ++ msg; + } + break :blk lm ++ "\"" ++ msg ++ "\""; + }; + buffer.appendSliceAssumeCapacity(level_and_msg); + } if (self.inject) |inject| { buffer.appendAssumeCapacity(' '); @@ -116,6 +160,12 @@ fn LogT(comptime Out: type) type { buffer.appendAssumeCapacity('='); try writeValue(allocator, buffer, @field(data, f.name)); } + + if (comptime enhanced_readability) { + // reset any color + try buffer.appendSlice(allocator, "\x1b[0m"); + } + try buffer.append(allocator, '\n'); mutex.lock(); @@ -229,8 +279,24 @@ fn getTime() i64 { return std.time.milliTimestamp(); } +var last_log_for_debug: i64 = 0; +fn msSinceLastLog() i64 { + if (comptime builtin.mode != .Debug) { + @compileError("Log's enhanced_readability is not safe to use in non-Debug mode"); + } + const now = getTime(); + + mutex.lock(); + defer mutex.unlock(); + defer last_log_for_debug = now; + if (last_log_for_debug == 0) { + return 0; + } + return now - last_log_for_debug; +} + const testing = std.testing; -const TestLogger = LogT(std.ArrayListUnmanaged(u8).Writer); +const TestLogger = LogT(std.ArrayListUnmanaged(u8).Writer, false); test "log: data" { var buf: std.ArrayListUnmanaged(u8) = .{}; @@ -241,7 +307,7 @@ test "log: data" { { log.err("nope", .{}); - try testing.expectEqualStrings("_time=1739795092929 _level=err _ctx=nope\n", buf.items); + try testing.expectEqualStrings("_time=1739795092929 _level=error _msg=nope\n", buf.items); } { @@ -249,7 +315,7 @@ test "log: data" { const string = try testing.allocator.dupe(u8, "spice_must_flow"); defer testing.allocator.free(string); - log.warn("a_ctx", .{ + log.warn("a msg", .{ .cint = 5, .cfloat = 3.43, .int = @as(i16, -49), @@ -265,7 +331,7 @@ test "log: data" { }); try testing.expectEqualStrings( - "_time=1739795092929 _level=warn _ctx=a_ctx " ++ + "_time=1739795092929 _level=warn _msg=\"a msg\" " ++ "cint=5 cfloat=3.43 int=-49 float=0.0003232 bt=true bf=false " ++ "nn=33 n=null lit=over9000! slice=spice_must_flow " ++ "err=Nope level=warn\n" @@ -280,7 +346,7 @@ test "log: string escape" { var log = try TestLogger.initTo(testing.allocator, buf.writer(testing.allocator)); defer log.deinit(); - const prefix = "_time=1739795092929 _level=err _ctx=test "; + const prefix = "_time=1739795092929 _level=error _msg=test "; { log.err("test", .{.string = "hello world"}); try testing.expectEqualStrings(prefix ++ "string=\"hello world\"\n", buf.items); @@ -308,5 +374,5 @@ test "log: with inject" { log.inject = "conn_id=339494"; log.fatal("hit", .{.over = 9000}); - try testing.expectEqualStrings("_time=1739795092929 _level=fatal _ctx=hit conn_id=339494 over=9000\n", buf.items); + try testing.expectEqualStrings("_time=1739795092929 _level=fatal _msg=hit conn_id=339494 over=9000\n", buf.items); } diff --git a/src/main.zig b/src/main.zig index 344e707f9..8d3e11722 100644 --- a/src/main.zig +++ b/src/main.zig @@ -32,7 +32,7 @@ pub const UserContext = apiweb.UserContext; pub const IO = @import("asyncio").Wrapper(jsruntime.Loop); const Log = @import("log.zig").Log; -pub const LogLevel = Log.Level.debug; +pub const LogLevel = Log.Level.info; pub const std_options = .{ // Set the log level to info @@ -131,31 +131,31 @@ const CliMode = union(CliModeTag) { if (std.mem.eql(u8, "--port", opt)) { if (args.next()) |arg| { _server.port = std.fmt.parseInt(u16, arg, 10) catch |err| { - log.err("--port {any}\n", .{err}); + log.err("--port {any}", .{.err = err}); return printUsageExit(execname, 1); }; continue; } else { - log.err("--port not provided\n", .{}); + log.err("--port not provided", .{}); return printUsageExit(execname, 1); } } if (std.mem.eql(u8, "--timeout", opt)) { if (args.next()) |arg| { _server.timeout = std.fmt.parseInt(u8, arg, 10) catch |err| { - log.err("--timeout {any}\n", .{err}); + log.err("--timeout {any}", .{err}); return printUsageExit(execname, 1); }; continue; } else { - log.err("--timeout not provided\n", .{}); + log.err("--timeout not provided", .{}); return printUsageExit(execname, 1); } } // unknown option if (std.mem.startsWith(u8, opt, "--")) { - log.err("unknown option\n", .{}); + log.err("unknown option", .{}); return printUsageExit(execname, 1); } @@ -164,7 +164,7 @@ const CliMode = union(CliModeTag) { // allow only one url if (_fetch.url.len != 0) { - log.err("more than 1 url provided\n", .{}); + log.err("more than 1 url provided", .{}); return printUsageExit(execname, 1); } @@ -235,7 +235,7 @@ pub fn main() !void { switch (cli_mode) { .server => |opts| { const address = std.net.Address.parseIp4(opts.host, opts.port) catch |err| { - log.fatal("main_opts_address", .{ + log.fatal("invalid address", .{ .host = opts.host, .port = opts.port, .err = err, @@ -248,13 +248,13 @@ pub fn main() !void { const timeout = std.time.ns_per_s * @as(u64, opts.timeout); server.run(alloc, address, timeout, &loop) catch |err| { - log.fatal("main_opts_server", .{.err = err}); + log.fatal("server error", .{.err = err}); return err; }; }, .fetch => |opts| { - log.debug("main_fetch", .{ + log.debug("fetch mode", .{ .url = opts.url, .dump = opts.dump, }); @@ -279,14 +279,14 @@ pub fn main() !void { _ = page.navigate(opts.url, null) catch |err| switch (err) { error.UnsupportedUriScheme, error.UriMissingHost => { - log.fatal("main_fetch_invalid_url", .{ + log.fatal("fetch has invalid url", .{ .url = opts.url, .err = err, }); return printUsageExit(opts.execname, 1); }, else => { - log.fatal("main_fetch_error", .{ + log.fatal("fetch error", .{ .url = opts.url, .err = err, });