// Expect retcode = 0 // 编译器自身日志(区别于 diag:diag 负责带源码位置的诊断)。 // 输出到 stderr,格式稳定:[LEVEL] name: message\n。 // 消息为借用;Logger 不拥有 name。fatal 打印后终止进程(退出码 1)。 // write 返回 Status;OOM 归类为 .Overflow(无独立内存耗尽错误码)。 type .{ .Status, .make } = @import("error.spl"); type .{ .ByteView } = @import("byte_view.spl"); type .{ .ByteBuffer } = @import("byte_buffer.spl"); type .{ .format } = @import("fmt.spl"); type .{ .Value, .int, .str } = @import("value.spl"); type .{ .eprint } = @import("io.spl"); type Exit = @import("../sys/exit.spl"); type Level = enum { Debug, Info, Warn, Error, Fatal } fn level_name(level: Level): []u8 { match level { .Debug => { ret "DEBUG"; }, .Info => { ret "INFO "; }, .Warn => { ret "WARN "; }, .Error => { ret "ERROR"; }, .Fatal => { ret "FATAL"; }, } ret "NOTSET"; } type Logger = @self; name: ByteView, min_level: Level, fn new(name: ByteView): Logger { ret Logger { .name = name, .min_level = .Debug }; } fn set_level(self: *Logger, level: Level) { self.min_level = level; } fn enabled(self: Logger, level: Level): bool { ret @enum2int(level) >= @enum2int(self.min_level); } // 追加成功返回 true;失败(仅 OOM)返回 false。 fn push(line: *ByteBuffer, text: []u8): bool { match ByteBuffer.append(line, text) { .Ok => { ret true; }, .Err(error) => { ret false; }, } ret false; } // 低于 min_level 的日志不写出,直接成功。 fn write(self: *Logger, level: Level, message: []u8): Status { if !enabled(self.*, level) { ret .Ok; } var line = ByteBuffer.new(); defer { ByteBuffer.drop(&line); } if !push(&line, "[") { ret .Err(make(.Overflow)); } if !push(&line, level_name(level)) { ret .Err(make(.Overflow)); } if !push(&line, "] ") { ret .Err(make(.Overflow)); } if !push(&line, ByteView.as_slice(self.name)) { ret .Err(make(.Overflow)); } if !push(&line, ": ") { ret .Err(make(.Overflow)); } if !push(&line, message) { ret .Err(make(.Overflow)); } if !push(&line, "\n") { ret .Err(make(.Overflow)); } ret eprint(ByteBuffer.as_slice(line)); } fn debug(self: *Logger, message: []u8): Status { ret write(self, .Debug, message); } fn info(self: *Logger, message: []u8): Status { ret write(self, .Info, message); } fn warn(self: *Logger, message: []u8): Status { ret write(self, .Warn, message); } fn error(self: *Logger, message: []u8): Status { ret write(self, .Error, message); } // 尽力写出后终止;写失败无法挽回,直接退出。 fn fatal(self: *Logger, message: []u8) { write(self, .Fatal, message); Exit.fail(); } // ---- 格式化入口(显式装箱,见 value.spl / fmt.format)---- // 先格式化到临时缓冲;格式错误在写出前返回 Err(.InvalidFormat)。 fn logf(self: *Logger, level: Level, fmt: []u8, args: []Value): Status { if !enabled(self.*, level) { ret .Ok; } var message = ByteBuffer.new(); defer { ByteBuffer.drop(&message); } match format(&message, fmt, args) { .Ok => {}, .Err(error) => { ret .Err(error); }, } ret write(self, level, ByteBuffer.as_slice(message)); } fn debugf(self: *Logger, fmt: []u8, args: []Value): Status { ret logf(self, .Debug, fmt, args); } fn infof(self: *Logger, fmt: []u8, args: []Value): Status { ret logf(self, .Info, fmt, args); } fn warnf(self: *Logger, fmt: []u8, args: []Value): Status { ret logf(self, .Warn, fmt, args); } fn errorf(self: *Logger, fmt: []u8, args: []Value): Status { ret logf(self, .Error, fmt, args); } // 格式化后终止;格式化失败也要退出(避免吞掉致命错误)。 fn fatalf(self: *Logger, fmt: []u8, args: []Value) { logf(self, .Fatal, fmt, args); Exit.fail(); } // ---- 测试 ---- fn expect_name(level: Level, expected: []u8) { @assert(ByteView.eq(ByteView.from_slice(level_name(level)), ByteView.from_slice(expected)), "level name"); } fn expect_ok(result: Status) { match result { .Ok => {}, .Err(error) => { @assert(false, "unexpected log error"); }, } } fn test_level_names() { expect_name(.Debug, "DEBUG"); expect_name(.Info, "INFO "); expect_name(.Warn, "WARN "); expect_name(.Error, "ERROR"); expect_name(.Fatal, "FATAL"); var debug: Level = .Debug; var info: Level = .Info; var warn: Level = .Warn; var err: Level = .Error; var fatal: Level = .Fatal; @assert(@enum2int(fatal) > @enum2int(err), "fatal above error"); @assert(@enum2int(err) > @enum2int(warn), "error above warn"); @assert(@enum2int(warn) > @enum2int(info), "warn above info"); @assert(@enum2int(info) > @enum2int(debug), "info above debug"); } fn test_filter_and_write() { var log = new(ByteView.from_slice("splc1")); @assert(enabled(log, .Debug), "default shows debug"); @assert(enabled(log, .Fatal), "default shows fatal"); set_level(&log, .Warn); @assert(!enabled(log, .Debug), "filtered debug"); @assert(!enabled(log, .Info), "filtered info"); @assert(enabled(log, .Warn), "warn passes"); @assert(enabled(log, .Error), "error passes"); // 被过滤的写入不产生输出,且返回成功。 expect_ok(debug(&log, "hidden")); expect_ok(write(&log, .Info, "hidden too")); expect_ok(error(&log, "visible")); } fn test_logf() { var log = new(ByteView.from_slice("splc1")); set_level(&log, .Error); var args: [2]Value = .[int(3), str("x")]; // 被过滤的级别不写出,直接成功。 expect_ok(infof(&log, "n={} s={}", args[0..2])); // 通过级别正常写出。 expect_ok(errorf(&log, "n={} s={}", args[0..2])); // 格式化失败向上传播,且不产出消息。 match errorf(&log, "{} {}", args[0..1]) { .Ok => { @assert(false, "expected invalid format"); }, .Err(error) => { match error.kind { .InvalidFormat => {}, _ => { @assert(false, "expected InvalidFormat"); }, } }, } } fn main() { test_level_names(); test_filter_and_write(); test_logf(); }