Configuring IIS for Docker-friendly logging
IIS writes log entries to text files, recording HTTP requests and responses. You can configure exactly what fields are written, but the default installation records useful things like the route of the HTTP request, the response status code, and the time taken for IIS to respond. It would be good to surface these logs entries to Docker, but IIS manages its own log files, buffering entries before writing them to the disk, and rotating log files to manage the disk space.
Log management is a fundamental part of application platforms, which is why IIS takes care of it for web apps, but Docker has its own logging system. Docker logging is far more powerful and pluggable than the text filesystem that IIS uses, but it only reads log entries from the container's console output stream. You can't have IIS writing logs to the console because it runs in a background Windows Service with no console attached, so you need a different approach.
There are two options for this. The first is to build an HTTP module which plugs into the IIS platform with an event handler that receives logs from IIS. This handler can publish all messages to a queue or a Windows pipe, so you don't change how IIS logs; you just add another log sink. Then, you'd package your web application together with a console app that listens for published log entries and relays them on the console. The console app would be the entry point when a container starts, so every IIS log entry would get routed to the console for Docker to read.
The HTTP module approach is robust and scalable, but it adds more complexity than we need when we're getting started. The second option is simpler - configure IIS to write all of the log entries to a single text file, and in the startup command for the container, run a PowerShell script to watch that file and echo new log entries to the console. When the container is running, all the IIS log entries get echoed to the console, which surfaces them to Docker.
To set this up in the Docker image, you first need to configure IIS so that it writes all of the log entries from any site to a single file, and it lets the file grow without rotating it. You can do this with PowerShell, using the Set-WebConfigurationProperty cmdlet in the Dockerfile, modifying the central logging properties at the application host level. I use this cmdlet in the Dockerfile for the dockeronwindows/ch03-iis-log-watcher image:
RUN Set-WebConfigurationProperty -p 'MACHINE/WEBROOT/APPHOST' -fi 'system.applicationHost/log' -n 'centralLogFileMode' -v 'CentralW3C'; `
Set-WebConfigurationProperty -p 'MACHINE/WEBROOT/APPHOST' -fi 'system.applicationHost/log/centralW3CLogFile' -n 'truncateSize' -v 4294967295; `
Set-WebConfigurationProperty -p 'MACHINE/WEBROOT/APPHOST' -fi 'system.applicationHost/log/centralW3CLogFile' -n 'period' -v 'MaxSize'; `
Set-WebConfigurationProperty -p 'MACHINE/WEBROOT/APPHOST' -fi 'system.applicationHost/log/centralW3CLogFile' -n 'directory' -v 'C:\iislog'
This is ugly code, but it shows you can write whatever you need in a Dockerfile to set up your application. It configures IIS to log all entries to a file in C:\iislog, and to set the maximum file size for log rotation, letting the log file grow to 4 GB. That's plenty of room to play with - remember, containers are not meant to be long-lived, so we shouldn't have gigabytes of log entries in a single container. IIS still uses a subdirectory format for the log file, so the actual log file path will be C:\iislog\W3SVC\u_extend1.log. Now that I have a known log file location, I can use PowerShell to echo log entries to the console.
I do this in the CMD instruction, so the final command that Docker runs and monitors is the PowerShell cmdlet to echo log entries. When new entries are written to the console, they get picked up by Docker. PowerShell makes it easy to watch the file, but there's a complication because the file needs to exist before PowerShell can watch it. In the Dockerfile, I run multiple commands in sequence at startup:
CMD Start-Service W3SVC; ` Invoke-WebRequest http://localhost -UseBasicParsing | Out-Null; ` netsh http flush logbuffer | Out-Null; ` Get-Content -path 'c:\iislog\W3SVC\u_extend1.log' -Tail 1 -Wait
Four things happen when a container starts:
- Start the IIS Windows service (W3SVC).
- Make an HTTP GET request to the localhost, which starts the IIS worker process and writes the first log entry.
- Flush the HTTP log buffer, so the log file gets written to the disk and exists for PowerShell to watch.
- Read the content of the log file in tail mode, so any new lines written to the file are shown on the console.
I can run a container from this image in the usual way:
docker container run -d -P --name log-watcher dockeronwindows/ch03-iis-log-watcher:2e
When I send some traffic to the site by browsing to the container's IP address (or using Invoke-WebRequest in PowerShell), I can see the IIS log entries that are relayed to Docker from the Get-Content cmdlet using docker container logs:
> docker container logs log-watcher
2019-02-06 20:21:30 W3SVC1 172.27.97.43 GET / - 80 - 192.168.2.214 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64;+rv:64.0)+Gecko/20100101+Firefox/64.0 - 200 0 0 7
2019-02-06 20:21:30 W3SVC1 172.27.97.43 GET /iisstart.png - 80 - 192.168.2.214 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64;+rv:64.0)+Gecko/20100101+Firefox/64.0 http://localhost:51959/ 200 0 0 17
2019-02-06 20:21:30 W3SVC1 172.27.97.43 GET /favicon.ico - 80 - 192.168.2.214 Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64;+rv:64.0)+Gecko/20100101+Firefox/64.0 - 404 0 2 23
I've added the configuration to IIS in the image and a new command, which means all IIS log entries get echoed to the console. This will work for any application hosted in IIS, so I can echo HTTP logs for ASP.NET applications and static websites without any changes to the apps or the site content. Console output is where Docker looks for log entries, so this simple extension integrates logging from the existing application into the new platform.