How to Configure IIS Logging and Analysis on Windows Server 2025
Internet Information Services (IIS) on Windows Server 2025 writes detailed W3C-format access logs for every HTTP request it handles—recording the date, time, client IP, requested URI, HTTP status code, bytes sent, user agent, and response time. Those logs are an essential resource for diagnosing 500 errors, identifying slow endpoints, detecting bot traffic, and feeding web analytics dashboards. This tutorial covers IIS log configuration, PowerShell-based log parsing, forwarding logs to Azure Log Analytics via a Data Collection Rule, Failed Request Tracing (FREB) for deep error diagnosis, and log visualization with both GoAccess and Grafana/Loki.
Prerequisites
- Windows Server 2025 with IIS role installed (
Web-Serverfeature) - At least one web site configured and receiving traffic
- PowerShell 7.4+ (recommended) or Windows PowerShell 5.1
- WebAdministration PowerShell module (
Import-Module WebAdministration) - For Azure Log Analytics forwarding: Azure Arc + Azure Monitor Agent deployed
- For GoAccess on WSL: Windows Subsystem for Linux with a Debian/Ubuntu distribution
- Administrator or sufficient IIS Manager rights
Step 1: Verify IIS Log Location and Format
By default, IIS writes logs to C:inetpublogsLogFilesW3SVC<SiteID>. Before anything else, confirm the log format and field selection for your site.
Import-Module WebAdministration
# List all sites and their log paths
Get-Website | Select-Object Name, Id, @{
n = "LogPath"
e = { (Get-WebConfigurationProperty -PSPath "IIS:Sites$($_.Name)" `
-Filter "system.applicationHost/sites/site[@name='$($_.Name)']/logFile" `
-Name "directory").Value }
}
# Get current log settings for the Default Web Site
$site = "Default Web Site"
Get-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.applicationHost/sites/site[@name='$site']/logFile" `
-Name "*"
# Confirm the W3C fields that are enabled
$logFields = Get-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.applicationHost/sites/site[@name='$site']/logFile" `
-Name "logExtFileFlags"
$logFields.Value
# Common flags: Date, Time, ClientIP, UserName, SiteName, ComputerName,
# ServerIP, Method, UriStem, UriQuery, HttpStatus,
# TimeTaken, UserAgent, Referer, BytesSentToClient, BytesReceivedFromClient
Step 2: Configure Log Rollover and Field Selection
Large sites can produce multi-gigabyte log files if rollover is not configured correctly. Set daily rollover and ensure all diagnostically useful fields are enabled.
Import-Module WebAdministration
$site = "Default Web Site"
# Set log format to W3C (most interoperable)
Set-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.applicationHost/sites/site[@name='$site']/logFile" `
-Name "logFormat" `
-Value "W3C"
# Set log directory (use a non-system drive for production)
Set-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.applicationHost/sites/site[@name='$site']/logFile" `
-Name "directory" `
-Value "D:IISLogs"
# Roll over daily
Set-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.applicationHost/sites/site[@name='$site']/logFile" `
-Name "period" `
-Value "Daily"
# Enable all key fields (bitwise OR of field flags)
# Fields: Date|Time|ClientIP|Method|UriStem|UriQuery|HttpStatus|
# TimeTaken|UserAgent|Referer|BytesSent|BytesRecvd
$fields = "Date,Time,ClientIP,UserName,Method,UriStem,UriQuery,HttpStatus,"
$fields += "Win32Status,TimeTaken,ServerIP,ServerPort,UserAgent,Referer,"
$fields += "BytesSentToClient,BytesReceivedFromClient"
Set-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.applicationHost/sites/site[@name='$site']/logFile" `
-Name "logExtFileFlags" `
-Value $fields
# Apply without requiring an IIS restart
Invoke-Command -ScriptBlock { iisreset /noforce } | Out-Null
Write-Host "IIS log settings updated for '$site'"
Step 3: Parse IIS Logs with PowerShell
IIS W3C logs are space-delimited text files with a header row beginning with #Fields:. PowerShell’s Import-Csv can parse them directly once you strip comment lines.
# Parse IIS log file and extract actionable data
$logDir = "D:IISLogsW3SVC1"
$logFile = Get-ChildItem $logDir -Filter "u_ex*.log" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
Write-Host "Parsing: $($logFile.FullName)"
# Read the file, skip comment lines starting with #
$rawLines = Get-Content $logFile.FullName
$header = ($rawLines | Where-Object { $_ -match "^#Fields:" }) -replace "^#Fields: ", ""
$headers = $header -split " "
$dataLines = $rawLines | Where-Object { $_ -notmatch "^#" }
$logs = $dataLines | ConvertFrom-Csv -Delimiter " " -Header $headers
# --- Analysis 1: HTTP 500 errors ---
Write-Host "`n=== HTTP 500 Errors ==="
$logs | Where-Object { $_."sc-status" -eq "500" } |
Select-Object date, time, "cs-uri-stem", "sc-substatus", "time-taken" |
Format-Table -AutoSize
# --- Analysis 2: Top 10 requested URLs ---
Write-Host "`n=== Top 10 URLs by Request Count ==="
$logs | Group-Object "cs-uri-stem" |
Sort-Object Count -Descending |
Select-Object -First 10 Count, Name |
Format-Table -AutoSize
# --- Analysis 3: Slow requests (time-taken > 3000ms) ---
Write-Host "`n=== Requests Over 3 Seconds ==="
$logs | Where-Object { [int]$_."time-taken" -gt 3000 } |
Select-Object date, time, "cs-uri-stem", "time-taken", "sc-status" |
Sort-Object { [int]$_."time-taken" } -Descending |
Select-Object -First 20 |
Format-Table -AutoSize
# --- Analysis 4: Top user agents (bot detection) ---
Write-Host "`n=== Top User Agents ==="
$logs | Group-Object "cs(User-Agent)" |
Sort-Object Count -Descending |
Select-Object -First 10 Count, Name |
Format-Table -AutoSize
# --- Analysis 5: Bandwidth by IP ---
Write-Host "`n=== Top Bandwidth Consumers ==="
$logs | Group-Object "c-ip" | ForEach-Object {
[PSCustomObject]@{
IP = $_.Name
Requests = $_.Count
BytesSent = ($_.Group | Measure-Object -Property "sc-bytes" -Sum).Sum
}
} | Sort-Object BytesSent -Descending |
Select-Object -First 10 |
Format-Table -AutoSize
Step 4: Enable Failed Request Tracing (FREB) for 500 Errors
Failed Request Tracing captures a detailed execution trace for requests that match your failure criteria—including the exact ISAPI filter, module, and timing for each pipeline event. This is invaluable for debugging intermittent 500 errors.
Import-Module WebAdministration
$site = "Default Web Site"
$frebDir = "D:FREBLogs"
New-Item -ItemType Directory -Path $frebDir -Force | Out-Null
# Enable Failed Request Tracing for the site
$sitePath = "IIS:Sites$site"
# Enable the feature (requires Web-Http-Tracing feature)
Enable-WebConfiguration -Filter "system.webServer/tracing/traceFailedRequests" -PSPath $sitePath
# Set the FREB output directory
Set-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.applicationHost/sites/site[@name='$site']/traceFailedRequestsLogging" `
-Name "directory" `
-Value $frebDir
Set-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.applicationHost/sites/site[@name='$site']/traceFailedRequestsLogging" `
-Name "enabled" `
-Value $true
Set-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.applicationHost/sites/site[@name='$site']/traceFailedRequestsLogging" `
-Name "maxLogFiles" `
-Value 50
# Add a rule: trace all requests returning HTTP 500 with all verbosity
Add-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.webServer/tracing/traceFailedRequests" `
-Name "." `
-Value @{
path = "*"
}
Set-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.webServer/tracing/traceFailedRequests/add[@path='*']/failureDefinitions" `
-Name "statusCodes" `
-Value "500"
Set-WebConfigurationProperty `
-PSPath "IIS:Sites$site" `
-Filter "system.webServer/tracing/traceFailedRequests/add[@path='*']/failureDefinitions" `
-Name "timeTaken" `
-Value "00:00:30" # Also trace requests taking over 30 seconds
Write-Host "FREB enabled. Traces will appear in: $frebDir"
Write-Host "Open .xml files with the freb.xsl stylesheet in IIS Manager or browser."
Step 5: Enable IIS Performance Counters in Azure Log Analytics
Forward IIS-specific performance counters (Active Connections, Requests/sec, Bytes Sent/sec) to Log Analytics by adding them to your Data Collection Rule.
$subId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$rg = "rg-web"
$workspaceRid = "/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.OperationalInsights/workspaces/law-web"
$token = (Get-AzAccessToken).Token
$iisCountersDcr = @{
location = "eastus"
properties = @{
dataSources = @{
performanceCounters = @(
@{
name = "iisCounters"
streams = @("Microsoft-Perf")
samplingFrequencyInSeconds = 30
counterSpecifiers = @(
"Web Service(_Total)Current Connections"
"Web Service(_Total)Get Requests/sec"
"Web Service(_Total)Post Requests/sec"
"Web Service(_Total)Bytes Sent/sec"
"Web Service(_Total)Bytes Received/sec"
"Web Service(_Total)Total Method Requests/sec"
"Web Service(_Total)Not Found Errors/sec"
"Web Service(_Total)Service Uptime"
"ASP.NETRequests Current"
"ASP.NETRequests Queued"
"ASP.NETRequest Wait Time"
)
}
)
}
destinations = @{
logAnalytics = @(
@{
workspaceResourceId = $workspaceRid
name = "webWorkspace"
}
)
}
dataFlows = @(
@{
streams = @("Microsoft-Perf")
destinations = @("webWorkspace")
}
)
}
} | ConvertTo-Json -Depth 10
Invoke-RestMethod `
-Method Put `
-Uri "https://management.azure.com/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.Insights/dataCollectionRules/dcr-iis-perf?api-version=2022-06-01" `
-Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
-Body $iisCountersDcr
Step 6: Visualize IIS Logs with GoAccess on WSL
GoAccess is an open-source, real-time web log analyzer that generates interactive HTML reports. On Windows Server 2025, run it inside WSL2.
# Install WSL2 with Ubuntu (run from elevated PowerShell, then restart)
wsl --install -d Ubuntu-24.04
# After WSL restart, install GoAccess inside Ubuntu (run in WSL terminal)
# sudo apt update && sudo apt install -y goaccess
# Parse IIS logs from Windows path — mount the log directory in WSL
# WSL mounts Windows drives at /mnt/d/, /mnt/c/, etc.
# Generate an HTML report from the previous day's log (run in WSL)
# Automate the report generation from PowerShell using WSL
$yesterday = (Get-Date).AddDays(-1).ToString("yyMMdd")
$logFile = "D:IISLogsW3SVC1u_ex$yesterday.log"
$reportDir = "D:Reports"
New-Item -ItemType Directory -Path $reportDir -Force | Out-Null
wsl goaccess /mnt/d/IISLogs/W3SVC1/u_ex$yesterday.log `
--log-format W3C `
--date-format "%Y-%m-%d" `
--time-format "%H:%M:%S" `
-o /mnt/d/Reports/iis_report_$yesterday.html `
--ignore-crawlers
Write-Host "Report generated: $reportDiriis_report_$yesterday.html"
Step 7: Forward IIS Logs to Grafana/Loki
Loki (from Grafana Labs) ingests unstructured logs and makes them queryable with LogQL. Use Grafana Alloy (the modern successor to Promtail) as the log shipper from Windows Server 2025.
# Install Grafana Alloy on Windows Server 2025
$alloyInstaller = "https://github.com/grafana/alloy/releases/latest/download/alloy-installer-windows-amd64.exe"
Invoke-WebRequest -Uri $alloyInstaller -OutFile "$env:TEMPalloy-installer.exe"
Start-Process -FilePath "$env:TEMPalloy-installer.exe" -ArgumentList "/S" -Wait
# Create the Alloy configuration file for IIS log forwarding
$alloyConfig = @'
local.file_match "iis_logs" {
path_targets = [{
__path__ = "D:/IISLogs/**/*.log",
job = "iis",
host = env("COMPUTERNAME"),
}]
sync_period = "30s"
}
loki.source.file "iis_source" {
targets = local.file_match.iis_logs.targets
forward_to = [loki.process.iis_parse.receiver]
}
loki.process "iis_parse" {
stage.regex {
expression = "^(?P\S+) (?P
Conclusion
Comprehensive IIS logging and analysis on Windows Server 2025 operates on multiple levels: well-structured W3C logs with daily rollover capture the raw request data, PowerShell parsing surfaces immediate insights into errors and slow requests without any additional tooling, Failed Request Tracing provides the deep module-by-module execution trace needed to diagnose complex 500 errors, Azure Log Analytics centralizes IIS performance metrics for long-term trending and alerting, GoAccess on WSL generates quick visual reports, and Grafana/Loki enables real-time log streaming with LogQL queries. Together these tools give you complete visibility into your IIS workloads—from a single slow request to aggregate traffic patterns—supporting both rapid incident response and long-term capacity planning.