How to Configure IIS Logging and Analysis on Windows Server 2022
Internet Information Services (IIS) on Windows Server 2022 generates detailed access logs for every HTTP request processed by the web server. These logs are the primary data source for diagnosing application errors, identifying performance bottlenecks, detecting security threats, and auditing user activity. This guide covers the IIS W3C log format in depth, log configuration and rotation, and multiple approaches to analysis from PowerShell scripts to centralized log shipping.
IIS W3C Extended Log Format
IIS defaults to the W3C Extended log format, which is the most information-rich option and the industry standard for HTTP server logging. Each log line is a space-delimited record of fields defined in a header comment at the top of the file. The header line beginning with #Fields: lists the field names in order, making the log self-describing.
A typical W3C log header and sample lines look like this:
#Software: Microsoft Internet Information Services 10.0
#Version: 1.0
#Date: 2026-05-17 00:00:01
#Fields: date time s-sitename s-computername 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 time-taken
2026-05-17 08:23:41 W3SVC1 WEBSERVER01 10.0.0.5 GET /api/products - 443 jdoe 192.168.1.100 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64) https://example.com/shop 200 0 0 143
2026-05-17 08:23:42 W3SVC1 WEBSERVER01 10.0.0.5 POST /api/checkout - 443 jdoe 192.168.1.100 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64) https://example.com/cart 500 0 0 4521
2026-05-17 08:23:43 W3SVC1 WEBSERVER01 10.0.0.5 GET /images/banner.jpg - 80 - 203.0.113.45 curl/7.81.0 - 404 0 2 12
Understanding each field is essential for effective analysis. The date and time fields record the UTC timestamp of the request. The s-ip field is the server IP address that handled the request, which matters on multi-homed servers. The cs-method is the HTTP verb (GET, POST, PUT, DELETE, HEAD, OPTIONS). The cs-uri-stem is the path portion of the URL without the query string. The cs-uri-query captures the query string (a hyphen when absent). The s-port identifies which virtual server processed the request. The cs-username is the authenticated username (hyphen for anonymous requests). The c-ip is the client IP address. The cs(User-Agent) field contains the browser or client identification string. The sc-status is the HTTP status code. The sc-substatus is the IIS-specific sub-status code that provides more detail than the HTTP status alone. The sc-win32-status is the underlying Windows API error code (0 means success). The time-taken field records elapsed processing time in milliseconds.
IIS Sub-Status Codes
The sc-substatus field is unique to IIS and carries critical diagnostic information invisible in the HTTP status code alone. A 404.0 is a standard not found, but 404.7 means file extension denied, 404.8 means hidden namespace, and 404.14 means URL too long. For 500 errors, 500.19 indicates a configuration error in web.config, 500.21 means handler not recognized, and 500.50 means a rewrite rule error. Analyzing sc-substatus alongside sc-status dramatically narrows troubleshooting scope.
Configuring IIS Log Location and Format
IIS log settings are configurable per site in IIS Manager or via the command-line tool AppCmd.exe and PowerShell with the WebAdministration module. The default log directory is C:inetpublogsLogFiles with one subfolder per site.
Change the log directory and configure field selection using PowerShell:
Import-Module WebAdministration
# View current logging configuration for Default Web Site
Get-WebConfigurationProperty -Filter "system.applicationHost/sites/site[@name='Default Web Site']/logFile" -PSPath IIS: -Name *
# Change log directory to a dedicated volume
Set-WebConfigurationProperty `
-Filter "system.applicationHost/sites/site[@name='Default Web Site']/logFile" `
-PSPath IIS: `
-Name directory `
-Value "D:IISLogs"
# Set log format to W3C
Set-WebConfigurationProperty `
-Filter "system.applicationHost/sites/site[@name='Default Web Site']/logFile" `
-PSPath IIS: `
-Name logFormat `
-Value W3C
# Enable specific log fields (add time-taken and cs(User-Agent) if not already enabled)
Set-WebConfigurationProperty `
-Filter "system.applicationHost/sites/site[@name='Default Web Site']/logFile" `
-PSPath IIS: `
-Name logExtFileFlags `
-Value "Date,Time,SiteName,ComputerName,ServerIP,Method,UriStem,UriQuery,ServerPort,UserName,ClientIP,UserAgent,Referer,HttpStatus,HttpSubStatus,Win32Status,TimeTaken"
Enable UTC timestamps (recommended for multi-timezone environments):
Set-WebConfigurationProperty `
-Filter "system.applicationHost/log" `
-PSPath IIS: `
-Name centralLogFileMode `
-Value Site
# Use UTC for log timestamps
Set-WebConfigurationProperty `
-Filter "system.applicationHost/sites/site[@name='Default Web Site']/logFile" `
-PSPath IIS: `
-Name localTimeRollover `
-Value $false
Log Rotation Configuration
IIS supports three log rotation strategies: by period (hourly, daily, weekly, monthly), by size (create a new file when the current file reaches a threshold), or a maximum lines approach for high-traffic sites. Daily rotation is the standard recommendation because it simplifies archiving and correlates log files with calendar dates for incident investigation.
# Set daily log rotation
Set-WebConfigurationProperty `
-Filter "system.applicationHost/sites/site[@name='Default Web Site']/logFile" `
-PSPath IIS: `
-Name period `
-Value Daily
# Set size-based rotation (rotate when file reaches 100 MB, expressed in bytes)
Set-WebConfigurationProperty `
-Filter "system.applicationHost/sites/site[@name='Default Web Site']/logFile" `
-PSPath IIS: `
-Name period `
-Value MaxSize
Set-WebConfigurationProperty `
-Filter "system.applicationHost/sites/site[@name='Default Web Site']/logFile" `
-PSPath IIS: `
-Name truncateSize `
-Value 104857600
Automate log compression and archiving with a scheduled PowerShell script:
# Compress IIS logs older than 7 days and move to archive
$logRoot = "D:IISLogs"
$archiveRoot = "E:IISLogsArchive"
$cutoffDate = (Get-Date).AddDays(-7)
Get-ChildItem -Path $logRoot -Recurse -Filter "*.log" |
Where-Object { $_.LastWriteTime -lt $cutoffDate } |
ForEach-Object {
$relativePath = $_.FullName.Substring($logRoot.Length)
$archivePath = Join-Path $archiveRoot ($relativePath + ".gz")
$archiveDir = Split-Path $archivePath -Parent
if (-not (Test-Path $archiveDir)) {
New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
}
# Compress to gzip
$srcStream = [System.IO.File]::OpenRead($_.FullName)
$dstStream = [System.IO.File]::Create($archivePath)
$gzip = [System.IO.Compression.GzipStream]::new($dstStream, [System.IO.Compression.CompressionMode]::Compress)
$srcStream.CopyTo($gzip)
$gzip.Close()
$srcStream.Close()
$dstStream.Close()
Remove-Item $_.FullName -Force
Write-Output "Archived: $($_.Name)"
}
Real-Time Log Analysis with Log Parser 2.2
Microsoft Log Parser 2.2 is a free tool that applies SQL-like queries directly against IIS log files without importing them into a database. It supports W3C, IIS, NCSA, and CSV formats and can output to CSV, SQL Server, charts, and other formats. Log Parser is indispensable for quick ad-hoc analysis of log files on the server itself.
# Count HTTP status codes for today's log file
"C:Program Files (x86)Log Parser 2.2LogParser.exe" `
"SELECT sc-status, COUNT(*) AS Hits FROM D:IISLogsW3SVC1u_ex260517.log GROUP BY sc-status ORDER BY Hits DESC" `
-i:W3C -o:CSV
# Find top 20 client IPs by request count
"C:Program Files (x86)Log Parser 2.2LogParser.exe" `
"SELECT TOP 20 c-ip, COUNT(*) AS Requests, MAX(time-taken) AS MaxTimeMs FROM D:IISLogsW3SVC1u_ex260517.log GROUP BY c-ip ORDER BY Requests DESC" `
-i:W3C -o:DATAGRID
# Find all 500 errors with URI and time-taken
"C:Program Files (x86)Log Parser 2.2LogParser.exe" `
"SELECT date, time, c-ip, cs-uri-stem, sc-status, sc-substatus, sc-win32-status, time-taken FROM D:IISLogsW3SVC1u_ex260517.log WHERE sc-status = 500 ORDER BY time-taken DESC" `
-i:W3C -o:DATAGRID
# Average response time by URI stem (slowest endpoints)
"C:Program Files (x86)Log Parser 2.2LogParser.exe" `
"SELECT TOP 20 cs-uri-stem, COUNT(*) AS Hits, AVG(time-taken) AS AvgMs, MAX(time-taken) AS MaxMs FROM D:IISLogsW3SVC1*.log WHERE sc-status slowest-endpoints.csv
PowerShell IIS Log Analysis Scripts
For automated reporting without Log Parser, PowerShell can parse W3C log files natively. The following script reads today’s IIS log, parses all fields, and produces a summary report:
function Get-IISLogSummary {
param (
[string]$LogPath = "D:IISLogsW3SVC1",
[int]$DaysBack = 1
)
$cutoff = (Get-Date).AddDays(-$DaysBack)
$logs = Get-ChildItem -Path $LogPath -Filter "*.log" | Where-Object { $_.LastWriteTime -ge $cutoff }
$allEntries = foreach ($log in $logs) {
$headers = $null
Get-Content $log.FullName | ForEach-Object {
if ($_ -match "^#Fields: (.+)") {
$headers = ($matches[1] -split " ")
} elseif ($_ -notmatch "^#") {
if ($headers) {
$values = $_ -split " "
$entry = [ordered]@{}
for ($i = 0; $i -lt $headers.Count; $i++) {
$entry[$headers[$i]] = if ($i -lt $values.Count) { $values[$i] } else { "-" }
}
[PSCustomObject]$entry
}
}
}
}
Write-Host "`n=== Status Code Distribution ==="
$allEntries | Group-Object "sc-status" | Sort-Object Count -Descending |
Select-Object Name, Count | Format-Table
Write-Host "`n=== Top 10 URIs by Request Count ==="
$allEntries | Group-Object "cs-uri-stem" | Sort-Object Count -Descending |
Select-Object -First 10 Name, Count | Format-Table
Write-Host "`n=== Top 10 Client IPs ==="
$allEntries | Group-Object "c-ip" | Sort-Object Count -Descending |
Select-Object -First 10 Name, Count | Format-Table
Write-Host "`n=== Slowest 10 Requests (ms) ==="
$allEntries | Where-Object { $_."time-taken" -match "^d+$" } |
Sort-Object { [int]$_."time-taken" } -Descending |
Select-Object -First 10 "cs-uri-stem", "sc-status", "time-taken", "c-ip" | Format-Table
}
Get-IISLogSummary -LogPath "D:IISLogsW3SVC1" -DaysBack 1
Shipping IIS Logs to Elastic Stack
For centralized log management with Elasticsearch and Kibana, the Elastic Agent with the IIS integration module is the recommended approach. It handles log parsing, field mapping, and dashboard provisioning automatically. Install Elastic Agent on the Windows Server and enroll it in Fleet:
# Download and install Elastic Agent (PowerShell, run as Administrator)
Invoke-WebRequest -Uri "https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.13.0-windows-x86_64.zip" `
-OutFile "C:Tempelastic-agent.zip"
Expand-Archive -Path "C:Tempelastic-agent.zip" -DestinationPath "C:Tempelastic-agent"
cd C:Tempelastic-agentelastic-agent-8.13.0-windows-x86_64
# Enroll in Fleet (replace with your Fleet server URL and enrollment token)
.elastic-agent.exe install `
--fleet-server-es=https://elastic-cluster:9200 `
--fleet-server-service-token= `
--fleet-server-policy=default-policy
# After enrollment, add the IIS integration from the Fleet UI
# The IIS integration auto-parses W3C logs from:
# C:inetpublogsLogFiles**.log
# Configure the log path in the integration settings if your logs are elsewhere
Alternatively, use Filebeat with the IIS module for environments not using Fleet:
# filebeat.yml configuration for IIS log shipping
filebeat.modules:
- module: iis
access:
enabled: true
var.paths: ["D:/IISLogs/*/*.log"]
error:
enabled: true
var.paths: ["C:/Windows/System32/LogFiles/HTTPERR/*.log"]
output.elasticsearch:
hosts: ["https://elastic-cluster:9200"]
username: "filebeat_user"
password: "securepassword"
ssl.certificate_authorities: ["C:/certs/ca.crt"]
setup.kibana:
host: "https://kibana:5601"
Shipping IIS Logs to Grafana Loki
Grafana Loki is a log aggregation system optimized for label-based querying rather than full-text indexing. Promtail, the Loki log shipper, handles Windows IIS logs with a pipeline configuration that parses W3C format and extracts fields as labels:
# promtail-config.yaml for IIS W3C logs
server:
http_listen_port: 9080
positions:
filename: C:promtailpositions.yaml
clients:
- url: http://loki-server:3100/loki/api/v1/push
scrape_configs:
- job_name: iis_access
static_configs:
- targets:
- localhost
labels:
job: iis
host: WEBSERVER01
__path__: D:IISLogs***.log
pipeline_stages:
- match:
selector: '{job="iis"}'
stages:
- regex:
expression: '^(?Pd{4}-d{2}-d{2})s+(?P
With logs in Loki, use LogQL queries in Grafana to build dashboards showing request rates, error rates, and latency percentiles from IIS access logs in real time. This centralized approach scales across multiple IIS servers and provides a unified view across the entire web tier.