关于 logtape
面向 JavaScript 和 TypeScript 开发者的 LogTape 编码助手技能,指导其在应用和库中正确使用日志框架。涵盖结构化日志、层级类别、上下文、输出配置与脱敏。
主要功能
- 01按层级类别获取 logger
- 02记录结构化消息与属性
- 03配置日志级别和输出目标
- 04为日志添加显式或隐式上下文
- 05通过字段或模式脱敏数据
- 06适配现有日志框架并测试
适用场景
- 01为应用配置 LogTape 日志
- 02在模块中组织可筛选的日志类别
- 03关联请求上下文与日志记录
- 04避免敏感数据进入日志输出
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ marcioaltoe/ skills --skill logtape安装后在 Claude Code 中输入/logtape确认已加载。
详细介绍
LogTape:面向 AI 编码助手的技能
LogTape 是一个零依赖、以库为先的 JavaScript 和 TypeScript 日志框架,可在 Deno、Node.js、Bun、浏览器和边缘 函数中运行。
完整文档:https://logtape.org/
获取日志记录器
始终使用带有数组类别的 getLogger(),以启用层级
筛选。层级结构类似路径:父类别的配置会应用于所有子类别。
import { getLogger } from "@logtape/logtape";
// Good: array form enables hierarchical filtering
const logger = getLogger(["my-app", "users", "auth"]);
// Acceptable shorthand for a single-segment category
const rootLogger = getLogger("my-app");
选择能反映模块结构的类别段,以便运维人员 按子系统选择性地启用或禁用日志记录。
使用 logger.getChild("sub") 派生子日志记录器,无需重复完整类别:
const dbLogger = logger.getChild("database");
// category = ["my-app", "users", "auth", "database"]
详情请参阅 https://logtape.org/manual/categories.md。
结构化消息
使用带有属性对象的命名占位符。这样可以让消息 易于解析,并可搜索属性:
// Correct: structured message with named placeholders
logger.info("User {userId} logged in from {ip}", { userId, ip });
// Correct: structured data without a message
logger.info({ userId, ip, action: "login" });
// Nested property access (since 1.2.0)
logger.info("Name: {user.name}", { user: { name: "Alice" } });
模板字面量语法适用于快速调试日志,但不会 生成结构化数据:
// Template literal: convenient but not structured
logger.debug`User ${userId} logged in`;
完整的结构化日志记录详情请参阅 https://logtape.org/manual/struct.md。
严重级别
LogTape 提供六个级别,从最详细到最简略:
| 级别 | 用途 |
|---|---|
trace | 非常细粒度的诊断输出 |
debug | 面向开发者的诊断消息 |
info | 常规运行事件(启动、关闭等) |
warning | 意外但可恢复的情况 |
error | 影响单个操作的错误 |
fatal | 需要终止进程的不可恢复错误 |
使用恰当的最低级别。将 error/fatal 留给实际故障;
不要将它们用于验证错误等预期情况。
详情请参阅 https://logtape.org/manual/levels.md。
配置
异步配置(最常见)
configure() 仅用于应用程序。必须对其使用 await,并在启动时恰好调用一次(例如,在入口点中):
import { configure, getConsoleSink } from "@logtape/logtape";
await configure({
sinks: {
console: getConsoleSink(),
},
loggers: [
{
category: "my-app",
lowestLevel: "debug",
sinks: ["console"],
},
],
});
同步配置
无法使用 await 时(例如,在 CommonJS 的顶层,
或在同步启动流程中),请使用 configureSync():
import { configureSync, getConsoleSink } from "@logtape/logtape";
configureSync({
sinks: {
console: getConsoleSink(),
},
loggers: [
{
category: "my-app",
lowestLevel: "debug",
sinks: ["console"],
},
],
});
限制:
configureSync()无法使用AsyncDisposable接收器,例如getStreamSink()或使用fromAsyncSink()创建的接收器。
关键规则
configure()返回Promise;务必对其使用await。configureSync()返回void;不要对其使用await。- 二者任选其一,只调用一次。未先重置就再次调用会抛出
ConfigError。 - 不要混用异步和同步方式:如果使用了
configure(),请使用await reset()重置;如果使用了configureSync(),请使用resetSync()重置。 - 对于测试,请在拆卸阶段调用
await reset()(或resetSync())。
特定框架的模式
- React:在
createRoot()之前配置。 - Vue:在
app.mount()之前配置。 - Next.js:使用 instrumentation.js(服务端)或 instrumentation-client.js(客户端)。
- SvelteKit:在 hooks.server.ts 中配置。
所有选项请参阅 https://logtape.org/manual/config.md。
库作者规则
绝不要在库代码中调用 configure() 或 configureSync()。 库
只应调用 getLogger() 并记录消息。依赖你的库的应用程序决定如何(或是否)配置接收器和级别。
// my-lib/src/client.ts — library code
import { getLogger } from "@logtape/logtape";
// Good: just get a logger, don't configure
const logger = getLogger(["my-lib", "client"]);
export function fetchData(url: string) {
logger.debug("Fetching {url}", { url });
// ...
}
如果你的库封装了其他使用 LogTape 的库,请使用 withCategoryPrefix()
将它们的日志嵌套在你的类别下:
import { withCategoryPrefix } from "@logtape/logtape";
export function myOperation() {
return withCategoryPrefix(["my-lib"], () => {
// Logs from inner libraries appear as ["my-lib", ...their-category]
innerLib.doWork();
});
}
注意:
withCategoryPrefix()要求应用程序配置contextLocalStorage。
完整指南请参阅 https://logtape.org/manual/library.md。
使用 with() 的上下文和惰性求值
添加显式上下文
使用 logger.with() 创建子日志记录器,为后续的每次日志调用附加属性:
const reqLogger = logger.with({ requestId, userId });
reqLogger.info("Processing order {orderId}", { orderId });
// Log record will contain requestId, userId, AND orderId
隐式上下文(请求跟踪)
使用 withContext() 在整个调用栈中传播上下文,无需手动传递日志记录器。需要在配置中启用 contextLocalStorage:
import { configure, getConsoleSink, withContext } from "@logtape/logtape";
import { AsyncLocalStorage } from "node:async_hooks";
await configure({
sinks: { console: getConsoleSink() },
loggers: [{ category: "app", sinks: ["console"] }],
contextLocalStorage: new AsyncLocalStorage(),
});
function handleRequest(req: Request) {
withContext({ requestId: crypto.randomUUID() }, () => {
// All logs inside this callback automatically include requestId
processRequest(req);
});
}
**注意:**浏览器暂不支持隐式上下文。
惰性求值
使用 lazy() 包装开销较大的计算,使其仅在启用相应级别时运行:
import { getLogger, lazy } from "@logtape/logtape";
const logger = getLogger(["my-app"]);
logger.debug("System state: {state}", {
state: lazy(() => JSON.stringify(getExpensiveState())),
});
对于结构化数据,将回调作为第二个参数传入:
logger.debug("Diagnostics", () => ({
heap: process.memoryUsage().heapUsed,
uptime: process.uptime(),
}));
对于异步惰性求值,传入异步回调并使用 await 获取结果:
await logger.info("User details", async () => ({
user: await fetchUserDetails(),
}));
对于多个开销较大的日志调用,请使用 isEnabledFor():
if (logger.isEnabledFor("debug")) {
const snapshot = await captureExpensiveSnapshot();
logger.debug("Snapshot: {data}", { data: snapshot });
}
详情请参阅 https://logtape.org/manual/lazy.md 和 https://logtape.org/manual/contexts.md。
记录错误
直接将 Error 对象传递给 error() 或 fatal()。可以将额外属性作为第二个参数附加:
try {
await riskyOperation();
} catch (error) {
logger.error(error, { operation: "riskyOperation", userId });
}
接收器和格式化器
内置接收器
import {
configure,
getConsoleSink,
getStreamSink,
} from "@logtape/logtape";
await configure({
sinks: {
console: getConsoleSink(),
stderr: getStreamSink(Writable.toWeb(process.stderr)),
},
loggers: [{ category: "app", sinks: ["console"] }],
});
接收器过滤器
使用 withFilter() 将不同级别的日志路由到不同的接收器:
import { configure, getConsoleSink, withFilter } from "@logtape/logtape";
await configure({
sinks: {
errorsOnly: withFilter(getConsoleSink(), "error"),
allLevels: getConsoleSink(),
},
loggers: [
{ category: "app", sinks: ["allLevels", "errorsOnly"] },
],
});
格式化器
import {
configure,
getAnsiColorFormatter,
getConsoleSink,
getJsonLinesFormatter,
getLogfmtFormatter,
} from "@logtape/logtape";
// Pretty ANSI-colored output for development
getConsoleSink({ formatter: getAnsiColorFormatter() });
// JSON Lines for production / log aggregation
getConsoleSink({ formatter: getJsonLinesFormatter() });
// logfmt for readable structured logs
getConsoleSink({ formatter: getLogfmtFormatter() });
若要获得更出色的开发输出,请使用 @logtape/pretty:
import { getPrettyFormatter } from "@logtape/pretty";
getConsoleSink({ formatter: getPrettyFormatter() });
释放资源
非阻塞接收器和流接收器会持有资源。在边缘函数或短生命周期进程中,请在退出前显式释放资源:
import { dispose } from "@logtape/logtape";
// At shutdown
await dispose();
详情请参阅 https://logtape.org/manual/sinks.md 和 https://logtape.org/manual/formatters.md。
数据脱敏
使用 @logtape/redaction 防止敏感数据出现在日志输出中。
基于模式的脱敏(扫描格式化后的文本)
使用 redactByPattern() 包装格式化器,以捕获日志输出中任意位置的数据,例如电子邮件地址、信用卡号或 JWT:
import { defaultConsoleFormatter, getConsoleSink } from "@logtape/logtape";
import {
EMAIL_ADDRESS_PATTERN,
JWT_PATTERN,
redactByPattern,
} from "@logtape/redaction";
const sink = getConsoleSink({
formatter: redactByPattern(defaultConsoleFormatter, [
EMAIL_ADDRESS_PATTERN,
JWT_PATTERN,
]),
});
内置模式:EMAIL_ADDRESS_PATTERN、CREDIT_CARD_NUMBER_PATTERN、
JWT_PATTERN、US_SSN_PATTERN、KR_RRN_PATTERN。
基于字段的脱敏(按名称移除或替换属性)
使用 redactByField() 包装接收器,在结构化日志数据传递给接收器之前移除其中的敏感字段:
import { getConsoleSink } from "@logtape/logtape";
import { redactByField } from "@logtape/redaction";
// Uses DEFAULT_REDACT_FIELDS (password, secret, token, etc.)
const sink = redactByField(getConsoleSink());
// Or customize with replacement instead of removal
const sink2 = redactByField(getConsoleSink(), {
fieldPatterns: [/password/i, /secret/i, /api[-_]?key/i],
action: () => "[REDACTED]",
});
结合两种方式以获得最高安全性
const sink = redactByField(
getConsoleSink({
formatter: redactByPattern(defaultConsoleFormatter, [
EMAIL_ADDRESS_PATTERN,
JWT_PATTERN,
]),
}),
);
详情请参阅 https://logtape.org/manual/redaction.md。
现有日志记录器适配器
如果项目已使用 winston、Pino 或 log4js,请使用适配器,而不是 configure():
import { install } from "@logtape/adaptor-winston";
import winston from "winston";
const winstonLogger = winston.createLogger({ /* ... */ });
install(winstonLogger);
// All LogTape logs now route through winston
可用适配器:@logtape/adaptor-winston、@logtape/adaptor-pino、
@logtape/adaptor-log4js。
详情请参阅 https://logtape.org/manual/adaptors.md。
测试
测试时,请使用 @logtape/testing,并断言收集到的记录:
import { configure, getLogger, reset } from "@logtape/logtape";
import { createLogRecorder } from "@logtape/testing";
const recorder = createLogRecorder();
try {
await configure({
sinks: { recorder: recorder.sink },
loggers: [
{ category: "test", sinks: ["recorder"] },
{ category: ["logtape", "meta"], sinks: [] },
],
});
const logger = getLogger(["test"]);
logger.info("User {userId} logged in.", { userId: "u-123" });
recorder.assertLogged({
category: "test",
level: "info",
message: /^User .+ logged in\.$/,
properties: { userId: "u-123" },
});
} finally {
await reset();
}
如果使用 configureSync(),请使用 resetSync() 重置:
import { configureSync, resetSync } from "@logtape/logtape";
configureSync({ /* ... */ });
// ... test ...
resetSync();
始终在测试拆卸时调用 reset() / resetSync(),以便每个测试都能独立配置。
如需了解更多用法,请参阅 https://logtape.org/manual/testing.md。
可用软件包
接收器软件包
| 软件包 | 说明 |
|---|---|
@logtape/file | 文件和轮转文件 |
@logtape/otel | OpenTelemetry |
@logtape/sentry | Sentry |
@logtape/syslog | Syslog |
@logtape/cloudwatch-logs | AWS CloudWatch Logs |
@logtape/windows-eventlog | Windows Event Log |
框架集成
| 软件包 | 说明 |
|---|---|
@logtape/drizzle-orm | Drizzle ORM 查询日志 |
@logtape/elysia | Elysia HTTP 日志 |
@logtape/express | Express HTTP 日志 |
@logtape/fastify | Fastify HTTP 日志 |
@logtape/graphql-yoga | GraphQL Yoga 日志 |
@logtape/hono | Hono HTTP 日志 |
@logtape/koa | Koa HTTP 日志 |
格式化器
| 包 | 描述 |
|---|---|
@logtape/pretty | 美观的控制台格式化器 |
@logtape/redaction | 敏感数据脱敏 |
测试
| 包 | 描述 |
|---|---|
@logtape/testing | 日志记录与断言 |
适配器(用于现有日志记录器)
| 包 | 描述 |
|---|---|
@logtape/adaptor-pino | 与 Pino 兼容 |
@logtape/adaptor-winston | 与 Winston 兼容 |
@logtape/adaptor-log4js | 与 log4js 兼容 |
常见错误
使用模板字面量处理结构化数据
// WRONG: template literals don't produce structured data
logger.info`User ${userId} performed ${action}`;
// CORRECT: use placeholders for structured, searchable logs
logger.info("User {userId} performed {action}", { userId, action });
字符串拼接
// WRONG: loses structure, always evaluates
logger.info("User " + userId + " logged in from " + ip);
// CORRECT
logger.info("User {userId} logged in from {ip}", { userId, ip });
在库代码中调用 configure()
// WRONG: library must never configure LogTape
import { configure } from "@logtape/logtape";
await configure({ /* ... */ }); // Don't do this in a library!
// CORRECT: just use getLogger()
import { getLogger } from "@logtape/logtape";
const logger = getLogger(["my-lib"]);
忘记对 configure() 使用 await
// WRONG: configure() returns a Promise; without await, logging may
// not work when you expect it to
configure({ /* ... */ });
// CORRECT
await configure({ /* ... */ });
// Or use the synchronous variant if you can't await
configureSync({ /* ... */ });
混用异步/同步配置与重置
// WRONG: mismatched pair causes errors
await configure({ /* ... */ });
resetSync(); // Can't sync-reset an async config!
// CORRECT: match configure and reset variants
await configure({ /* ... */ });
await reset();
// Or:
configureSync({ /* ... */ });
resetSync();
在使用 LogTape 的同时调用 console.log
// WRONG: bypasses LogTape's filtering, formatting, and routing
console.log("User logged in:", userId);
// CORRECT: use the logger so the message goes through configured sinks
logger.info("User {userId} logged in", { userId });
扁平类别
// WRONG: single flat string prevents granular filtering
const logger = getLogger("myapp");
// CORRECT: hierarchical array enables per-module control
const logger = getLogger(["myapp", "auth", "oauth"]);
记录敏感数据时未进行脱敏
// WRONG: password ends up in log files
logger.info("Login attempt for {email} with {password}", { email, password });
// CORRECT: never log secrets; use @logtape/redaction if needed
logger.info("Login attempt for {email}", { email });
最佳实践摘要
- 在启动时调用一次
configure()或configureSync() - 始终对
configure()使用await;绝不要对configureSync()使用await - 让重置方法与配置方法的变体保持一致
- 使用层次化的数组类别(例如
["app", "module", "sub"]) - 使用带命名占位符的结构化消息,而非字符串插值
- 对开销较大的计算使用
lazy()或回调 - 使用
logger.with()设置请求范围的上下文 - 使用
withContext()+AsyncLocalStorage隐式传递上下文 - 仅在确有故障时使用
error/fatal - 库:绝不进行配置,只调用
getLogger() - 测试:在拆卸阶段调用
reset()/resetSync(),并使用缓冲 sink 进行断言 - 绝不记录敏感数据(密码、令牌、个人身份信息)
- 在边缘函数或短生命周期进程中使用
dispose()/disposeSync()