Guide
ログのタイムスタンプ: エラーの解析、正規化、および修正
このページを作成する理由
ログは、形式、タイムゾーン、精度が混在して到着します。タイムスタンプが 1 つ間違っていると、注文、ダッシュボード、インシデントのタイムラインが破壊されます。このガイドでは、ログ時間を迅速に正規化するためのクイック チェック、正規表現テンプレート、および言語スニペットを提供し、エッジ ケースを検証する必要がある場合はコンバーターを示します。
簡単な診断チェックリスト
- 形式を特定します: ISO 8601/RFC 3339、Apache/Nginx (
[dd/Mon/yyyy:HH:mm:ss Z])、syslog (MMM d HH:mm:ss)、またはカスタム。 - タイムゾーン: 明示的なオフセット (
+0800、Z) と暗黙的なローカルをチェックします。存在しない場合のデフォルトは UTC です。 - 精度を確認します: 秒、ミリ秒、マイクロ秒。一貫した長さを確保します。
- DST エッジ の処理: 最初に UTC に解析します。単純なローカル解析は避けてください。
- 範囲ガードを強制します: 信じられない年 (例:
<2000または>2100) を拒否します。
一般的なログ形式 (テンプレート)
- ISO 8601 / RFC 3339:
2024-12-01T10:15:30Z、2024-12-01T18:15:30+08:00 - Apache/Nginx:
[01/Dec/2024:10:15:30 +0000] - Syslog:
Dec 1 10:15:30 hostname app[123]: message(年/オフセットなし) - カスタム数値:
1701425730(秒)、1701425730000(ミリ秒)
正規化の手順
- フォーマットとタイムゾーンを検出します。欠落している場合は、UTC とみなされます。
- 認識可能な日時 (UTC) に解析します。
- 数値エポックの場合: 長さによってスケールします (10 秒/13 ミリ秒/16 μ秒/19 ナノ秒)。
- 正規の列 (UTC ミリなど) に保存し、オプションで生の文字列を保持します。
- 範囲を検証します。インデックスを作成する前に、外れ値を削除またはフラグを立てます。
コードスニペット
JavaScript / Node.js
import { DateTime } from "luxon";
export function parseLogTime(raw) {
const trimmed = raw.trim();
// ISO/RFC first
const iso = DateTime.fromISO(trimmed, { setZone: true });
if (iso.isValid) return iso.toUTC().toMillis();
// Apache/Nginx: [01/Dec/2024:10:15:30 +0000]
const m = trimmed.match(/\\[(\\d{2}\\/\\w{3}\\/\\d{4}:\\d{2}:\\d{2}:\\d{2} [+-]\\d{4})\\]/);
if (m) {
const dt = DateTime.fromFormat(m[1], "dd/MMM/yyyy:HH:mm:ss ZZZZ", { zone: "utc" });
if (dt.isValid) return dt.toMillis();
}
// Numeric epoch (seconds/ms/us/ns)
if (/^\\d+$/.test(trimmed)) {
const len = trimmed.length;
if (len === 10) return Number(trimmed) * 1000;
if (len === 13) return Number(trimmed);
if (len === 16) return Number(trimmed) / 1000;
if (len === 19) return Number(trimmed) / 1_000_000;
}
throw new Error("Unrecognized log timestamp");
}
パイソン
from datetime import datetime, timezone, timedelta
from dateutil import parser
def parse_log_time(raw: str) -> int:
s = raw.strip()
# ISO/RFC 3339, syslog with tz, etc.
try:
dt = parser.isoparse(s)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return int(dt.astimezone(timezone.utc).timestamp() * 1000)
except Exception:
pass
# Apache/Nginx like: [01/Dec/2024:10:15:30 +0000]
if s.startswith("[") and s.endswith("]"):
inner = s[1:-1]
dt = datetime.strptime(inner, "%d/%b/%Y:%H:%M:%S %z")
return int(dt.astimezone(timezone.utc).timestamp() * 1000)
# Numeric epoch
if s.isdigit():
n = len(s)
if n == 10:
return int(s) * 1000
if n == 13:
return int(s)
if n == 16:
return int(int(s) / 1000)
if n == 19:
return int(int(s) / 1_000_000)
raise ValueError("Unrecognized log timestamp")
正規表現テンプレート
regex
# ISO 8601 / RFC 3339 with offset
^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:?\\d{2})$
# Apache/Nginx
^\\[\\d{2}/[A-Za-z]{3}/\\d{4}:\\d{2}:\\d{2}:\\d{2} [+-]\\d{4}\\]$
# Syslog (no year/offset, add externally)
^[A-Za-z]{3}\\s+\\d{1,2}\\s\\d{2}:\\d{2}:\\d{2}\\s.+$
順序付け、インデックス付け、および保存
- 保存する前に UTC ミリ秒 (またはそれ以上) に正規化します。再解析が必要な場合は、生のテキストを保持してください。
- 範囲フィルター (
WHERE happens_at_ms >= ... AND happens_at_ms < ...) を使用して、インデックスを使いやすくします。 - 取り込みで形式が混在する場合は、validated_at 列を追加して、失敗を隔離します。
- 大量のログを日付で分割します。フィルターでのキャストは避けてください。
よくある落とし穴 (および修正)
- タイムゾーンがありません: デフォルトは UTC;ログ パーサーは、ソース ホストのタイムゾーンがわかっている場合は追加する必要があります。
- DST ギャップ/オーバーラップ: 最初に UTC として解析します。表示時にのみローカライズします。
- 混合精度: 秒とミリ秒が 1 つの列に混在しています。摂取時に長さを強制します。
- Syslog に年がありません: 解析中に現在の年を追加し、範囲を検証します。
- 関数でラップされたフィルター: WHERE の
DATE(occurred_at)を避けます。生成された日付列を使用します。
よくある質問
- ミリ秒と秒を検出するにはどうすればよいですか? 桁の長さを確認してください: 10=秒、13=ms、16=μs、19=ns。
- タイムゾーンが見つからない場合はどうなりますか? UTC を想定するか、解析する前に既知のホストのタイムゾーンを挿入します。
- ダッシュボードが正常に動作しないのはなぜですか? 精度または文字列の並べ替えが混在している可能性があります。数値 UTC とインデックスに正規化します。
- 履歴ログをバッチでクリーンアップするにはどうすればよいですか? ステージング テーブルを使用し、解析して検証し、クリーンな行のみを移動します。関連ツールを参照してください。