mcpField
L
logtape

logtape

github.com/marcioaltoe开发058 次安装1 天前更新
代码仓库

关于 logtape

面向 JavaScript 和 TypeScript 开发者的 LogTape 编码助手技能,指导其在应用和库中正确使用日志框架。涵盖结构化日志、层级类别、上下文、输出配置与脱敏。

主要功能

  1. 01按层级类别获取 logger
  2. 02记录结构化消息与属性
  3. 03配置日志级别和输出目标
  4. 04为日志添加显式或隐式上下文
  5. 05通过字段或模式脱敏数据
  6. 06适配现有日志框架并测试

适用场景

  1. 01为应用配置 LogTape 日志
  2. 02在模块中组织可筛选的日志类别
  3. 03关联请求上下文与日志记录
  4. 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/otelOpenTelemetry
@logtape/sentrySentry
@logtape/syslogSyslog
@logtape/cloudwatch-logsAWS CloudWatch Logs
@logtape/windows-eventlogWindows Event Log
框架集成
软件包说明
@logtape/drizzle-ormDrizzle ORM 查询日志
@logtape/elysiaElysia HTTP 日志
@logtape/expressExpress HTTP 日志
@logtape/fastifyFastify HTTP 日志
@logtape/graphql-yogaGraphQL Yoga 日志
@logtape/honoHono HTTP 日志
@logtape/koaKoa 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()

评论

支持 Markdown