Our existing ASP/MVC apps all log to the eventlog. I’m working on moving an app into docker, but I need the events from the container’s eventlog to make it to the host’s eventlog. Is this possible?
I didn’t get an answer from anyone, but I did find what I was looking for in the Docker for Windows book. I have a startup powershell script that starts W3SVC and then a loop that polls the eventlog and writes it out to stdout where it can be pulled using docker container logs. It also configures IIS to log to C:\Logs which I have setup as a volume on the host, so I can monitor the IIS logs. Here’s what it looks like:
Configure IIS to log to c:\logs which is mapped to c:\logs on the host, and start IIS
Import-Module WebAdministration
Set-WebConfigurationProperty -pspath ‘MACHINE/WEBROOT/APPHOST’ -filter “system.applicationHost/sites/siteDefaults/logFile” -name “directory” -value “c:\logs”
Start-Service W3SVC
Poll eventlog and write entries to stdout so they can be retrieved with docker log
$lastCheck = (Get-Date).AddSeconds(-5)
while ($true)
{
Get-EventLog -LogName Application -After $lastCheck | Select-Object EntryType, TimeGenerated, Source, Message
$lastCheck = Get-Date
Start-Sleep -Seconds 5
}
On Windows (dotnet Core) it can be done using powershell remote-invoke.
In the container you need to set a trusted host by doing:
RUN @powershell Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
On the host you need to create an Event Source called MyLogger
.
After that in your app you can create a custom logger that you add to the Microsoft Logging pipeline.
RemoteEventLogProvider.cs:
using System.Management.Automation;
using System.Net;
using Microsoft.Extensions.Logging;
namespace LogWriter
{
public class RemoteEventLogProvider : ILoggerProvider
{
private readonly string _computer;
private readonly PowerShell _powershell;
private readonly NetworkCredential _credential;
public RemoteEventLogProvider(string computer, NetworkCredential credential)
{
_computer = computer;
_credential = credential;
_powershell = PowerShell.Create();
}
public ILogger CreateLogger(string category)
{
return new RemoteEventLogger(_computer, _credential, _powershell, category);
}
public void Dispose()
{
_powershell.Dispose();
}
}
}
RemoteEventLogger.cs
using System;
using System.Management.Automation;
using System.Net;
using Microsoft.Extensions.Logging;
namespace LogWriter
{
public class RemoteEventLogger : ILogger
{
private readonly string _computer;
private readonly string _category;
private readonly PowerShell _powershell;
private readonly NetworkCredential _credential;
public RemoteEventLogger(string computer, NetworkCredential credential, PowerShell powershell, string category)
{
_computer = computer;
_category = category;
_credential = credential;
_powershell = powershell;
}
public IDisposable BeginScope<TState>(TState state)
{
throw new NotImplementedException();
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var script = ScriptBlock.Create($"$password = ConvertTo-SecureString '{_credential.Password}' -AsPlainText -Force;" +
$"$credential = New-Object System.Management.Automation.PSCredential ('{_credential.UserName}', $password);" +
$"Invoke-Command -ScriptBlock {{ Write-EventLog -EntryType {logLevel} -EventId 0 -Source MyLogger -LogName Application -Message \"{formatter(state, exception)}\" }} -ComputerName {_computer} -Credential $credential;");
var results = _powershell.AddScript(script.ToString()).Invoke();
_powershell.Commands.Clear();
}
}
}
Example usage Program.cs:
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace LogWriter
{
class Program
{
static async Task Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
.AddEnvironmentVariables()
.Build();
var computer = config["ConnectionStrings:Host.Address"];
var provider = new RemoteEventLogProvider(computer, new NetworkCredential($"{computer}\\username", "password"));
using (var factory = new LoggerFactory().AddConsole())
{
factory.AddProvider(provider);
var logger = factory.CreateLogger("MyLogger");
logger.LogInformation($"Remote log at {computer}");
while (true)
{
try
{
logger.LogInformation($"The time is {DateTime.UtcNow}");
if (DateTime.UtcNow.Second % 2 == 0)
{
throw new Exception("random shit");
}
else
{
logger.LogWarning("Example warning");
}
}
catch (Exception ex)
{
logger.LogError(ex, ex.Message);
}
await Task.Delay(TimeSpan.FromSeconds(5));
}
}
}
}
}
Example Dockerfile:
# Build
FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build-env
WORKDIR /app
COPY . /app
RUN dotnet publish -c Release -o /app/out
# Runtime
FROM mcr.microsoft.com/windows/servercore:ltsc2019
RUN curl -o c:\dotnet.exe https://download.visualstudio.microsoft.com/download/pr/a9bb6d52-5f3f-4f95-90c2-084c499e4e33/eba3019b555bb9327079a0b1142cc5b2/dotnet-hosting-2.2.6-win.exe
RUN c:\dotnet.exe /quiet /install
# Host
RUN @powershell Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
# Application
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT dotnet LogWriter.dll