mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Merge pull request #571 from lightpanda-io/remove-log.zig
Remove log.zig
This commit is contained in:
377
src/log.zig
377
src/log.zig
@@ -1,377 +0,0 @@
|
|||||||
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: {
|
|
||||||
const root = @import("root");
|
|
||||||
break :blk if (@hasDecl(root, "LogLevel")) root.LogLevel else .info;
|
|
||||||
};
|
|
||||||
|
|
||||||
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, comptime enhanced_readability: bool) type {
|
|
||||||
return struct {
|
|
||||||
out: Out,
|
|
||||||
inject: ?[]const u8,
|
|
||||||
allocator: Allocator,
|
|
||||||
buffer: std.ArrayListUnmanaged(u8),
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub const Level = enum {
|
|
||||||
debug,
|
|
||||||
info,
|
|
||||||
warn,
|
|
||||||
@"error",
|
|
||||||
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 msg: []const u8, data: anytype) void {
|
|
||||||
self.log(.debug, msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn info(self: *Self, comptime msg: []const u8, data: anytype) void {
|
|
||||||
self.log(.info, msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn warn(self: *Self, comptime msg: []const u8, data: anytype) void {
|
|
||||||
self.log(.warn, msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn err(self: *Self, comptime msg: []const u8, data: anytype) void {
|
|
||||||
self.log(.@"error", msg, 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 msg: []const u8, data: anytype) void {
|
|
||||||
if (comptime enabled(level) == false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
defer self.buffer.clearRetainingCapacity();
|
|
||||||
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 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 + msg + inject
|
|
||||||
// to fit in the initial buffer;
|
|
||||||
var buffer = &self.buffer;
|
|
||||||
|
|
||||||
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 ++ "'"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std.debug.assert(buffer.capacity >= 1024);
|
|
||||||
|
|
||||||
if (comptime enhanced_readability) {
|
|
||||||
// used when developing, and we prefer readability over having
|
|
||||||
// the output in logfmt
|
|
||||||
|
|
||||||
// write the level this way so that the column lines up.
|
|
||||||
switch (level) {
|
|
||||||
.info => buffer.appendSliceAssumeCapacity("info | "),
|
|
||||||
.debug => buffer.appendSliceAssumeCapacity("debug | "),
|
|
||||||
.warn => buffer.appendSliceAssumeCapacity("\x1b[33m warn\x1b[0m | "),
|
|
||||||
.@"error" => buffer.appendSliceAssumeCapacity("\x1b[31m error\x1b[0m | "),
|
|
||||||
.fatal => buffer.appendSliceAssumeCapacity("\x1b[41m fatal\x1b[0m | "),
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.appendSliceAssumeCapacity(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(' ');
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comptime enhanced_readability) {
|
|
||||||
// reset any color
|
|
||||||
try buffer.appendSlice(allocator, "\x1b[0m");
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
},
|
|
||||||
.comptime_int, .int, .comptime_float, .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");
|
|
||||||
},
|
|
||||||
.error_set => 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, "\"<binary data> (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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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, false);
|
|
||||||
|
|
||||||
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=error _msg=nope\n", buf.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
buf.clearRetainingCapacity();
|
|
||||||
const string = try testing.allocator.dupe(u8, "spice_must_flow");
|
|
||||||
defer testing.allocator.free(string);
|
|
||||||
|
|
||||||
log.warn("a msg", .{
|
|
||||||
.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 _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", 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=error _msg=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 _msg=hit conn_id=339494 over=9000\n", buf.items);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user