Is there a way to log to the host eventlog

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