JS, TSのロギングライブラリLogTapeでログのマスキングをする

JS, TSのロギングライブラリLogTapeでログのマスキングをする

LogTapeのマスキング機能を試してみました。API KeyやJWTなどの秘匿情報をログから隠蔽する方法について、Pattern-based redactionとField-based redactionの2つのアプローチを紹介します。
2026.09.11

こんばんは、情報システム室の夏目です。

先日LogTapeというロギングライブラリを紹介しましたが、API Keyなどの秘匿したい情報をマスキングする仕組みがあるので試してみます。

https://dev.classmethod.jp/articles/using-js-ts-logging-library-logtape/

@logtape/redaction

https://logtape.org/manual/redaction

ログに含まれる機密データは、セキュリティリスクやプライバシー侵害の原因となる可能性があります。LogTapeでは、@logtape/redaction パッケージを通じて強力なマスキング機能を提供しており、ログに記録される機密情報が外部に漏洩するのを効果的に防ぐことができます。

LogTapeが提供するマスキング機能は @logtape/redaction というパッケージで提供されています。

LogTapeが提供するマスキング機能は二つのアプローチがあります。

  • Pattern-based redaction
    • 正規表現を使って、 をマスキングします
  • Field-based redaction
    • 正規表現や文字列で プロパティのフィールド名 を指定してログから除外します

Pattern-based redaction

https://logtape.org/manual/redaction#pattern-based-redaction

パターンベースのマスキングでは、正規表現を使用して、クレジットカード番号、メールアドレス、トークンなどの機密データパターンを識別し、ログの整形出力からこれらの情報を隠蔽します。

正規表現を使ってマスキングしたい を指定する方法です。

code
import { configure, getConsoleSink, defaultConsoleFormatter } from "@logtape/logtape";
import { redactByPattern, JWT_PATTERN } from "@logtape/redaction";

await configure({
  sinks: {
    console: getConsoleSink({
      formatter: redactByPattern(defaultConsoleFormatter, [JWT_PATTERN]),
    }),
  },
  loggers: [{ category: ["app"], sinks: ["console"], lowestLevel: "debug" }],
});

Pattern-based redactionではformatterをラップしてマスキングの設定を行います。
5つほど事前定義されたパターンがあり、ここではJWTをマスキングするように設定しています。

試しに動かして見ます。

code
import { configure, getConsoleSink, defaultConsoleFormatter, getLogger } from "@logtape/logtape";
import { redactByPattern, JWT_PATTERN } from "@logtape/redaction";

await configure({
  sinks: {
    console: getConsoleSink({
      formatter: redactByPattern(defaultConsoleFormatter, [JWT_PATTERN]),
    }),
  },
  loggers: [
    { category: ["logtape", "meta"], sinks: [], lowestLevel: "fatal" },
    { category: ["app"], sinks: ["console"], lowestLevel: "debug" },
  ],
});

const jwt =
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30";
const logger = getLogger(["app"]);

logger.info("jwt: {jwt}", { jwt });
出力
07:49:59.004 INF app jwt: '[JWT REDACTED]'

カスタムパターンを定義することも可能で次のように書きます。

code
import { configure, getConsoleSink, defaultConsoleFormatter, getLogger } from "@logtape/logtape";
import { redactByPattern, type RedactionPattern } from "@logtape/redaction";

const patterns: Array<RedactionPattern> = [
  {
    pattern: /ghs_\d{3,}_eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g,
    replacement: "[GITHUB APP INSTALLATION TOKEN REDACTED]",
  },
];

await configure({
  sinks: {
    console: getConsoleSink({
      formatter: redactByPattern(defaultConsoleFormatter, patterns),
    }),
  },
  loggers: [
    { category: ["logtape", "meta"], sinks: [], lowestLevel: "fatal" },
    { category: ["app"], sinks: ["console"], lowestLevel: "debug" },
  ],
});

const pat =
  "ghs_4868100_eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRobmQiLCJjdHgiOiIwU0Y1SlVDSml2Q0tTWlJZTVVKWWdfM3VQdlFCUDZMWkM0ZXFUaElfMjNneW52Mm9pb3Q4RVlaV2hzcDdRaTQiLCJleHAiOjE3ODkxMTYwNjksImlhdCI6MTc4OTExMjQ2OSwiaXNzIjoiZ2l0aHViIiwianRpIjoiNWJiNDk1ZDYtZDNlYS00Y2Y0LWJiNTctMzZjZWU2NjEyMWQ0IiwidmVyIjozfQ.BMz2Qd3gTHgebizvaX8fQMS2PDu68HQGazGKcAWvRHWqeopxc-hfdBquiSUmobd0oh0SByky5phbnyg57SBijw";
const logger = getLogger(["app"]);

logger.info("pat: {pat}", { pat });
出力
07:52:49.089 INF app pat: '[GITHUB APP INSTALLATION TOKEN REDACTED]'

pattern に正規表現でマスキングしたい値を定義し、 replacement にマスキングで上書きに使用する値を定義します。
pattern に定義する正規表現では g のフラグを必要があります。

事前定義されているパターン

https://github.com/dahlia/logtape/blob/main/packages/redaction/src/pattern.ts#L42-L50

https://github.com/dahlia/logtape/blob/main/packages/redaction/src/pattern.ts#L186-L194

https://github.com/dahlia/logtape/blob/main/packages/redaction/src/pattern.ts#L196-L203

https://github.com/dahlia/logtape/blob/main/packages/redaction/src/pattern.ts#L205-L213

https://github.com/dahlia/logtape/blob/main/packages/redaction/src/pattern.ts#L215-L222

Field-based redaction

https://logtape.org/manual/redaction#field-based-redaction

フィールドベースのマスキング機能は、構造化ログデータ内のフィールド名に基づいて機密データを特定し、マスキングします。ログレコードのプロパティ名を検査し、指定されたパターンに一致するものをマスキングすることで機能します。

正規表現か文字列を使用して除外したい プロパティのフィールド名 を指定する方法です。

import { configure, getConsoleSink, jsonLinesFormatter } from "@logtape/logtape";
import { redactByField } from "@logtape/redaction";

await configure({
  sinks: {
    console: redactByField(getConsoleSink({ formatter: jsonLinesFormatter })),
  },
  loggers: [{ category: ["app"], sinks: ["console"], lowestLevel: "debug" }],
});

Field-based redactionではsinkをラップしてマスキングを設定します。
マスキング対象のフィールドを指定していない場合、デフォルトのフィールドが対象になります。

https://github.com/dahlia/logtape/blob/main/packages/redaction/src/field.ts#L49-L69

試しに動かしてみます。

code
import { configure, getConsoleSink, jsonLinesFormatter, getLogger } from "@logtape/logtape";
import { redactByField } from "@logtape/redaction";

await configure({
  sinks: {
    console: redactByField(getConsoleSink({ formatter: jsonLinesFormatter })),
  },
  loggers: [
    { category: ["logtape", "meta"], sinks: [], lowestLevel: "fatal" },
    { category: ["app"], sinks: ["console"], lowestLevel: "debug" },
  ],
});

const logger = getLogger(["app"]);

logger.info("useId: {userId}, passcode: {passcode}", { userId: 111, passcode: "9Ylgk_p." });
出力
{
  "@timestamp": "2026-09-11T08:12:23.420Z",
  "level": "INFO",
  "message": "useId: 111, passcode: \"\"",
  "logger": "app",
  "properties": {
    "userId": 111
  }
}

Field-based redactionのデフォルト挙動はマスキングではなくプロパティからの除外なので該当するフィールドが消えてます。

フィールド削除ではなくマスキングにすることもできます。

code
import { configure, getConsoleSink, jsonLinesFormatter, getLogger } from "@logtape/logtape";
import { redactByField, DEFAULT_REDACT_FIELDS } from "@logtape/redaction";

await configure({
  sinks: {
    console: redactByField(getConsoleSink({ formatter: jsonLinesFormatter }), {
      fieldPatterns: DEFAULT_REDACT_FIELDS,
      action: () => "[REDACTED]",
    }),
  },
  loggers: [
    { category: ["logtape", "meta"], sinks: [], lowestLevel: "fatal" },
    { category: ["app"], sinks: ["console"], lowestLevel: "debug" },
  ],
});

const logger = getLogger(["app"]);

logger.info("useId: {userId}, passcode: {passcode}", { userId: 111, passcode: "9Ylgk_p." });
出力
{
  "@timestamp": "2026-09-11T08:16:33.249Z",
  "level": "INFO",
  "message": "useId: 111, passcode: \"[REDACTED]\"",
  "logger": "app",
  "properties": {
    "userId": 111,
    "passcode": "[REDACTED]"
  }
}

対象のフィールド名を正規表現と文字列を使って設定することもできます。

import { configure, getConsoleSink, jsonLinesFormatter } from "@logtape/logtape";
import { redactByField, DEFAULT_REDACT_FIELDS } from "@logtape/redaction";

await configure({
  sinks: {
    console: redactByField(getConsoleSink({ formatter: jsonLinesFormatter }), {
      fieldPatterns: ["city", /(first|second|full)Name/i, ...DEFAULT_REDACT_FIELDS],
    }),
  },
  loggers: [
    { category: ["logtape", "meta"], sinks: [], lowestLevel: "fatal" },
    { category: ["app"], sinks: ["console"], lowestLevel: "debug" },
  ],
});

匿名化マスキング

他にもフィールドの値を匿名化してマスキングする機能もあります。

code
import {
  configure,
  getConsoleSink,
  jsonLinesFormatter,
  getLogger,
  dispose,
} from "@logtape/logtape";
import { redactByFieldAsync, createHmacPseudonymizer } from "@logtape/redaction";

await configure({
  sinks: {
    console: redactByFieldAsync(getConsoleSink({ formatter: jsonLinesFormatter }), {
      fieldPatterns: [/userId/i],
      action: await createHmacPseudonymizer({ key: "key" }),
    }),
  },
  loggers: [
    { category: ["logtape", "meta"], sinks: [], lowestLevel: "fatal" },
    { category: ["app"], sinks: ["console"], lowestLevel: "debug" },
  ],
});

const logger = getLogger(["app"]);

logger.info("userId: {userId}", { userId: "aaa" });
logger.info("userId: {userId}", { userId: "bbb" });
logger.info("userId: {userId}", { userId: "ccc" });
logger.info("userId: {userId}", { userId: "aaa" });

// 終了前に実行する
await dispose();
出力
{"level":"INFO","message":"userId: \"hmac-sha256:KziwWjLmrHd5JnkcPfSoBkgkaHgPqc4eVABxq9tiP2s\"","logger":"app","@timestamp":"2026-09-11T08:27:48.958Z","properties":{"userId":"hmac-sha256:KziwWjLmrHd5JnkcPfSoBkgkaHgPqc4eVABxq9tiP2s"}}
{"level":"INFO","message":"userId: \"hmac-sha256:BgLcoOo6eiBMRWxBetCpqgDdq0d2CdfnReAtywhRuW0\"","logger":"app","@timestamp":"2026-09-11T08:27:48.962Z","properties":{"userId":"hmac-sha256:BgLcoOo6eiBMRWxBetCpqgDdq0d2CdfnReAtywhRuW0"}}
{"level":"INFO","message":"userId: \"hmac-sha256:eO3CTIxMHHMj_LPAGf9Ru4DMzmQ-slFb-7atEWLaB-0\"","logger":"app","@timestamp":"2026-09-11T08:27:48.962Z","properties":{"userId":"hmac-sha256:eO3CTIxMHHMj_LPAGf9Ru4DMzmQ-slFb-7atEWLaB-0"}}
{"level":"INFO","message":"userId: \"hmac-sha256:KziwWjLmrHd5JnkcPfSoBkgkaHgPqc4eVABxq9tiP2s\"","logger":"app","@timestamp":"2026-09-11T08:27:48.962Z","properties":{"userId":"hmac-sha256:KziwWjLmrHd5JnkcPfSoBkgkaHgPqc4eVABxq9tiP2s"}}

HMACを使って値をハッシュ化することで対象のフィールドの値を匿名化して区別可能にします。
注意点としては非同期処理で匿名化しているので、終了前に await dispose() を呼ぶ必要があることです。
(Web Crypto APIを使用しており、これが非同期処理であるため、 await dispose() を実行して匿名化処理の完了を待機してから終了する必要がある)

組み合わせて使う

Pattern-based redactionはformatterをラップして機能し、Field-based redactionはsinkをラップして機能するものであるため組み合わせて使うこともできます。

code
import {
  configure,
  getConsoleSink,
  jsonLinesFormatter,
  getLogger,
  dispose,
} from "@logtape/logtape";
import {
  redactByPattern,
  redactByField,
  redactByFieldAsync,
  createHmacPseudonymizer,
} from "@logtape/redaction";

await configure({
  sinks: {
    console: redactByField(
      redactByFieldAsync(
        getConsoleSink({
          formatter: redactByPattern(jsonLinesFormatter, [
            { pattern: /(red|green|blue)/g, replacement: "[COLOR REDACTED]" },
          ]),
        }),
        {
          fieldPatterns: [/userId/i],
          action: await createHmacPseudonymizer({ key: "key" }),
        },
      ),
      {
        fieldPatterns: ["name"],
      },
    ),
  },
  loggers: [
    { category: ["logtape", "meta"], sinks: [], lowestLevel: "fatal" },
    { category: ["app"], sinks: ["console"], lowestLevel: "debug" },
  ],
});

const logger = getLogger(["app"]);

logger.info("message", { userId: "aaa", name: "yuta", color: "red" });

// 終了前に実行する
await dispose();
出力
{
  "@timestamp": "2026-09-11T08:48:13.328Z",
  "level": "INFO",
  "message": "message",
  "logger": "app",
  "properties": {
    "userId": "hmac-sha256:KziwWjLmrHd5JnkcPfSoBkgkaHgPqc4eVABxq9tiP2s",
    "color": "[COLOR REDACTED]"
  }
}

まとめ

以上LogTapeでログの一部をマスキングする方法についてまとめました。

何かのお役に立てたら幸いです。

この記事をシェアする

関連記事