0

I run all my services as Docker containers on Linux by default. Recently got an order from a very large company to deploy to their Windows Servers. They have a very specific request for all application logs to appear in the host machine's EventLog. Their administrators don't know much about Docker and they don't want a centralised logging solution, like Kibana, for this deployment.

Is there a feasible way to achieve that on Windows?

Cheers

EDIT

The services are all written in .NET Core 2.2.

1 Answer 1

0

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

Ideally replace the * with the host actual IP address. Keep in mind it changes between system restarts.

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.