适用于 PowerShell 5.1 及以上版本
日志分析是运维排障的核心技能。无论是 IIS 访问日志、应用程序错误日志、Windows 事件日志还是自定义的业务日志,快速定位问题需要高效的日志解析能力。PowerShell 的文本处理命令(Select-String、ConvertFrom-Csv、正则表达式)结合对象管道,可以构建强大的日志分析工具链。
本文将讲解常见的日志格式解析方法、模式匹配技巧,以及实用的日志分析脚本。
基础文本搜索
Select-String 是 PowerShell 中的 grep,支持正则表达式和文件通配符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Select-String -Path "C:\Logs\app.log" -Pattern "ERROR" | Select-Object -First 10 | ForEach-Object { $_.Line }
Select-String -Path "C:\Logs\app.log" -Pattern "Exception" -Context 2,2
Select-String -Path "C:\Logs\*.log" -Pattern "timeout|refused|reset" -Recurse
Get-ChildItem "C:\Logs" -Filter *.log | ForEach-Object { $errors = (Select-String -Path $_.FullName -Pattern "ERROR|FATAL" -Quiet).Count [PSCustomObject]@{ File = $_.Name Errors = $errors SizeMB = [math]::Round($_.Length / 1MB, 2) } } | Sort-Object Errors -Descending | Format-Table -AutoSize
|
执行结果示例:
1 2 3 4 5 6 7 8 9
| 2025-05-21 08:15:32 ERROR [DB] Connection timeout after 30s 2025-05-21 08:20:18 ERROR [API] Upstream timeout (504) - /api/users 2025-05-21 08:25:45 ERROR [AUTH] Failed to authenticate user: john.doe
File Errors SizeMB ---- ------ ------ app-2025.log 42 15.32 api-2025.log 28 8.45 auth-2025.log 12 3.21
|
解析结构化日志
许多应用使用结构化的日志格式(如 JSON Lines),PowerShell 可以轻松解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| $logEntries = Get-Content "C:\Logs\app.jsonl" | ForEach-Object { try { $_ | ConvertFrom-Json } catch { $null } } | Where-Object { $_ }
$logEntries | Where-Object { $_.level -eq 'error' } | Select-Object timestamp, message, @{N='source'; E={$_.source -join '.'}} | Format-Table -AutoSize
$startTime = (Get-Date).AddHours(-1) $recentLogs = $logEntries | Where-Object { [datetime]$_.timestamp -gt $startTime }
Write-Host "最近 1 小时日志条数:$($recentLogs.Count)"
$logEntries | Group-Object level | Select-Object Name, Count | Sort-Object Count -Descending | Format-Table -AutoSize
$logEntries | Where-Object { $_.level -eq 'error' -and $_.exception } | ForEach-Object { $exType = if ($_.exception.type) { $_.exception.type } else { $_.exception.Split("`n")[0] } [PSCustomObject]@{ Timestamp = $_.timestamp ExceptionType = $exType Message = $_.message } } | Format-Table -AutoSize -Wrap
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| timestamp message source --------- ------- ------ 2025-05-21T08:15:32.123Z Connection timeout after 30s db.pool 2025-05-21T08:20:18.456Z Upstream timeout (504) api.gateway
最近 1 小时日志条数:342
Name Count ---- ----- info 280 warn 45 error 17
Timestamp ExceptionType Message --------- -------------- ------- 2025-05-21T08:15:32Z SqlException Connection timeout 2025-05-21T08:22:15Z HttpRequestException Upstream timeout 2025-05-21T08:30:01Z AuthenticationException Invalid token
|
解析 IIS 日志
IIS 日志使用 W3C 扩展格式,是空格分隔的文本。使用 ConvertFrom-Csv 可以高效解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| function Get-IISLogEntry {
param( [Parameter(Mandatory)] [string]$LogPath,
[datetime]$StartTime, [datetime]$EndTime )
$header = Get-Content $LogPath | Where-Object { $_ -match '^#Fields:' } | Select-Object -First 1
if (-not $header) { Write-Error "无法识别的日志格式" return }
$fields = ($header -replace '^#Fields:\s*', '') -split '\s+'
$entries = Get-Content $LogPath | Where-Object { $_ -notmatch '^\#' -and $_.Trim() } | ConvertFrom-Csv -Delimiter ' ' -Header $fields
if ($StartTime -or $EndTime) { $entries = $entries | Where-Object { $ts = [datetime]"$($_.date) $($_.time)" ($StartTime -eq $null -or $ts -ge $StartTime) -and ($EndTime -eq $null -or $ts -le $EndTime) } }
return $entries }
$iisLogs = Get-IISLogEntry -LogPath "C:\inetpub\logs\LogFiles\W3SVC1\ex250521.log" ` -StartTime (Get-Date).AddHours(-1)
$iisLogs | Group-Object 'sc-status' | Select-Object Name, Count | Sort-Object Count -Descending | Format-Table -AutoSize
$iisLogs | Group-Object 'cs-uri-stem' | Sort-Object Count -Descending | Select-Object -First 10 Count, Name | Format-Table -AutoSize
$iisLogs | Where-Object { $_.'time-taken' -gt 5000 } | Select-Object date, time, 'cs-uri-stem', 'sc-status', 'time-taken' | Sort-Object 'time-taken' -Descending | Format-Table -AutoSize
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Name Count ---- ----- 200 8456 304 1234 404 456 500 123
Count Name ----- ---- 2345 /api/users 1876 /api/products 987 /static/css/main.css 654 /api/orders
date time cs-uri-stem sc-status time-taken ---- ---- ----------- ---------- ---------- 2025-05-21 08:15:32 /api/search 200 12456 2025-05-21 08:22:18 /api/reports 200 8734 2025-05-21 08:35:45 /api/export 200 6234
|
正则表达式提取模式
对于非结构化的文本日志,正则表达式是提取关键信息的利器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| $logContent = Get-Content "C:\Logs\access.log" -Tail 10000
$ipPattern = '\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b' $ips = $logContent | Select-String -Pattern $ipPattern | ForEach-Object { $_.Matches.Groups[1].Value }
$ips | Group-Object | Sort-Object Count -Descending | Select-Object -First 20 Count, Name | Format-Table -AutoSize
$errorPattern = '(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*?ERROR.*?\[(\w+)\]\s+(.+)' $errorEntries = $logContent | Select-String -Pattern $errorPattern | ForEach-Object { [PSCustomObject]@{ Timestamp = $_.Matches.Groups[1].Value Module = $_.Matches.Groups[2].Value Message = $_.Matches.Groups[3].Value } }
$errorEntries | Group-Object Module | Select-Object Name, Count | Sort-Object Count -Descending | Format-Table -AutoSize
$httpPattern = '"\s(\d{3})\s(\d+)\s(\d+)' $httpStats = $logContent | Select-String -Pattern $httpPattern | ForEach-Object { [PSCustomObject]@{ StatusCode = $_.Matches.Groups[1].Value Size = $_.Matches.Groups[2].Value Duration = $_.Matches.Groups[3].Value } }
$httpStats | Where-Object { $_.StatusCode -match '^[45]' } | Group-Object StatusCode | Format-Table Name, Count -AutoSize
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Count Name
1245 192.168.1.100 876 10.0.0.55 432 172.16.0.10 234 192.168.1.101
Name Count
DB 15 API 10 AUTH 8 CACHE 4
Name Count
500 89 404 45 503 12
|
实时日志监控
对于正在运行的服务,实时监控日志输出可以快速发现问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| function Watch-LogFile {
param( [Parameter(Mandatory)] [string]$Path,
[string[]]$Pattern = @('ERROR', 'FATAL', 'WARN'),
[int]$MaxLines = 1000 )
$patternRegex = $Pattern -join '|'
Write-Host "监控中:$Path" -ForegroundColor Cyan Write-Host "过滤规则:$patternRegex" -ForegroundColor DarkGray Write-Host "按 Ctrl+C 停止`n" -ForegroundColor DarkGray
Get-Content $Path -Tail 50 | Where-Object { $_ -match $patternRegex } | ForEach-Object { $level = if ($_ -match 'ERROR|FATAL') { 'Error' } else { 'Warning' } $color = if ($level -eq 'Error') { 'Red' } else { 'Yellow' } Write-Host $_ -ForegroundColor $color }
Get-Content $Path -Wait -Tail 0 | Where-Object { $_ -match $patternRegex } | ForEach-Object { $level = if ($_ -match 'ERROR|FATAL') { 'Error' } else { 'Warning' } $color = if ($level -eq 'Error') { 'Red' } else { 'Yellow' } $timestamp = Get-Date -Format 'HH:mm:ss' Write-Host "[$timestamp] $_" -ForegroundColor $color } }
Watch-LogFile -Path "C:\Logs\app.log" -Pattern @('ERROR', 'FATAL', 'WARN', 'Exception')
|
执行结果示例:
1 2 3 4 5 6 7
| 监控中:C:\Logs\app.log 过滤规则:ERROR|FATAL|WARN|Exception 按 Ctrl+C 停止
2025-05-21 08:15:32 ERROR [DB] Connection timeout after 30s 2025-05-21 08:20:18 WARN [CACHE] Cache miss rate > 50% [08:25:45] 2025-05-21 08:25:45 ERROR [API] Upstream timeout (504)
|
注意事项
- 大文件处理:对于 GB 级别的日志文件,使用
Get-Content -ReadCount 10000 分批读取,或使用 Select-String 直接搜索避免全部加载到内存
- 编码问题:日志文件可能使用不同编码(UTF-8、GBK、UTF-16),使用
Get-Content -Encoding 指定正确编码
- 正则性能:复杂正则表达式在大文件上可能很慢,先用简单模式缩小范围再精细匹配
- 时间格式:不同应用的日志时间格式各异(ISO 8601、自定义格式),解析时注意时区转换
- 日志轮转:生产环境日志通常会轮转压缩(如
app.log.2025-05-20.gz),分析时需要考虑跨文件查询
- 结构化日志:新项目建议使用 JSON Lines 或类似的结构化日志格式,解析效率远高于正则提取