IIS日志文件记录了每一次对Web服务器的请求,但原始数据量巨大,靠肉眼逐行查看根本不现实。面对几百兆甚至几个G的日志,你需要的是一个能快速筛选、统计、定位问题的分析脚本。下面直接给出几个拿来就能用的脚本方案,覆盖了从基础统计到安全分析的核心场景。
基础统计分析脚本:快速掌握站点健康度最常用的需求是统计状态码分布、请求耗时、独立IP数和流量。下面这个PowerShell脚本能一次性输出这些关键指标。把它保存为 iis-log-basic.ps1,放在日志目录下运行即可。
param(
[string]$LogPath = ".\u_ex*.log"
)
$logs = Get-Content $LogPath
$totalRequests = $logs.Count - 4
$statusCodes = @{}
$ipList = @{}
$totalBytes = 0
$timeTakenSum = 0
$timeTakenCount = 0
for ($i = 4; $i -lt $logs.Count; $i++) {
$line = $logs[$i]
if ([string]::IsNullOrWhiteSpace($line)) { continue }
$fields = $line -split ' '
if ($fields.Count -lt 15) { continue }
$statusCode = $fields[11]
$clientIP = $fields[8]
$bytesSent = 0
[int]::TryParse($fields[12], [ref]$bytesSent)
$timeTaken = 0
[int]::TryParse($fields[13], [ref]$timeTaken)
if ($statusCodes.ContainsKey($statusCode)) {
$statusCodes[$statusCode]++
} else {
$statusCodes[$statusCode] = 1
}
if ($ipList.ContainsKey($clientIP)) {
$ipList[$clientIP]++
} else {
$ipList[$clientIP] = 1
}
$totalBytes += $bytesSent
$timeTakenSum += $timeTaken
$timeTakenCount++
}
Write-Host "=== IIS 日志基础统计 ===" -ForegroundColor Cyan
Write-Host "总请求数: $totalRequests" -ForegroundColor Yellow
Write-Host "`n状态码分布:" -ForegroundColor Green
$statusCodes.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object {
$color = if ([int]$_.Key -ge 400) { "Red" } elseif ([int]$_.Key -ge 300) { "Yellow" } else { "White" }
Write-Host " HTTP $($_.Key): $($_.Value) 次" -ForegroundColor $color
}
Write-Host "`nTop 10 访问IP:" -ForegroundColor Green
$ipList.GetEnumerator() | Sort-Object Value -Descending | Select-Object -First 10 | ForEach-Object {
Write-Host " $($_.Key) -> $($_.Value) 次"
}
if ($timeTakenCount -gt 0) {
$avgTime = [math]::Round($timeTakenSum / $timeTakenCount, 2)
Write-Host "`n平均响应时间: ${avgTime}ms" -ForegroundColor Yellow
}
$totalMB = [math]::Round($totalBytes / 1MB, 2)
Write-Host "总流量: ${totalMB} MB" -ForegroundColor Yellow
这个脚本直接解析IIS默认的W3C格式日志,跳过前四行注释头,从第五行开始逐行拆分字段。字段索引根据IIS默认配置来定,如果你的日志字段顺序不同,需要调整 fields[8]、fields[11] 这些索引值。运行后你会立刻看到站点的整体画像:哪些状态码占比异常、哪些IP在大量请求、平均响应时间是否恶化。
慢请求定位脚本:找出性能瓶颈用户投诉页面打开慢,你需要从日志里揪出耗时最长的那些请求。下面的脚本专门提取响应时间超过指定阈值的记录,并按耗时降序排列,同时解析出对应的URL和来源IP。
param(
[string]$LogPath = ".\u_ex*.log",
[int]$Threshold = 5000
)
$logs = Get-Content $LogPath
$slowRequests = @()
for ($i = 4; $i -lt $logs.Count; $i++) {
$line = $logs[$i]
if ([string]::IsNullOrWhiteSpace($line)) { continue }
$fields = $line -split ' '
if ($fields.Count -lt 15) { continue }
$timeTaken = 0
[int]::TryParse($fields[13], [ref]$timeTaken)
if ($timeTaken -gt $Threshold) {
$slowRequests += [PSCustomObject]@{
Time = "$($fields[0]) $($fields[1])"
IP = $fields[8]
Method = $fields[3]
URI = $fields[4]
Query = $fields[5]
StatusCode = $fields[11]
TimeTaken = $timeTaken
}
}
}
Write-Host "响应时间超过 ${Threshold}ms 的请求共 $($slowRequests.Count) 条" -ForegroundColor Cyan
$slowRequests | Sort-Object TimeTaken -Descending | Select-Object -First 20 | Format-Table -AutoSize
阈值设成5000毫秒是个经验起点,你可以根据业务调整。如果输出结果集中在某几个动态页面或API接口上,那性能瓶颈大概率就在后端代码或数据库查询上。如果连静态资源都慢,就要检查带宽、磁盘I/O或者是否有CC攻击。
安全分析脚本:检测扫描与攻击行为日志里藏着的安全威胁往往有规律可循。SQL注入探测会带大量特殊字符,目录扫描会产生一连串404,暴力破解会对登录接口高频POST。下面这个脚本从三个维度做安全检测:高频404的IP、包含SQL注入特征的请求、以及短时间内大量请求的IP。
param(
[string]$LogPath = ".\u_ex*.log",
[int]$RequestThreshold = 200,
[int]$TimeWindowMinutes = 5
)
$logs = Get-Content $LogPath
$ipRequests = @{}
$ip404 = @{}
$sqlPatterns = @("select%20", "union%20", "1=1", "'or", "exec%20", "xp_cmdshell", "../", "..%5c")
Write-Host "=== IIS 日志安全分析 ===" -ForegroundColor Cyan
for ($i = 4; $i -lt $logs.Count; $i++) {
$line = $logs[$i]
if ([string]::IsNullOrWhiteSpace($line)) { continue }
$fields = $line -split ' '
if ($fields.Count -lt 15) { continue }
$clientIP = $fields[8]
$statusCode = $fields[11]
$uri = $fields[4]
$query = $fields[5]
$fullUrl = "$uri`?$query"
if ($ipRequests.ContainsKey($clientIP)) {
$ipRequests[$clientIP]++
} else {
$ipRequests[$clientIP] = 1
}
if ($statusCode -eq "404") {
if ($ip404.ContainsKey($clientIP)) {
$ip404[$clientIP]++
} else {
$ip404[$clientIP] = 1
}
}
foreach ($pattern in $sqlPatterns) {
if ($fullUrl -match $pattern) {
Write-Host "[!] 疑似SQL注入探测: IP=$clientIP URL=$fullUrl" -ForegroundColor Red
break
}
}
}
Write-Host "`n高频404 IP (可能在进行目录扫描):" -ForegroundColor Yellow
$ip404.GetEnumerator() | Where-Object { $_.Value -gt 50 } | Sort-Object Value -Descending | ForEach-Object {
Write-Host " $($_.Key) -> 404次数: $($_.Value)" -ForegroundColor Red
}
Write-Host "`n高频请求IP (可能在进行CC攻击或暴力破解):" -ForegroundColor Yellow
$ipRequests.GetEnumerator() | Where-Object { $_.Value -gt $RequestThreshold } | Sort-Object Value -Descending | ForEach-Object {
Write-Host " $($_.Key) -> 请求数: $($_.Value)" -ForegroundColor Red
}
这个脚本里的SQL注入特征库可以根据实际攻击趋势持续扩充。高频404的阈值设成50次,如果你站点本身页面就很少,可以降到20次。高频请求阈值200次需要结合你的正常业务量来调,一个普通用户5分钟内不可能访问200个页面,但API客户端有可能,所以要根据实际情况判断。
日志合并与批量处理:应对多台服务器场景生产环境往往有多台Web服务器,日志分散在不同机器上。分析前需要先把它们合并,或者逐台分析后汇总。下面的脚本支持从远程服务器拉取日志并合并分析,省去手动拷贝的麻烦。
param(
[string[]]$Servers = @("Web01", "Web02"),
[string]$RemoteLogPath = "D$\IISLogs\W3SVC1\u_ex{0:yyMMdd}*.log",
[string]$LocalTempPath = "C:\Temp\IISLogs"
)
$date = Get-Date
$remotePattern = $RemoteLogPath -f $date
New-Item -ItemType Directory -Force -Path $LocalTempPath | Out-Null
foreach ($server in $Servers) {
$remoteFullPath = "\\$server\$remotePattern"
Write-Host "正在从 $server 拉取日志..." -ForegroundColor Cyan
try {
Copy-Item -Path $remoteFullPath -Destination $LocalTempPath -Force -ErrorAction Stop
Write-Host " $server 日志拉取完成" -ForegroundColor Green
} catch {
Write-Host " $server 日志拉取失败: $_" -ForegroundColor Red
}
}
Write-Host "`n合并分析本地日志文件..." -ForegroundColor Cyan
$allLogs = Get-ChildItem -Path $LocalTempPath -Filter "*.log" | Get-Content
Write-Host "合并后总行数: $($allLogs.Count)" -ForegroundColor Yellow
这个脚本用Windows文件共享的方式拉取日志,需要你有目标服务器的管理员权限。如果你用的是集中式日志方案比如ELK,那就不需要这个脚本,直接在Kibana里查就行。但对于没有上日志平台的小团队,这个方案成本最低。
定时任务与自动化报告手动跑脚本只能解决一时之需,把分析做成定时任务才能真正防患于未然。下面的批处理脚本配合Windows任务计划程序,可以每天凌晨自动分析前一天的日志,并把结果输出到文本文件。
@echo off setlocal set LOG_DIR=D:\IISLogs\W3SVC1 set REPORT_DIR=D:\IISReports set POWERSHELL=C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe set DATE=%date:~0,4%%date:~5,2%%date:~8,2% if not exist "%REPORT_DIR%" mkdir "%REPORT_DIR%" %POWERSHELL% -ExecutionPolicy Bypass -File "C:\Scripts\iis-log-basic.ps1" -LogPath "%LOG_DIR%\u_ex%DATE%*.log" > "%REPORT_DIR%\report_%DATE%.txt" %POWERSHELL% -ExecutionPolicy Bypass -File "C:\Scripts\iis-log-security.ps1" -LogPath "%LOG_DIR%\u_ex%DATE%*.log" >> "%REPORT_DIR%\report_%DATE%.txt" echo 报告已生成: %REPORT_DIR%\report_%DATE%.txt
在任务计划程序中创建一个基本任务,触发器设为每天凌晨2点,操作选“启动程序”并指向这个bat文件。记得把PowerShell执行策略设为RemoteSigned或Bypass,否则脚本会被阻止运行。报告文件按日期命名,方便后续回溯和对比趋势。
日志字段顺序的自定义适配前面所有脚本都假设你的IIS日志字段顺序是默认的。但实际环境中很多人会自定义字段,比如增加Cookie、Referer、User-Agent等。如果你的字段顺序和默认不同,脚本里的索引就会错位,导致统计结果完全错误。查看你的字段顺序很简单,打开任意一个日志文件,看第四行 #Fields 那行,它列出了所有字段的排列顺序。
比如默认顺序是:date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status sc-bytes cs-bytes time-taken
那么c-ip就是第8个字段(索引从0开始),sc-status是第11个,time-taken是第14个。如果你的顺序不同,就把脚本里 fields[8]、fields[11] 这些数字改成对应的位置。建议把索引值定义成变量放在脚本开头,这样改一次就行,不用满脚本找数字。
处理大日志文件的性能优化当单个日志文件超过1GB时,Get-Content一次性读入内存会让服务器内存飙高甚至卡死。改用.NET的StreamReader逐行读取可以大幅降低内存占用,处理速度也会更快。
$reader = [System.IO.StreamReader]::new("D:\IISLogs\u_ex20250101.log")
$lineNumber = 0
while (($line = $reader.ReadLine()) -ne $null) {
$lineNumber++
if ($lineNumber -le 4) { continue }
if ([string]::IsNullOrWhiteSpace($line)) { continue }
$fields = $line -split ' '
# 后续处理逻辑不变
}
$reader.Close()
用StreamReader处理10GB的日志文件,内存占用基本稳定在几十MB,而Get-Content可能会吃掉十几个GB。如果你的日志按天切割,单日文件通常不会太大,但遇到攻击时日志量会暴增,这个优化就有用了。
跨平台替代方案:Python版分析脚本如果你的运维工具链里有Linux或者喜欢用Python,下面这个Python脚本实现和PowerShell版相同的核心功能。它不依赖Windows特性,可以在任何装了Python的机器上运行。
import re
import os
from collections import Counter
from datetime import datetime
def parse_iis_log(log_path):
ips = Counter()
status_codes = Counter()
total_bytes = 0
time_takens = []
slow_requests = []
with open(log_path, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
for line in lines[4:]:
if not line.strip():
continue
fields = line.split()
if len(fields) < 15:
continue
try:
ip = fields[8]
status = fields[11]
bytes_sent = int(fields[12]) if fields[12] != '-' else 0
time_taken = int(fields[13]) if fields[13] != '-' else 0
ips[ip] += 1
status_codes[status] += 1
total_bytes += bytes_sent
time_takens.append(time_taken)
if time_taken > 5000:
slow_requests.append({
'time': f"{fields[0]} {fields[1]}",
'ip': ip,
'url': fields[4],
'time_taken': time_taken
})
except (IndexError, ValueError):
continue
print(f"总请求数: {sum(ips.values())}")
print(f"\n状态码分布:")
for code, count in status_codes.most_common():
print(f" HTTP {code}: {count}")
print(f"\nTop 10 IP:")
for ip, count in ips.most_common(10):
print(f" {ip}: {count}")
if time_takens:
print(f"\n平均响应时间: {sum(time_takens)/len(time_takens):.2f}ms")
print(f"总流量: {total_bytes/1024/1024:.2f} MB")
print(f"\n慢请求 (>{5000}ms):")
for req in sorted(slow_requests, key=lambda x: x['time_taken'], reverse=True)[:10]:
print(f" {req['time']} {req['ip']} {req['url']} {req['time_taken']}ms")
if __name__ == "__main__":
parse_iis_log("u_ex250101.log")
Python版本的可读性更好,也更容易扩展。比如你想把结果输出成JSON或者直接写进数据库,加几行代码就行。日志文件编码通常是UTF-8,但有些老服务器可能是GBK,用errors='ignore'参数可以避免编码报错中断分析。
日志分析的实际排错思路有了脚本只是第一步,知道怎么看结果、怎么关联排查才是关键。当你发现500错误突然增多,先看这些500请求集中在哪些URL上,是某个接口全挂还是零星偶发。然后交叉对比对应时间段的应用程序日志和系统事件查看器,IIS日志告诉你“哪个请求失败了”,但“为什么失败”通常写在应用程序日志里。
如果发现某个IP在短时间内请求了大量不同URL且返回大量404,十有八九是扫描器在踩点。这时候应该立刻在防火墙或IIS的“IP地址和域限制”模块里封禁这个IP,而不是等它扫到弱点。如果404的URL呈现规律性变化,比如 /admin、/wp-admin、/phpmyadmin 这种常见后台路径,那基本确定是自动化扫描工具。
响应时间突然整体变慢,但服务器CPU和内存正常,很可能是数据库查询阻塞或者外部API调用超时。从日志里筛选出耗时最长的请求,看它们的URL是否都调用了同一个数据库存储过程或者同一个外部服务。这种关联分析靠人工看日志很难发现,但用脚本把慢请求的URL做聚合统计,模式就一目了然了。
