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做聚合统计,模式就一目了然了。