PowerShellでログファイルからエラーが発生している時間帯を特定する方法を現在の環境で使うなら「error時間帯の特定は、error行を抽出する前にlogのtimestamp形式、timezone、複数行eventを確認し、TryParseExactで時刻を検証して一定bucketへ集計します。parse失敗行を捨てず別に数えます。」が基本方針です。対象はlog形式ごとにtimestamp patternとtimezoneを定義し、先頭tokenを時刻と決め打ちしないケースです。原logを変えない抽出から始め、生成CSVの検証項目と破棄条件を先に決めます。
timestamp書式と明示offsetを最初に固定する
ログの障害時間帯を絞るときは、タイムスタンプ形式、タイムゾーン、夏時間、複数行イベントの境界を最初に確定します。代表行をDateTimeOffsetとして解析し、解析不能行を別に数えます。UTCへ正規化した時刻を一定間隔で集計し、元行の前後関係と件数を再確認してピーク時間帯を特定します。
- Get-Itemでsizeと更新時刻、Get-Content -TotalCountで形式を確認する
- timestampのformat、culture、timezone、日付跨ぎを特定する
- ERRORがlevel欄かmessage中の文字かをschemaで区別する
- rotation fileを含む期間と重複の扱いを決める
原logを変えず候補行だけ抽出する
log分析は読み取りですが、全文にはcredential、query、個人情報が含まれ得ます。作業copyを限定ACLで作り、抽出結果は時刻・event IDなど必要最小限にします。元logを整形やmaskで上書きしません。script変更はsample copyで試し、parse率と件数が合格してから本番read-only分析へ使います。失敗時は生成CSVだけを破棄し、原logを保持します。
malformed行をLineNumber付きで隔離する
- Splitの先頭を無条件にtimestampとする
- local timeとUTCを混在させる
- parse失敗行を黙って捨てる
- ERROR文字列をseverityと断定する
- rotation重複と複数行eventを見落とす
TryParseExact後のUTCだけをbucket化する
候補行とline numberを読む
Select-String -LiteralPath 'C:\Logs\app.log' -SimpleMatch 'ERROR' |
Select-Object Path,LineNumber,Line | Select-Object -First 20
まず少量を確認し、ERROR文字列が本当にseverityを表すかを見ます。機密値を含む行は共有しません。
ISO 8601 timestampを厳密parse
$culture=[Globalization.CultureInfo]::InvariantCulture
$format="yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK"
$rows=Select-String -LiteralPath '.\app.log' -Pattern '^\d{4}-\d{2}-\d{2}T.*\sERROR\s'
$parsed=[Collections.Generic.List[object]]::new()
$rejected=[Collections.Generic.List[object]]::new()
foreach($row in $rows){
$token=($row.Line -split '\s+',2)[0]
$dt=[datetimeoffset]::MinValue
$hasExplicitOffset=$token -match '(?:Z|[+-]\d{2}:\d{2})$'
if($hasExplicitOffset -and [datetimeoffset]::TryParseExact($token,$format,$culture,[Globalization.DateTimeStyles]::None,[ref]$dt)){
$parsed.Add([pscustomobject]@{Time=$dt;LineNumber=$row.LineNumber;Token=$token})
} else {
$rejected.Add([pscustomobject]@{LineNumber=$row.LineNumber;Token=$token;Reason='FormatOrOffset'})
}
}
[pscustomobject]@{Candidates=$rows.Count;Accepted=$parsed.Count;Rejected=$rejected.Count}
ISO 8601のZまたは±hh:mm offsetを必須にし、TryParseExactへ通らないtokenはLineNumber付きのRejected一覧へ分けます。
15分bucketへ集計
$bucketed=$parsed | ForEach-Object {
$utc=$_.Time.UtcDateTime
$minute=[int]([math]::Floor($utc.Minute/15)*15)
$bucket=[datetime]::new($utc.Year,$utc.Month,$utc.Day,$utc.Hour,$minute,0,[DateTimeKind]::Utc)
[pscustomobject]@{BucketUtc=$bucket;LineNumber=$_.LineNumber}
}
$bucketed | Group-Object BucketUtc | ForEach-Object {
[pscustomobject]@{BucketUtc=$_.Group[0].BucketUtc;Count=$_.Count}
} | Sort-Object BucketUtc
UTCへ正規化し15分単位へまとめます。local業務時間へ戻す表示ではtimezone名とDSTを明記します。
最初と最後のerrorを確認
$ordered=$parsed | Sort-Object Time
$ordered | Select-Object -First 1
$ordered | Select-Object -Last 1
単一clusterと断定せず、bucket間のgapや複数peakを確認します。
CSVへ最小項目を保存
$parsed | Select-Object Time,LineNumber,Token | Export-Csv '.\error-times-accepted.csv' -NoTypeInformation -Encoding utf8
$rejected | Export-Csv '.\error-times-rejected.csv' -NoTypeInformation -Encoding utf8
message本文を出さず時刻と行番号だけにします。原logはread-onlyのまま、出力は新規fileです。
文字列名ではなくDateTimeで時系列sortする
エラー行の最小・最大時刻だけでは連続障害を意味しません。bucket count、連続gap、deploymentやrestart時刻との相関を見ます。複数行stack traceは先頭行だけにtimestampがある場合があり、一行ずつ独立eventとして扱うと誤集計します。rotationで同じeventが重複するか、clock skewがあるかも確認します。
parse成功数と拒否数を候補総数へ照合する
total error候補、parse成功、parse失敗、bucket合計が一致することを確認します。既知の5行を手計算し、UTC変換とbucket境界を照合します。Event logや監視alert、deployment記録と同時刻を比較し、相関を原因と断定せず追加evidenceを集めます。
UTC範囲・bucket合計・Rejected件数を検収する
ERROR行のLineNumberとtimestamp文字列を取得し、ParseExactでformat・culture・DateTimeStylesを固定してからUTCへ揃えます。parse成功/失敗を別件数にし、15分bucketの最初・最後・件数から集中時間帯を判断します。
offset必須のISO 8601を厳密parseする
$pattern='yyyy-MM-ddTHH:mm:ss.fffK'
$c=[Globalization.CultureInfo]::InvariantCulture
$dto=[DateTimeOffset]::ParseExact('2026-07-17T12:34:56.789+09:00',$pattern,$c)
$utc=$dto.UtcDateTime
$bucket=Get-Date -Date $utc -Minute ([math]::Floor($utc.Minute/15)*15) -Second 0 -Millisecond 0
[pscustomobject]@{Utc=$utc;Bucket=$bucket.ToString('o')}
Accepted・Rejected・read errorを分類する
- timestamp受理:AcceptedとRejectedの合計が候補行数に一致し、AcceptedのUTC順とbucket合計が再計算でも一致する
- 書式不一致を隔離:ERROR 0件は正常な空結果として検索期間とfile hashを記録する
- log読取・集計error:timestamp欠落、DST曖昧、encoding error、read errorはRejected行としてLineNumber付きで残す
Get-Dateへの暗黙parseはlocaleで月日を逆に解釈し得ます。文字列sortを時刻順とせず、offsetなしlocal時刻はRejectedへ送り、別途timezone mappingを合意しない限り補完しません。複数logのclock skewとrotation重複を無視しません。
offsetなし・DST境界・malformedを試す
try { [DateTimeOffset]::ParseExact('17/07/2026 12:00','yyyy-MM-ddTHH:mm:ssK',[Globalization.CultureInfo]::InvariantCulture) } catch { [pscustomobject]@{State='Rejected';Value='17/07/2026 12:00'} }
offset有無、DST重複時刻、millisecond欠落、malformed、rotation境界をtestします。候補数、parse成功/失敗、最初/最後UTC、bucket合計、source hashを保存します。
候補行数はAcceptedとRejectedの合計へ一致させます。offsetなし時刻を端末localとして補完せずRejectedへ送り、bucketはUTCのDateTime propertyでsortしてlocale表示の順序へ依存しません。

コメント