logtape について
LogTapeを使うJavaScript・TypeScript開発者向けのコーディングスキルです。アプリケーションやライブラリでの構造化ログ、階層カテゴリ、コンテキスト、出力設定、データ秘匿化を扱います。
主な機能
- 01階層カテゴリによるロガー取得
- 02構造化メッセージとプロパティの記録
- 03レベルと出力先の設定
- 04明示的・暗黙的コンテキストの付与
- 05フィールドやパターンによるデータ秘匿化
- 06既存ロガーとの連携とテスト
活用シーン
- 01アプリケーションのLogTape設定
- 02モジュール別の絞り込み可能なカテゴリ整理
- 03リクエストコンテキストとログの関連付け
- 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/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"]);
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()を使う