mcpField
L
logtape

logtape

github.com/marcioaltoe開発058 回インストール1 日前に更新
リポジトリ

logtape について

LogTapeを使うJavaScript・TypeScript開発者向けのコーディングスキルです。アプリケーションやライブラリでの構造化ログ、階層カテゴリ、コンテキスト、出力設定、データ秘匿化を扱います。

主な機能

  1. 01階層カテゴリによるロガー取得
  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を入力して読み込まれたか確認します。

詳細

AI コーディングアシスタント向け LogTape スキル

LogTape は、Deno、Node.js、Bun、ブラウザー、エッジ関数で動作する、依存関係ゼロでライブラリを中心に設計された JavaScript および TypeScript 用ロギングフレームワークです。

ドキュメント全文: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 には、最も詳細なレベルから最も詳細でないレベルまで、6 つのレベルがあります。

レベル用途
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() は、getStreamSink() などの AsyncDisposable シンクや、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())),
});

構造化データの場合は、2 番目の引数としてコールバックを渡します。

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() に直接渡します。2 番目の引数として追加のプロパティを付加できます。

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-pinoPino との互換性
@logtape/adaptor-winstonWinston との互換性
@logtape/adaptor-log4jslog4js との互換性

よくある間違い

構造化データにテンプレートリテラルを使う
// 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"]);
await configure() を忘れる
// 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({ /* ... */ });
非同期/同期の configure と reset を混在させる
// 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 しない
  • configure の種類に対応する reset の種類を使う
  • 階層型の配列カテゴリーを使う(例:["app", "module", "sub"])
  • 文字列補間ではなく、名前付きプレースホルダーを使った構造化メッセージを使う
  • 時間のかかる計算には lazy() またはコールバックを使う
  • リクエストスコープのコンテキストには logger.with() を使う
  • 暗黙のコンテキスト伝播には withContext() と AsyncLocalStorage を使う
  • error/fatal は実際の障害にのみ使う
  • ライブラリーでは configure を行わず、getLogger() のみを使う
  • テストでは、終了処理で reset()/resetSync() を呼び出し、アサーションにはバッファーシンクを使う
  • 機密データ(パスワード、トークン、個人情報)を決してログに記録しない
  • エッジ関数または短時間だけ実行されるプロセスでは、dispose()/disposeSync() を使う

コメント