206 lines
6.2 KiB
Plaintext
206 lines
6.2 KiB
Plaintext
// 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();
|
||
}
|