PowerShellでJSONLログを1行ずつ追記・検査する ― 壊れた行を切り分ける方法

PowerShellカテゴリを表すパンダのイラスト PowerShell

この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。JSON Linesの形式要件とMicrosoft LearnのPowerShellコマンド仕様を確認し、ダミーログの追記・読込み・異常行の扱いを学べるコードにまとめました。

検証ステータス:📘 公式仕様確認済み・PowerShell実機未確認
対象はPowerShell 7以降です。掲載する実行結果は、コードから想定される出力例であり、実機実行記録ではありません。

PowerShellで処理履歴を残すとき、JSONファイル全体を毎回書き直す方法だと、イベントが増えるほど管理が面倒になります。

そんなときに便利なのがJSON Lines(JSONL、改行区切りJSON)です。通常のJSONは「ファイル全体で1つのJSON値」ですが、JSONLは1行につき1つの独立したJSON値を置きます。

例えば、次のような形式です。

{"event_id":"evt-001","status":"START","message":"開始"}
{"event_id":"evt-002","status":"DONE","message":"完了"}

この2行は、1行ずつ取り出して別々にJSONとして解析できます。ここではPowerShellで2件を追記し、最後に不正な1行をわざと追加して、どこまで正常に読み取れるかまで確認できる形にします。

JSONとJSONLはどこが違う?

観点通常のJSON配列JSONL
保存例[{...},{...}]1行に{...}を1件ずつ
追記配列の末尾・カンマ・括弧を意識する新しいJSON値と改行を末尾に追加
読み込みファイル全体を扱うことが多い1行ずつ順番に処理しやすい
途中に壊れた行全体の解析が失敗し得る行単位でエラーを切り分けられる
向く用途まとまった設定・API応答イベント記録・バッチ結果・ログ転送

JSONLには3つの重要な形式条件があります。

  1. 文字コードはUTF-8でBOMなし。BOMは先頭に付く特殊な識別バイトです。

  2. 1行それ自体が有効なJSON値。空行は有効なJSONではありません。

  3. 改行文字でレコードを区切る。LFが基本で、CRLFも扱えます。

これはJSON Linesの形式説明に記載されています。拡張子は通常 .jsonl を使います。

まず試す:ダミーイベント2件を追記する

管理者権限や外部サービスは不要です。PowerShell 7以降を開き、以下のコードを新しいコンソールで実行してください。新しい一時ディレクトリだけを使うため、既存のログファイルは変更しません。

$ErrorActionPreference = 'Stop'

# 毎回異なる一時フォルダーを作り、既存ファイルを触らない

$work = Join-Path ([IO.Path]::GetTempPath()) (
    'jsonl-try-' + [guid]::NewGuid().ToString('N')
)
New-Item -ItemType Directory -Path $work | Out-Null
$file = Join-Path $work 'events.jsonl'

try {
    $events = @(
        [ordered]@{ event_id='evt-001'; status='START'; message='開始' }
        [ordered]@{ event_id='evt-002'; status='DONE';  message='完了' }
    )

    foreach ($event in $events) {

        # -Compressで「1イベント=1行」にする

        $json = ConvertTo-Json -InputObject $event -Compress -Depth 5
        Add-Content -LiteralPath $file -Value $json -Encoding utf8NoBOM
    }

    # 壊れた3行目を、教材用にだけ追加

    Add-Content -LiteralPath $file -Value '{broken json' -Encoding utf8NoBOM

    $valid = 0
    $invalid = 0
    $lineNo = 0

    foreach ($line in Get-Content -LiteralPath $file -Encoding utf8) {
        $lineNo++
        try {
            $record = ConvertFrom-Json -InputObject $line -ErrorAction Stop
            if ($null -eq $record -or
                [string]::IsNullOrWhiteSpace([string]$record.event_id)) {
                throw 'event_id is missing'
            }
            $valid++
            Write-Output ("[OK] {0} {1}" -f $record.event_id, $record.status)
        }
        catch {
            $invalid++
            Write-Warning ("line {0}: invalid JSON/record" -f $lineNo)
        }
    }

    Write-Output ("[RESULT] valid={0} invalid={1}" -f $valid, $invalid)
}
finally {

    # この実行で作成したフォルダーだけを削除する

    if (Test-Path -LiteralPath $work) {
        Remove-Item -LiteralPath $work -Recurse -Force
    }
}

想定される表示例

[OK] evt-001 START
[OK] evt-002 DONE
WARNING: line 3: invalid JSON/record
[RESULT] valid=2 invalid=1

警告の表示色や先頭文字はホストによって異なります。見てほしいのは、3行目が壊れていても、1~2行目のイベントは読み取れているという点です。

このコードは「壊れた行を直す」のではありません。正常な行と異常な行を区別する例です。運用システムでは、不正行を無言で破棄せず、別のエラー記録や隔離ファイルに残す判断も必要になります。

コードを分解すると何をしている?

ConvertTo-Json -Compress:1行に収める

PowerShellのハッシュテーブルを、JSON文字列へ変換します。

$data = [ordered]@{
    event_id = 'evt-003'
    status   = 'WARN'
    message  = "1行目" + [Environment]::NewLine + "2行目"
}
$data | ConvertTo-Json -Compress -Depth 5

-Compressは見た目の改行や余分な空白を省きます。値の中の改行はJSON文字列内でエスケープされるため、messageに改行が含まれていても1レコードを1行に収めやすいのが利点です。

-Depthはネストしたオブジェクトを何階層まで変換するかを指定するものです。深い構造を扱う場合、単に数字を大きくするのではなく、変換後のJSONに必要なプロパティが残っているか確認してください。Microsoft LearnのConvertTo-Jsonが仕様の根拠です。

Add-Content:上書きではなく追記する

Add-Content -LiteralPath $file -Value $json -Encoding utf8NoBOM

Set-Contentは既存内容の置換に使いますが、Add-Contentは末尾への追記に使います。-LiteralPathを指定すると、ファイル名の記号をワイルドカードとして解釈しません。

PowerShell 7で utf8NoBOM を明示する理由は、JSONLの形式要件と文字コードを一致させるためです。Windows PowerShell 5.1は文字コードの扱いが異なるので、このままのサンプルの対象外とします。文字コード差の詳細はMicrosoft Learnの文字エンコーディング解説を参照してください。

Get-ContentとConvertFrom-Json:1行ずつ解析する

foreach ($line in Get-Content -LiteralPath $file -Encoding utf8) {
    $record = ConvertFrom-Json -InputObject $line -ErrorAction Stop
    $record.event_id
}

正常なファイルならこれで各イベントIDを取り出せます。ただし上の短縮例は不正行があると途中で停止するため、実務では最初のサンプルのように1行単位の try/catch を使います。

PowerShellのGet-ContentとConvertFrom-Jsonの仕様を合わせて確認できます。

1か所変えて結果の違いを見る

最初のコードにある、不正な行を追加する次の1行をコメントアウトします。

# Add-Content -LiteralPath $file -Value '{broken json' -Encoding utf8NoBOM

次に再実行したときの期待結果は、[RESULT] valid=2 invalid=0 です。

比較で分かることは、JSONL自体が壊れたデータを自動修復するわけではなく、読込側の実装によって障害範囲を1行に限定できるということです。

空行やスキーマ違反にも注意

JSONLでは空行も不正なレコードです。ただし ConvertFrom-Json は空文字列を渡された際に出力しない場合があります。厳格なJSONL検証では、解析前に空白行を明示的に弾いてください。

また、JSONとして正しくても、イベントに必須の event_id がなければ、業務上は不完全です。今回のコードは event_id の存在を最低限確認しますが、status の許容値、時刻、イベントIDの重複までは検査していません。

仕事で使うなら:バッチ実行履歴を残す

例えばCSV集計の自動処理で、以下を1件ずつ記録できます。

{"event_id":"job-042-start","job":"daily-report","status":"START","rows":0}
{"event_id":"job-042-end","job":"daily-report","status":"DONE","rows":145}

後から event_id や status を使って失敗した処理を抽出できます。人が読むログであると同時に、別のツールが順次取り込める形式なのが強みです。

ただし、1つのファイルを複数のPowerShellプロセスが同時に書く場合、行単位の不可分な書込みまではこのサンプルでは保証しません。排他制御、ファイルロック、イベントIDによる重複排除、ディスク満杯や途中終了への対応が別途必要です。JSONLは簡便な保存形式であって、データベースのトランザクションの代用品ではありません。

実務上の課題対応を考える方向
重複実行イベントID・実行IDを持たせて再取込時に重複除外
書込み途中で停止末尾行の破損検知、再実行時の扱いを定義
複数プロセス書込み書込み担当を1つに集約、または排他処理
ファイル肥大化日付・サイズでローテーションし、古いログを退避
秘密情報の混入トークン・氏名・本文データを保存しない設計
改ざん対策アクセス権、転送先の保護、必要なら改ざん検知

完全版サンプル

本文のコードは動きを観察するための短い版です。GitHubの教材版には、-KeepFiles で保存結果を確認する機能と、空行・不正行の扱い、後始末を入れています。

サンプルは実装済み、PowerShell実機での実行確認は未実施です。業務の本番ログを対象にする前に、まずダミーデータで動きと出力を確かめてください。

公式情報・形式仕様

文書情報

記事タイトル
PowerShellでJSONLログを1行ずつ追記・検査する ― 壊れた行を切り分ける方法
作成日
更新日
Source URL
https://papanda925.com/?p=18061

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

タイトルとURLをコピーしました