Something went wrong. Try again.
jetstream v2 in zig stream.waow.tech
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144const std = @import("std");
const Io = std.Io;
pub const Format = enum(u8) { text, json,};
var configured_level: std.atomic.Value(u8) = .init(@backingInt(std.log.Level.info));var configured_format: std.atomic.Value(u8) = .init(@backingInt(Format.json));
pub fn configure(level_raw: []const u8, format_raw: []const u8) !void { const level = try parseLevel(level_raw); const format = try parseFormat(format_raw); configured_level.store(@backingInt(level), .release); configured_format.store(@backingInt(format), .release);}
pub fn parseLevel(raw: []const u8) !std.log.Level { const value = std.mem.trim(u8, raw, " \t\r\n"); if (value.len == 0 or std.ascii.eqlIgnoreCase(value, "i") or std.ascii.eqlIgnoreCase(value, "info")) return .info; if (std.ascii.eqlIgnoreCase(value, "d") or std.ascii.eqlIgnoreCase(value, "dbg") or std.ascii.eqlIgnoreCase(value, "debug")) return .debug; if (std.ascii.eqlIgnoreCase(value, "w") or std.ascii.eqlIgnoreCase(value, "warn") or std.ascii.eqlIgnoreCase(value, "warning")) return .warn; if (std.ascii.eqlIgnoreCase(value, "e") or std.ascii.eqlIgnoreCase(value, "err") or std.ascii.eqlIgnoreCase(value, "error")) return .err; return error.InvalidLogLevel;}
pub fn parseFormat(raw: []const u8) !Format { const value = std.mem.trim(u8, raw, " \t\r\n"); if (value.len == 0 or std.ascii.eqlIgnoreCase(value, "json")) return .json; if (std.ascii.eqlIgnoreCase(value, "text")) return .text; return error.InvalidLogFormat;}
pub fn logFn( comptime level: std.log.Level, comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype,) void { if (@backingInt(level) > configured_level.load(.acquire)) return;
var message: Io.Writer.Allocating = .init(std.heap.page_allocator); defer message.deinit(); message.writer.print(format, args) catch { std.log.defaultLog(level, scope, format, args); return; };
const io = std.Options.debug_io; const previous = io.swapCancelProtection(.blocked); defer _ = io.swapCancelProtection(previous); var buffer: [1024]u8 = undefined; const terminal = std.debug.lockStderr(&buffer).terminal(); defer std.debug.unlockStderr();
const selected: Format = @fromBackingInt(@intCast(configured_format.load(.acquire))); writeRecord( terminal.writer, selected, level, @tagName(scope), message.written(), Io.Timestamp.now(io, .real).toMicroseconds(), ) catch {};}
fn writeRecord( writer: *Io.Writer, format: Format, level: std.log.Level, scope: []const u8, message: []const u8, now_us: i64,) !void { var time_buffer: [32]u8 = undefined; const timestamp = formatRfc3339(&time_buffer, now_us); const component = if (std.mem.eql(u8, scope, "default")) "main" else scope; switch (format) { .json => { var json: std.json.Stringify = .{ .writer = writer }; try json.beginObject(); try json.objectField("time"); try json.write(timestamp); try json.objectField("level"); try json.write(levelName(level)); try json.objectField("msg"); try json.write(message); try json.objectField("component"); try json.write(component); try json.endObject(); try writer.writeByte('\n'); }, .text => { try writer.print("time={s} level={s} msg=", .{ timestamp, levelName(level) }); var json: std.json.Stringify = .{ .writer = writer }; try json.write(message); try writer.print(" component={s}\n", .{component}); }, } try writer.flush();}
fn levelName(level: std.log.Level) []const u8 { return switch (level) { .debug => "DEBUG", .info => "INFO", .warn => "WARN", .err => "ERROR", };}
fn formatRfc3339(buffer: []u8, micros: i64) []const u8 { return @import("zat").Datetime.fromMicros(micros).toString(buffer);}
test "canonical log level and format parsing" { try std.testing.expectEqual(std.log.Level.info, try parseLevel("")); try std.testing.expectEqual(std.log.Level.debug, try parseLevel("DBG")); try std.testing.expectEqual(std.log.Level.warn, try parseLevel(" warning ")); try std.testing.expectEqual(std.log.Level.err, try parseLevel("ERROR")); try std.testing.expectError(error.InvalidLogLevel, parseLevel("loud")); try std.testing.expectEqual(Format.json, try parseFormat("")); try std.testing.expectEqual(Format.json, try parseFormat("JSON")); try std.testing.expectEqual(Format.text, try parseFormat(" text ")); try std.testing.expectError(error.InvalidLogFormat, parseFormat("xml"));}
test "JSON and text log records are structured and escaped" { var output: Io.Writer.Allocating = .init(std.testing.allocator); defer output.deinit(); try writeRecord(&output.writer, .json, .info, "stream", "hello \"river\"", 1_720_000_000_123_456); const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, output.written(), .{}); defer parsed.deinit(); try std.testing.expectEqualStrings("INFO", parsed.value.object.get("level").?.string); try std.testing.expectEqualStrings("hello \"river\"", parsed.value.object.get("msg").?.string); try std.testing.expectEqualStrings("stream", parsed.value.object.get("component").?.string);
output.clearRetainingCapacity(); try writeRecord(&output.writer, .text, .warn, "stream", "slow peer", 1_720_000_000_123_456); try std.testing.expect(std.mem.indexOf(u8, output.written(), "level=WARN msg=\"slow peer\" component=stream\n") != null);}