传输器
Transport 接口
interface Transport {
readonly name: string;
readonly formatter?: Formatter;
readonly level?: LogLevelInput; // 可选的传输器最小级别
write(entry: LogEntry, formatted: string): void | Promise<void>;
onError?(err: unknown, entry: LogEntry): void; // write 失败时由 logger 调用
flush?(): void | Promise<void>;
close?(): void | Promise<void>;
/**
* 异步写入队列的最大深度。超出时最新条目被丢弃,并调用 `onOverflow`。
* `0` 表示使用全局 {@link LoggerOptions.maxQueueDepth}。默认 `0`。
*/
queueLimit?: number;
/** 当该传输器队列已满、条目被丢弃时回调。 */
onOverflow?(entry: LogEntry, queueDepth: number): void;
}level 会在 logger 级别过滤之外,单独限制该传输器:低于它的日志仅会被此 sink 跳过。可用它拆分日志流——例如把 error 及以上发往远端,其余写入文件。
onError 不仅在 write() 失败时被调用,FileTransport 还会通过它上报内部 文件系统错误,例如轮转失败或备份删除失败。这些错误不会中断传输器运行—— 你可以在 onError 中记录日志、上报指标或发出告警。
ConsoleTransport
interface ConsoleTransportOptions {
name?: string;
formatter?: Formatter; // 默认 createLineFormatter()
methodMap?: Partial<Record<string, (...args: unknown[]) => void>>;
/** 路由到 `console.error`(stderr)的级别。默认为空——内置 `methodMap` 已把
* `error`/`fatal` 发往 stderr;可在此追加 `'warn'` 等。 */
stderrLevels?: LogLevelName[];
}
class ConsoleTransport implements Transport;将每个级别映射到某个 console 方法(可通过 methodMap 覆盖)。列在 stderrLevels 中的级别会被额外路由到 console.error(stderr)——便于把 warning/error 引到单独的流。模块加载时会用 typeof console 保护捕获真实的 console 方法,因此在不提供全局 console 的精简运行时中导入 lograil 也不会抛错。
FileTransport
interface FileBaseOptions {
appName: string; // 必填;日志文件名始终包含它
dir?: string; // 默认 os.tmpdir()
formatter?: Formatter; // 默认 createLineFormatter()
filter?: (entry: LogEntry) => boolean; // 返回 false 时丢弃该条目
name?: string; // 传输器名,用于诊断与 removeTransport;默认 `file:<appName>`(不参与文件名)
/** 所有文件(活动 + 历史)的总体积上限。默认 Infinity。 */
maxTotalSize?: number;
/**
* 文件最大存活毫秒数。
* - `undefined` 或 `-1`(默认):不限制。
* - `0`:立即删除所有历史文件。
* - `>0`:毫秒数阈值。
*/
maxAge?: number;
}
// 1.1 — 单文件,一直追加到磁盘满
interface SingleFileOptions extends FileBaseOptions {
mode: 'single';
ext?: string; // 默认 'log'
}
// 1.2 — 单文件;超出 maxSize 时备份后原地截断
interface SingleTruncateOptions extends FileBaseOptions {
mode: 'single-truncate';
maxSize: number; // 必填
backupName?: string; // 备份文件名;默认 `${appName}.bak`
ext?: string;
}
// 2.1 — 按体积轮转;文件名是代际序号的函数
interface RotateSizeOptions extends FileBaseOptions {
mode: 'rotate-size';
maxSize: number; // 必填
maxFiles: number; // 必填;保留多少代(1=仅活动文件,<=0=不限制)
ext?: string;
}
// 2.2 — 按时间轮转;文件名是时间戳的函数
interface RotateTimeOptions extends FileBaseOptions {
mode: 'rotate-time';
unit: 'hour' | 'day'; // 必填
maxFiles?: number; // 可选:时间桶(stamp)上限(1=仅最新桶,<=0/未设=不限制)
maxSize?: number; // 可选:同一时间桶内超过此大小时自动拆分
maxFilesPerBucket?: number; // 可选:单个时间桶内最多保留多少个 seq 文件(桶内成环)
now?: () => Date; // 时钟覆盖(用于测试)
ext?: string;
}
// 2.3 — 自定义何时轮转;文件名是序号的函数
interface RotateCustomOptions extends FileBaseOptions {
mode: 'rotate-custom';
shouldRotate: (entry: LogEntry, ctx: RotateContext) => boolean; // 必填
fileName: (app: string, seq: number, ext: string) => string; // 必填
maxFiles?: number; // 可选:1=仅活动文件,<=0/未设=不限制
ext?: string;
}
type FileTransportOptions =
| SingleFileOptions
| SingleTruncateOptions
| RotateSizeOptions
| RotateTimeOptions
| RotateCustomOptions;
class FileTransport implements Transport;FileTransport 取代了旧的 RotatingFileTransport。appName 必填,且始终是文件名的一部分,因此日志文件可以按所属应用识别。可选的 name 与之无关——它只给传输器实例打标签,用于诊断和 removeTransport(),默认 file:<appName>,不会出现在文件名里。模式是一个判别联合——选一种模式,就只有该模式的字段是必填的,绝不会忘记所选模式需要的参数。
查看和获取日志文件
interface FileMeta {
name: string; // 文件名,如 myapp.2026-09-01.0.log
path: string; // 绝对路径
size: number; // 字节数
mtime: number; // 最后修改时间戳(毫秒)
active: boolean; // 是否当前正在写入的文件
}
interface GetFilesOptions {
/** 只返回本次实例创建的文件(排除旧文件),默认 false */
currentSessionOnly?: boolean;
}
class FileTransport {
/** 日志目录的绝对路径 */
getDir(): string;
/** 当前正在写入的文件路径 */
getActiveFile(): string;
/** 列出所有日志文件(包括旧文件) */
getFiles(options?: GetFilesOptions): Promise<FileMeta[]>;
}getFiles() 默认返回该 transport 管理的所有文件,包括启动时从磁盘扫描到的历史文件。传入 { currentSessionOnly: true } 可以只返回本次 transport 实例创建的文件。
通过 instanceof FileTransport 可以从 logger.getTransports() 中找到 FileTransport 实例:
import { logger, FileTransport } from 'lograil';
const ft = logger.getTransports()
.find((t): t is FileTransport => t instanceof FileTransport);
if (ft) {
const files = await ft.getFiles();
// ...业务逻辑
}仅 Node / Electron 主进程可用。
FileTransport使用真实的node:fsAPI 写入。 在浏览器打包中,其 fs 函数被替换为调用即抛错的 stub——引入是安全的,但在浏览器中写文件会失败。 Web 端请使用控制台或远程传输器(或createWebRuntime())。
single— 单个dir/<appName>.<ext>文件,一直追加(直到磁盘写满)。single-truncate— 同样是单文件,但一旦即将超过maxSize,当前内容被改名为backupName,原文件原地截断(环形缓冲)。一个主文件加一个备份。rotate-size— 当size + bytes > maxSize时,按maxFiles推移代际(app.log→app.1.log→ …),归档名遵循默认的${app}.${seq}.${ext}。rotate-time— 在每个unit边界(hour/day)开启新文件,并以时间戳命名(${app}.${stamp}.${seq}.${ext})。设置maxSize后,同一时间桶内的文件会按体积拆分(如app.2026-08-31.0.log、app.2026-08-31.1.log)。maxFiles按时间桶计数,而非按文件——删除某个桶时,其下所有 seq 文件一并删除。maxFilesPerBucket限制单个桶内的 seq 文件数,超出时删除桶内最旧的(桶内成环,活动文件永不删除)。三者组合后磁盘占用上界约为maxFiles × maxFilesPerBucket × maxSize字节。rotate-custom—shouldRotate(entry, ctx)决定何时切分;fileName(app, seq, ext)为每个文件命名。对轮转拥有完全控制。
所有模式共用同一套 open/queue/mkdir/flush/close 机制,仅在"何时切换、下一个文件如何命名"上各模式不同。
ElectronIpcTransport
interface ElectronIpcTransportOptions {
channel?: string;
name?: string;
}
class ElectronIpcTransport implements Transport;渲染进程侧:通过 IPC 将每条条目转发到主进程。在 Electron 之外导入也是安全的。
传输器使用 ipcRenderer.send() 进行跨进程通信,Electron 内部会自动对消息做结构化克隆。 主进程侧用 registerIpcReceiver 接收并处理(同时标记为渲染进程来源)。 参见不可变性。
WorkerIpcTransport
interface WorkerIpcTransportOptions {
name?: string;
}
class WorkerIpcTransport implements Transport {
readonly name: string;
onError?: (err: unknown, entry: LogEntry) => void;
write(entry: LogEntry, _formatted: string): void;
/** 向主线程 / 父线程转发一条跨进程级别命令。 */
sendLevelCommand(level: number): void;
close(): void; // 空操作;Worker 生命周期由 Worker 实例自身管理
}
function registerWorkerReceiver(
ingest: (entry: LogEntry) => void,
options?: WorkerReceiverOptions & { onLevelCommand?: (level: number) => void },
): () => void;Worker 侧传输器:通过 self.postMessage() 把每条条目转发到主线程 / 父线程。 在非 Worker 环境导入是安全的——self.postMessage 不可用时 write 为空操作。 主线程侧用 registerWorkerReceiver 解码条目,并把级别命令转发给 onLevelCommand。传入显式 worker 实例且该实例使用单槽位 onmessage 属性时,原有的处理函数会被保存并由返回的取消注册函数恢复,避免被静默覆盖。 断路器(连续 10 次失败 → 5 秒窗口)会在主线程死亡时抑制逐条报错,同时每次 写入仍尝试投递,因此通道恢复后自动继续工作。
ClusterIpcTransport
interface ClusterIpcTransportOptions {
channel?: string; // IPC 通道名,默认 'lograil:cluster:log'
name?: string;
}
class ClusterIpcTransport implements Transport {
readonly name: string;
onError?: (err: unknown, entry: LogEntry) => void;
write(entry: LogEntry, _formatted: string): void;
/** 向主进程转发一条跨进程级别命令。 */
sendLevelCommand(level: number): void;
close(): void; // 空操作;cluster 模块管理 worker 生命周期
}
function registerClusterReceiver(
ingest: (entry: LogEntry) => void,
options?: ClusterReceiverOptions & { onLevelCommand?: (level: number) => void },
): () => void;Cluster worker 侧传输器:通过 process.send() 把每条条目转发到主进程。 在非 cluster worker 环境导入是安全的——process.send 不可用时 write 为 空操作。主进程侧用 registerClusterReceiver 解码条目,并把级别命令转发给 onLevelCommand。断路器(连续 5 次失败 → 5 秒窗口)会在主进程死亡时抑制 逐条报错。
OtlpTransport
interface OtlpTransportOptions {
endpoint?: string; // 默认 http://localhost:4318/v1/logs
headers?: Record<string, string>;
resource?: Record<string, unknown>;
serviceName?: string; // 默认 'lograil'
scopeName?: string; // 默认 'lograil'
batchSize?: number; // 默认 100
formatter?: Formatter; // 接口对齐用;OTLP 实际自行序列化条目
onError?: (err: unknown) => void;
/**
* 瞬态失败(网络错误或 5xx)后的最大重试次数。失败尝试在原地以指数退避重试。
* 最后一次尝试仍未能投递(或被 4xx 直接拒绝)的 batch 会递增 {@link dropCount};
* 其记录集合会被记住,供下一次 `flush()` / `close()` 再投递一次,之后永久丢弃——
* 因此一个始终失败的端点每个 batch 最多消耗两个 flush 周期。默认 `3`。
* 设为 `0` 则禁用 batch 内重试(每次尝试快速失败)。
*/
maxRetries?: number;
/**
* 首次重试前的初始退避延迟(毫秒)。每次重试翻倍,上限为 {@link retryMaxDelayMs}。默认 `250`。
*/
retryInitialDelayMs?: number;
/**
* 退避延迟上限(毫秒)。默认 `5000`。
*/
retryMaxDelayMs?: number;
/**
* 内部缓冲区可容纳的最大条目数,超出后丢弃最新条目。`0` 表示不限制。默认 `10_000`。
*/
maxQueueSize?: number;
/** 当达到 `maxQueueSize` 导致条目被丢弃时调用。 */
onQueueOverflow?: (entry: LogEntry, queueDepth: number) => void;
}
class OtlpTransport implements Transport {
/** 因队列溢出而丢弃的总条目数。 */
readonly overflowDropCount: number;
/** 缓冲区中等待发送的当前条目数。 */
readonly bufferLength: number;
/** 重试耗尽后未能投递的记录数。 */
readonly dropCount: number;
}通过 OTLP HTTP/JSON(POST /v1/logs)把日志转发到 OpenTelemetry Collector。日志会被缓冲并按批次发送;调用 logger.flush()(或开启 autoFlushOnExit)即可排空。需要全局 fetch(Node >= 18、现代浏览器、Electron 均支持)。
由于 write() 是同步的,Logger 层的通用 queueLimit / onOverflow 不会对 OTLP 生效。请使用 maxQueueSize / onQueueOverflow 控制 OTLP 的背压行为。
registerIpcReceiver
function registerIpcReceiver(
ingest: (entry: LogEntry) => void,
options?: { channel?: string },
): () => void;主进程侧的辅助函数:监听 IPC 频道,并将渲染进程发来的条目喂给 ingest(通常是 logger.ingestEntry)。返回用于取消注册的函数。
import { registerIpcReceiver } from 'lograil';
const off = registerIpcReceiver((entry) => logger.ingestEntry(entry));LiveTransport
interface LiveTransportOptions {
name?: string;
formatter?: Formatter; // 供 onFormatted 使用;否则给原始条目
bufferSize?: number; // 供 replay() 使用的环形缓冲大小;0 关闭(默认)
}
class LiveTransport implements Transport {
readonly name: string;
readonly formatter?: Formatter;
get subscriberCount(): number;
subscribe(cb: (entry: LogEntry) => void): () => void; // 返回取消订阅函数
onFormatted(cb: (line: string, entry: LogEntry) => void): () => void;
replay(cb: (entry: LogEntry) => void, newestFirst?: boolean): number; // 数量
clearBuffer(): void;
close(): void;
}用于实时日志流的内存型、可订阅传输器。write() 把每条条目转发给所有订阅者, 并捕获订阅者抛出的异常,使 logger 的热路径永不被打断。订阅者拿到的是已冻结、 LogEntry。当 bufferSize > 0 时,后加入的订阅者可用 replay() 回放 环形缓冲。详见传输器指南。
BroadcastChannelTransport
interface BroadcastChannelTransportOptions {
name?: string; // BroadcastChannel 名称,默认 'lograil'
transportName?: string; // Transport.name,默认 'broadcast-channel'
formatter?: Formatter;
}
class BroadcastChannelTransport implements Transport {
readonly name: string;
readonly formatter: Formatter;
onError?: (err: unknown, entry: LogEntry) => void;
/** 订阅本频道从其他标签页收到的条目。 */
subscribe(cb: (entry: LogEntry) => void): () => void;
write(entry: LogEntry, formatted: string): void;
close(): void;
}
function registerBroadcastChannelReceiver(
ingest: (entry: LogEntry) => void,
options?: { name?: string; transportName?: string },
): () => void;跨标签页 / 跨窗口的 Web 流式传输。每个 addTransport 了它的标签页会把每条 条目广播到一个共享的 BroadcastChannel;同一来源、相同频道名的其他标签页 通过 subscribe(或 registerBroadcastChannelReceiver)收到它。可在任意 运行时导入——在没有 BroadcastChannel 的平台(Node、Electron 主进程、旧 浏览器)上 write 为空操作,订阅者永不被调用。
OS 原生传输器
三个传输器把记录交给操作系统自身的日志栈。每个都可在任意运行时中导入, 且在模块加载时绝不抛错;在缺少对应二进制 / 设施时,传输器降级到 onError 路径,并用断路器限制,避免损坏的 sink 刷爆控制台。
interface SyslogTransportOptions {
name?: string;
formatter?: Formatter;
facility?: number; // RFC 5424 设施,默认 3(LOG_DAEMON)
protocol?: 'local' | 'udp'; // 'local' = logger 二进制,'udp' = 原始数据报
host?: string; // udp 模式,默认 'localhost'
port?: number; // udp 模式,默认 514
tag?: string; // logger -t tag,默认取传输器名
loggerPath?: string; // 覆盖二进制位置
}
class SyslogTransport implements Transport { /* name, formatter, write, close, onError */ }
interface JournaldTransportOptions {
name?: string;
formatter?: Formatter;
fields?: Record<string, string>; // 固定的 KEY=VALUE 日志字段(IDENTIFIER 默认取名称)
priority?: number; // 固定的 PRIORITY 日志字段
}
class JournaldTransport implements Transport { /* name, formatter, write, close, onError */ }
interface WindowsEventLogTransportOptions {
name?: string;
formatter?: Formatter;
log?: string; // 目标事件日志,默认取传输器名
source?: string; // 提供方 / 来源字符串,默认取传输器名
eventId?: number; // 默认记录 ID,默认 1
}
class WindowsEventLogTransport implements Transport { /* name, formatter, write, close, onError */ }SyslogTransport— Unix,经logger(protocol: 'local')或原始 UDP 数据报(protocol: 'udp',经nc)。JournaldTransport— Linux/systemd,经一个常驻的systemd-cat;fields成为固定的日志字段。WindowsEventLogTransport— Windows,经eventcreate.exe;可通过entry.metadata.wevtid按条目覆盖事件 ID。
三者共用级别 → syslog 优先级映射(trace/debug→7,info→6,warn→4, error→3,fatal→2)。平台门控:Unix 传输器需要对应二进制;Windows 传输器需要 win32。在浏览器构建中,三者都降级为不可用的 onError 路径。 详见传输器指南。