管道
管道通过三个阶段将 LogEntry 转换为格式化后的字符串:过滤器(丢弃)、处理器(转换)与 格式化器(序列化)。
Filter
type Filter = (entry: LogEntry) => boolean;
function createLevelFilter(minLevel: number): Filter;
function createScopeFilter(allowed: string[]): Filter;
function createSampler(options?: SamplingOptions): Filter;
function combineFilters(filters: Filter[]): Filter;采样(Sampling)
createSampler 通过丢弃条目来降低日志量,提供两种正交策略(按逻辑"与"组合):
- 概率采样(
rate,0~1):以rate的概率保留每条条目; - 限速(
maxPerSecond+burst):令牌桶限制每秒保留条数,允许短时突发。
不在 levels 内(或省略 levels 时对所有级别)的条目始终保留。由于它是过滤器,被采样的条目不会进入处理器、格式化器或传输器——这是高负载下降低成本最省力的方式。采样是有损的,请仅用于高量、低价值级别。
logger.getPipeline().addFilter(
createSampler({ levels: ['debug', 'info'], maxPerSecond: 100, burst: 200 }),
);Processor
type Processor = (entry: LogEntry) => LogEntry;
const identityProcessor: Processor;
function createRedactProcessor(keys?: string[], replacement?: unknown): Processor;
const DEFAULT_SENSITIVE_KEYS: string[];
function createDefaultSerializers(): Record<string, Serializer>;
function createSerializeProcessor(serializers: Record<string, Serializer>): Processor;createRedactProcessor 在格式化之前对敏感数据脱敏——它会遍历 context、metadata 以及 args 的每个元素,将路径匹配 keys 中任一项的值替换为 replacement(默认 "[REDACTED]",可为任意值)。
- 裸键名(如
'password')匹配任意深度下该名称的属性。 - 点路径(如
'user.password')只匹配确切路径。 *匹配任意单个键/下标:'*.password'脱敏所有password,'user.*'脱敏user下的全部直接子字段。
原始对象永不会被修改;仅真正包含匹配项的分支会被克隆,因此无匹配时条目会被原样返回。
createRedactProcessor 不传 keys 时,会对一组内置的常见机密 / PII 键脱敏——即 DEFAULT_SENSITIVE_KEYS(密码、token、API key、authorization、cookie、私钥、sessionId、csrf、otp、ssn 等)。传入显式列表会覆盖默认值。
序列化器(Serializers)
type Serializer = (value: unknown, entry: LogEntry) => unknown;createSerializeProcessor 在格式化之前按属性名对值做归一化。只要在 context、metadata、args 的每个元素(以及条目的 error)中找到名为 key 的属性,就会用对应序列化器替换其值。它相当于 pino 的 serializers——可用它脱敏敏感字段、裁剪大对象,或统一渲染框架对象(请求、数据库行等)。
- 按属性名在任意深度匹配——任何带有
req属性的对象都会触发req序列化器。 - 第二个参数会传入
entry,便于做带上下文的序列化。 - 转换是结构性的:仅包含匹配键的分支会被克隆;无匹配时条目原样返回。
createDefaultSerializers() 返回一组开箱即用的 Serializer,按所匹配的属性名命名(error、date、buffer、url、req、res)——例如错误变为 {name,message,stack},Date → ISO 字符串,Buffer → Buffer(n),URL → href,req/res 裁剪为 method/url/headers(或 status/headers)。可将其展开进你自己的映射:
logger.getPipeline().addProcessor(
createSerializeProcessor({ ...createDefaultSerializers(), user: (u) => ({ id: u.id }) }),
);import { createSerializeProcessor, createRedactProcessor } from 'lograil';
logger.getPipeline().addProcessor(
createSerializeProcessor({
err: (e) => ({ name: e.name, message: e.message, stack: e.stack }),
user: (u) => ({ id: u.id }), // 只保留 id
}),
);建议把序列化器放在 createRedactProcessor 之前,以便脱敏能进一步掩盖已归一化的输出。
真实场景
一个 Web 服务要为每次请求记录日志。原始的 req / user 对象既庞大又容易泄密(headers、cookies、socket、密码哈希等)——用序列化器只保留需要的字段,再配合脱敏作为兜底:
const log = createLogger({
pipeline: {
processors: [
createSerializeProcessor({
// 只保留安全字段,丢弃 headers/cookies/socket
req: (r: any) => ({ method: r.method, url: r.url, ip: r.ip }),
// user 含 passwordHash,只暴露 id 与角色
user: (u: any) => ({ id: u.id, role: u.role }),
// 把错误展开成可读结构
err: (e: any) => ({ name: e.name, message: e.message, stack: e.stack }),
}),
// 脱敏任何漏网的敏感字段
createRedactProcessor(['token', 'authorization', 'cookie']),
],
},
});
// 之后,在请求处理函数内:
log.info('request handled', { req, user, err });输出的 JSON 行既实用又不会泄露机密:
{
"level": "info",
"message": "request handled",
"args": [
{
"req": { "method": "GET", "url": "/api/me", "ip": "203.0.113.7" },
"user": { "id": 42, "role": "admin" }
}
]
}Formatter
type Formatter<T = string> = (entry: LogEntry) => T;
function createLineFormatter(): Formatter<string>;
function createJsonFormatter(options?: { flatten?: boolean }): Formatter<string>;createLineFormatter—— 一行可读文本,包含完整的Error因果链。createJsonFormatter—— 结构化 JSON,其中error通过errorToJson序列化(对循环cause安全)。传入{ flatten: true }时,context与metadata会被拍平到对象顶层(而非嵌套),便于 Loki、Elasticsearch 这类期望扁平字段的后端。
Pipeline API
class Pipeline {
constructor(options?: PipelineOptions);
process(entry: LogEntry): LogEntry | null; // 运行过滤器 + 处理器
addFilter(filter: Filter): void;
removeFilter(filter: Filter): void;
addProcessor(processor: Processor): void;
removeProcessor(processor: Processor): void;
setFormatter(formatter: Formatter): void;
getFormatter(): Formatter;
}PipelineOptions 可通过 createLogger({ pipeline }) 传入,以声明式方式配置管道:
interface PipelineOptions {
filters?: Filter[];
processors?: Processor[];
formatter?: Formatter;
}在运行时访问并修改活动的管道:
import { createRedactProcessor } from 'lograil';
logger.getPipeline().addProcessor(createRedactProcessor(['password']));