[備忘録] PowerShellパイプラインでログファイル処理を効率化！シンプルに実装するエラー抽出術

PowerShellのパイプラインを使って、ログファイルからエラーを抽出・整形する方法を一気通貫で解説します。
備忘録としてまとめました。

![powershell-pipeline-diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/f4165b3e-c89a-4749-baee-72e785c45e38.png)

## 想定読者
- PowerShellの基本コマンドは理解している方
- ログファイルなどのテキスト処理を効率化したい方
- 「パイプライン」の可能性をもっと知りたい方

## 環境準備

この記事のサンプルはPowerShell 5.1以上で動作確認しています。まずは環境を確認しましょう。

```powershell
# バージョン確認
$PSVersionTable.PSVersion

# 作業用フォルダを作成（存在しない場合）
$workDir = "C:\Temp\PowerShellLogDemo"
if (-not (Test-Path $workDir)) {
    New-Item -Path $workDir -ItemType Directory
}
Set-Location $workDir

# 既存のデモファイルがあれば削除
if (Test-Path ".\sample_log.txt") {
    Remove-Item ".\sample_log.txt"
}
```

## 実践！一気通貫ログ分析パイプライン

### STEP 1: サンプルログファイルの作成

まずは分析対象のサンプルログファイルを作成します。実際の環境では既存のログファイルを使いますが、ここではデモ用のファイルを生成します。

```powershell
# テスト用のログファイルを作成
"2023-05-01 10:15:23 INFO システム起動しました" | Out-File -FilePath .\sample_log.txt
"2023-05-01 10:16:45 ERROR 接続に失敗しました" | Out-File -FilePath .\sample_log.txt -Append
"2023-05-01 10:18:12 WARN リソース使用率が高くなっています" | Out-File -FilePath .\sample_log.txt -Append
"2023-05-01 10:22:30 INFO バックアップ処理を開始します" | Out-File -FilePath .\sample_log.txt -Append
"2023-05-01 10:30:15 ERROR データベース接続がタイムアウトしました" | Out-File -FilePath .\sample_log.txt -Append
"2023-05-01 10:35:42 INFO バックアップ処理が完了しました" | Out-File -FilePath .\sample_log.txt -Append
"2023-05-01 10:40:18 WARN メモリ使用率が80%を超えています" | Out-File -FilePath .\sample_log.txt -Append
"2023-05-01 10:42:53 ERROR 処理がタイムアウトしました: ID=12345" | Out-File -FilePath .\sample_log.txt -Append

# 作成したログファイルの内容を確認
Write-Host "作成したログファイルの内容:" -ForegroundColor Green
Get-Content .\sample_log.txt
```

**実行結果:**
```
作成したログファイルの内容:
2023-05-01 10:15:23 INFO システム起動しました
2023-05-01 10:16:45 ERROR 接続に失敗しました
2023-05-01 10:18:12 WARN リソース使用率が高くなっています
2023-05-01 10:22:30 INFO バックアップ処理を開始します
2023-05-01 10:30:15 ERROR データベース接続がタイムアウトしました
2023-05-01 10:35:42 INFO バックアップ処理が完了しました
2023-05-01 10:40:18 WARN メモリ使用率が80%を超えています
2023-05-01 10:42:53 ERROR 処理がタイムアウトしました: ID=12345
```

### STEP 2: エラーログの抽出（基本）

まずは最も基本的な処理として、エラーログだけを抽出してみましょう。

```powershell
Write-Host "`nステップ2: エラーログの抽出（基本）" -ForegroundColor Green
Get-Content .\sample_log.txt | Where-Object { $_ -match "ERROR" }
```

**実行結果:**
```
ステップ2: エラーログの抽出（基本）
2023-05-01 10:16:45 ERROR 接続に失敗しました
2023-05-01 10:30:15 ERROR データベース接続がタイムアウトしました
2023-05-01 10:42:53 ERROR 処理がタイムアウトしました: ID=12345
```

### STEP 3: エラーログを整形して表示

続いて、エラーログから必要な情報だけを抽出し、見やすい形に整形します。

```powershell
Write-Host "`nステップ3: エラーログを整形して表示" -ForegroundColor Green
Get-Content .\sample_log.txt | 
    Where-Object { $_ -match "ERROR" } | 
    ForEach-Object {
        $parts = $_ -split " ", 4
        [PSCustomObject]@{
            Time = $parts[1]
            Message = $parts[3]
        }
    } | 
    Format-Table -AutoSize
```

**実行結果:**
```
ステップ3: エラーログを整形して表示

Time     Message
----     -------
10:16:45 接続に失敗しました
10:30:15 データベース接続がタイムアウトしました
10:42:53 処理がタイムアウトしました: ID=12345
```

### STEP 4: 正規表現を使った高度な抽出

正規表現のキャプチャグループを使って、より柔軟にデータを抽出します。

```powershell
Write-Host "`nステップ4: 正規表現を使った高度な抽出" -ForegroundColor Green
Get-Content .\sample_log.txt | 
    ForEach-Object {
        if ($_ -match "^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (INFO|WARN|ERROR) (.+)") {
            [PSCustomObject]@{
                Date = $matches[1]
                Time = $matches[2]
                Level = $matches[3]
                Message = $matches[4]
                Severity = switch ($matches[3]) {
                    "INFO" { 1 }
                    "WARN" { 2 }
                    "ERROR" { 3 }
                }
            }
        }
    } | 
    Format-Table -AutoSize
```

**実行結果:**
```
ステップ4: 正規表現を使った高度な抽出

Date       Time     Level Message                               Severity
----       ----     ----- -------                               --------
2023-05-01 10:15:23 INFO  システム起動しました                          1
2023-05-01 10:16:45 ERROR 接続に失敗しました                            3
2023-05-01 10:18:12 WARN  リソース使用率が高くなっています                   2
2023-05-01 10:22:30 INFO  バックアップ処理を開始します                      1
2023-05-01 10:30:15 ERROR データベース接続がタイムアウトしました                3
2023-05-01 10:35:42 INFO  バックアップ処理が完了しました                     1
2023-05-01 10:40:18 WARN  メモリ使用率が80%を超えています                   2
2023-05-01 10:42:53 ERROR 処理がタイムアウトしました: ID=12345              3
```

### STEP 5: レベル別にグループ化して統計情報を表示

各ログレベル（INFO/WARN/ERROR）ごとの出現回数を集計します。

```powershell
Write-Host "`nステップ5: レベル別にグループ化して統計情報を表示" -ForegroundColor Green
Get-Content .\sample_log.txt | 
    ForEach-Object {
        if ($_ -match "^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (INFO|WARN|ERROR) (.+)") {
            [PSCustomObject]@{
                Level = $matches[3]
                Message = $matches[4]
            }
        }
    } | 
    Group-Object -Property Level | 
    Select-Object Name, Count, @{
        Name = 'Percentage'; 
        Expression = { "{0:P1}" -f ($_.Count / 8) }  # 合計ログ数に対する割合
    }
```

**実行結果:**
```
ステップ5: レベル別にグループ化して統計情報を表示

Name  Count Percentage
----  ----- ----------
INFO  3     37.5%
WARN  2     25.0%
ERROR 3     37.5%
```

### STEP 6: 特定の時間帯のログだけを抽出

時間帯で絞り込んでログを抽出します。10:30〜10:45の間のログだけを表示してみましょう。

```powershell
Write-Host "`nステップ6: 特定の時間帯のログだけを抽出" -ForegroundColor Green
Get-Content .\sample_log.txt | 
    ForEach-Object {
        if ($_ -match "^(\d{4}-\d{2}-\d{2}) (10:[3-4]\d:\d{2}) (INFO|WARN|ERROR) (.+)") {
            [PSCustomObject]@{
                Time = $matches[2]
                Level = $matches[3]
                Message = $matches[4]
            }
        }
    } | 
    Format-Table -AutoSize
```

**実行結果:**
```
ステップ6: 特定の時間帯のログだけを抽出

Time     Level Message
----     ----- -------
10:30:15 ERROR データベース接続がタイムアウトしました
10:35:42 INFO  バックアップ処理が完了しました
10:40:18 WARN  メモリ使用率が80%を超えています
10:42:53 ERROR 処理がタイムアウトしました: ID=12345
```

### STEP 7: 特定のキーワードを含むログを検索

ログメッセージ内の特定のキーワードを検索します。ここでは「タイムアウト」というキーワードを含むログを抽出します。

```powershell
Write-Host "`nステップ7: 特定のキーワードを含むログを検索" -ForegroundColor Green
Get-Content .\sample_log.txt | 
    Where-Object { $_ -match "タイムアウト" } |
    ForEach-Object {
        if ($_ -match "^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (INFO|WARN|ERROR) (.+)") {
            [PSCustomObject]@{
                Time = $matches[2]
                Level = $matches[3]
                Message = $matches[4]
            }
        }
    } | 
    Format-Table -AutoSize
```

**実行結果:**
```
ステップ7: 特定のキーワードを含むログを検索

Time     Level Message
----     ----- -------
10:30:15 ERROR データベース接続がタイムアウトしました
10:42:53 ERROR 処理がタイムアウトしました: ID=12345
```

### STEP 8: CSVファイルに出力

分析結果をCSVファイルに保存します。これにより、Excelなどで詳細な分析や共有が容易になります。

```powershell
Write-Host "`nステップ8: CSVファイルに出力" -ForegroundColor Green
Get-Content .\sample_log.txt | 
    ForEach-Object {
        if ($_ -match "^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (INFO|WARN|ERROR) (.+)") {
            [PSCustomObject]@{
                Date = $matches[1]
                Time = $matches[2]
                Level = $matches[3]
                Message = $matches[4]
            }
        }
    } | 
    Export-Csv -Path ".\log_analysis.csv" -NoTypeInformation -Encoding UTF8

# CSVファイルが作成されたことを確認
Get-Item ".\log_analysis.csv" | Select-Object Name, Length, LastWriteTime

# CSVファイルの内容を表示
Import-Csv ".\log_analysis.csv" | Format-Table -AutoSize
```

**実行結果:**
```
ステップ8: CSVファイルに出力

Name           Length LastWriteTime
----           ------ -------------
log_analysis.csv   422 2023-05-01 11:00:00

Date       Time     Level Message
----       ----     ----- -------
2023-05-01 10:15:23 INFO  システム起動しました
2023-05-01 10:16:45 ERROR 接続に失敗しました
2023-05-01 10:18:12 WARN  リソース使用率が高くなっています
2023-05-01 10:22:30 INFO  バックアップ処理を開始します
2023-05-01 10:30:15 ERROR データベース接続がタイムアウトしました
2023-05-01 10:35:42 INFO  バックアップ処理が完了しました
2023-05-01 10:40:18 WARN  メモリ使用率が80%を超えています
2023-05-01 10:42:53 ERROR 処理がタイムアウトしました: ID=12345
```

### STEP 9: 複数条件でフィルタリング

ERRORログかつ特定の時間帯のみを抽出する、といった複数条件でのフィルタリングを行います。

```powershell
Write-Host "`nステップ9: 複数条件でフィルタリング" -ForegroundColor Green
Get-Content .\sample_log.txt | 
    ForEach-Object {
        if ($_ -match "^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (INFO|WARN|ERROR) (.+)") {
            [PSCustomObject]@{
                Time = $matches[2]
                Level = $matches[3]
                Message = $matches[4]
            }
        }
    } | 
    Where-Object { $_.Level -eq "ERROR" -and $_.Time -ge "10:30:00" } |
    Format-Table -AutoSize
```

**実行結果:**
```
ステップ9: 複数条件でフィルタリング

Time     Level Message
----     ----- -------
10:30:15 ERROR データベース接続がタイムアウトしました
10:42:53 ERROR 処理がタイムアウトしました: ID=12345
```

### STEP 10: 実用的な関数化

これまでの処理を再利用可能な関数にまとめます。

```powershell
Write-Host "`nステップ10: 実用的な関数化" -ForegroundColor Green

function Get-FilteredLog {
    param(
        [Parameter(Mandatory = $true)]
        [string]$LogPath,
        
        [Parameter(Mandatory = $false)]
        [ValidateSet("INFO", "WARN", "ERROR", "ALL")]
        [string]$Level = "ALL",
        
        [Parameter(Mandatory = $false)]
        [string]$StartTime,
        
        [Parameter(Mandatory = $false)]
        [string]$EndTime,
        
        [Parameter(Mandatory = $false)]
        [string]$Keyword,
        
        [Parameter(Mandatory = $false)]
        [switch]$ExportToCsv,
        
        [Parameter(Mandatory = $false)]
        [string]$CsvPath = ".\filtered_log.csv"
    )
    
    # ログを取得して解析
    $logEntries = Get-Content $LogPath | 
        ForEach-Object {
            if ($_ -match "^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) (INFO|WARN|ERROR) (.+)") {
                [PSCustomObject]@{
                    Date = $matches[1]
                    Time = $matches[2]
                    Level = $matches[3]
                    Message = $matches[4]
                }
            }
        }
    
    # フィルタリング
    $filteredLogs = $logEntries
    
    # レベルでフィルタリング
    if ($Level -ne "ALL") {
        $filteredLogs = $filteredLogs | Where-Object { $_.Level -eq $Level }
    }
    
    # 開始時間でフィルタリング
    if ($StartTime) {
        $filteredLogs = $filteredLogs | Where-Object { $_.Time -ge $StartTime }
    }
    
    # 終了時間でフィルタリング
    if ($EndTime) {
        $filteredLogs = $filteredLogs | Where-Object { $_.Time -le $EndTime }
    }
    
    # キーワードでフィルタリング
    if ($Keyword) {
        $filteredLogs = $filteredLogs | Where-Object { $_.Message -match $Keyword }
    }
    
    # CSVに出力
    if ($ExportToCsv) {
        $filteredLogs | Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8
        Write-Host "CSVファイルを保存しました: $CsvPath" -ForegroundColor Yellow
    }
    
    return $filteredLogs
}

# 関数の使用例1: ERRORログのみ表示
Write-Host "`n関数の使用例1: ERRORログのみ表示" -ForegroundColor Yellow
Get-FilteredLog -LogPath ".\sample_log.txt" -Level "ERROR" | Format-Table -AutoSize

# 関数の使用例2: 特定の時間帯の全ログ
Write-Host "`n関数の使用例2: 特定の時間帯の全ログ" -ForegroundColor Yellow
Get-FilteredLog -LogPath ".\sample_log.txt" -StartTime "10:30:00" -EndTime "10:40:00" | Format-Table -AutoSize

# 関数の使用例3: キーワード検索とCSV出力
Write-Host "`n関数の使用例3: キーワード検索とCSV出力" -ForegroundColor Yellow
Get-FilteredLog -LogPath ".\sample_log.txt" -Keyword "処理" -ExportToCsv -CsvPath ".\process_logs.csv" | Format-Table -AutoSize

# CSV出力を確認
if (Test-Path ".\process_logs.csv") {
    Write-Host "`nCSV出力の確認:" -ForegroundColor Green
    Import-Csv ".\process_logs.csv" | Format-Table -AutoSize
}
```

**実行結果:**
```
ステップ10: 実用的な関数化

関数の使用例1: ERRORログのみ表示

Date       Time     Level Message
----       ----     ----- -------
2023-05-01 10:16:45 ERROR 接続に失敗しました
2023-05-01 10:30:15 ERROR データベース接続がタイムアウトしました
2023-05-01 10:42:53 ERROR 処理がタイムアウトしました: ID=12345

関数の使用例2: 特定の時間帯の全ログ

Date       Time     Level Message
----       ----     ----- -------
2023-05-01 10:30:15 ERROR データベース接続がタイムアウトしました
2023-05-01 10:35:42 INFO  バックアップ処理が完了しました

関数の使用例3: キーワード検索とCSV出力
CSVファイルを保存しました: .\process_logs.csv

Date       Time     Level Message
----       ----     ----- -------
2023-05-01 10:22:30 INFO  バックアップ処理を開始します
2023-05-01 10:35:42 INFO  バックアップ処理が完了しました
2023-05-01 10:42:53 ERROR 処理がタイムアウトしました: ID=12345

CSV出力の確認:

Date       Time     Level Message
----       ----     ----- -------
2023-05-01 10:22:30 INFO  バックアップ処理を開始します
2023-05-01 10:35:42 INFO  バックアップ処理が完了しました
2023-05-01 10:42:53 ERROR 処理がタイムアウトしました: ID=12345
```

## まとめ

![powershell-pipeline-summary.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/a4628b93-0c34-43ed-89b7-9a4d8ed2d0d2.png)

このチュートリアルでは、PowerShellのパイプラインを使ったログ分析の手法を一気通貫で学びました。

PowerShellのパイプラインを通じて、ログの抽出や整形、正規表現によるパターンマッチ、集計処理、CSV出力、さらには関数化まで幅広く学びました。少ないコードで効率よくデータ処理ができるのが魅力です。日々のログ分析や運用業務に積極的に活用していきたいと思います。

## 参考資料
- [Microsoft PowerShell Documentation](https://docs.microsoft.com/en-us/powershell/)
- [PowerShell Gallery](https://www.powershellgallery.com/)
- [GitHub - PowerShell Community](https://github.com/powershell/powershell)

---

*この記事は2025年5月に執筆されました。PowerShellのバージョンやコマンドレットの仕様は、今後のアップデートで変更される可能性があります。*