Files

204 lines
6.1 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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();
}
// 先格式化到临时缓冲;格式错误在写出前返回 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();
}