工作机制
每条日志记录都带有有序的严重级别,常见为 trace、debug、info、warn、error、fatal。日志器的阈值会屏蔽低于配置级别的记录,管道还可以按级别分流:error 触发值班告警,warn 进入仪表盘,debug 写入短保留期存储。级别是结构化元数据,因此仍可被检索和过滤。
适用场景与取舍
所有生产服务都需要级别约定,尤其是多种语言和库共用一条管道时。生产默认使用 info,排查时才开启 debug,error 只留给需要人工处理的情况。与无结构的普通消息相比,级别支持自动路由和成本控制。代价是级别很粗:可预期的校验失败和数据库中断都可能是 error;团队抬高所有级别会导致告警疲劳,压低警告又会丢失早期信号。
具体示例
一个 Node.js 服务把银行卡授权失败连同拒付码记为 warn,把超时记为 error,把请求体记为 debug。生产级别设为 info,因此请求体不会落盘;支持人员排查某个租户时,通过限定范围的开关临时开启 debug。
常见误解与关联概念
error 日志并不自动值得呼叫值班,严重级别不等于业务影响;日志越多也不越安全,数量会淹没信号。不同库的级别顺序并不统一,syslog 与常见日志库就有差异,因此管道应先归一化再路由。遥测管道负责按级别分流,采样控制各级别数据量,黄金信号决定什么才值得告警。
代码示例
package main
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
logger.Info("request handled",
slog.String("method", "GET"),
slog.String("path", "/users/42"),
slog.Int("status", 200),
slog.Duration("latency", 0),
)
logger.Error("dependency failed",
slog.String("dependency", "postgres"),
slog.Int("attempt", 3),
)
}
const LEVELS = Object.freeze({ debug: 10, info: 20, warn: 30, error: 40 });
function serializeError(error, seen) {
if (seen.has(error)) return "[Circular]";
seen.add(error);
try {
const output = { name: error.name, message: error.message };
if (typeof error.stack === "string") output.stack = error.stack;
if (error.cause !== undefined) {
output.cause =
error.cause instanceof Error
? serializeError(error.cause, seen)
: toSerializable(error.cause, seen);
}
return output;
} finally {
seen.delete(error);
}
}
function toSerializable(value, seen = new WeakSet()) {
if (typeof value === "bigint") return value.toString();
if (value instanceof Error) return serializeError(value, seen);
if (value === null || typeof value !== "object") return value;
if (seen.has(value)) return "[Circular]";
seen.add(value);
try {
if (Array.isArray(value)) {
return value.map((item) => toSerializable(item, seen));
}
if (value instanceof Date) return value.toISOString();
if (value instanceof Map) {
return [...value.entries()].map(([key, item]) => [
toSerializable(key, seen),
toSerializable(item, seen),
]);
}
if (value instanceof Set) {
return [...value].map((item) => toSerializable(item, seen));
}
const output = Object.create(null);
for (const [key, item] of Object.entries(value)) {
if (key === "__proto__") continue;
output[key] = toSerializable(item, seen);
}
return output;
} finally {
seen.delete(value);
}
}
function safeStringify(value) {
return JSON.stringify(toSerializable(value));
}
export function createLogger(options = {}) {
const {
level = "info",
base = {},
write = (line) => process.stdout.write(`${line}\n`),
now = () => new Date().toISOString(),
} = options;
if (!(level in LEVELS)) {
throw new RangeError(`unknown log level: ${level}`);
}
if (base === null || typeof base !== "object" || Array.isArray(base)) {
throw new TypeError("base must be a plain object");
}
if (typeof write !== "function" || typeof now !== "function") {
throw new TypeError("write and now must be functions");
}
const emit = (entryLevel, message, fields = {}) => {
if (LEVELS[entryLevel] < LEVELS[level]) return;
if (fields === null || typeof fields !== "object" || Array.isArray(fields)) {
throw new TypeError("fields must be a plain object");
}
let line;
try {
const entry = {
...base,
...fields,
timestamp: now(),
level: entryLevel,
message: String(message),
};
line = safeStringify(entry);
} catch (error) {
line = JSON.stringify({
timestamp: now(),
level: "error",
message: "failed to serialize log entry",
error: error instanceof Error ? error.message : String(error),
});
}
write(line);
};
return Object.freeze({
level,
log: emit,
debug: (message, fields) => emit("debug", message, fields),
info: (message, fields) => emit("info", message, fields),
warn: (message, fields) => emit("warn", message, fields),
error: (message, fields) => emit("error", message, fields),
child(bindings) {
if (
bindings === null ||
typeof bindings !== "object" ||
Array.isArray(bindings)
) {
throw new TypeError("child bindings must be a plain object");
}
return createLogger({
level,
base: { ...base, ...bindings },
write,
now,
});
},
});
}
const logger = createLogger({
level: "info",
base: { service: "billing", env: "test" },
});
const requestLogger = logger.child({ requestId: "req-42" });
requestLogger.info("request received", { method: "GET", path: "/invoices" });
requestLogger.warn("slow upstream", { durationMs: 812, attempt: 2 });
requestLogger.error("upstream failed", {
error: new Error("socket hang up"),
meta: { retryable: true },
});
logger.debug("filtered out");
const circular = { name: "node" };
circular.self = circular;
requestLogger.info("circular payload", { circular, big: 9007199254740993n });