Extends the Microsoft.Extensions.Logging class to provide a rolling file logger.

// ---------------------------------------------------------------------------
// Logging/FileLogger.cs
// https://github.com/NonFactors/AspNetCore.Template/blob/develop/src/MvcTemplate.Components/Logging/FileLogger.cs
// ---------------------------------------------------------------------------

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using DotnetCommon.Components.Extensions;
using System;
using System.IO;
using System.Text;

namespace Components.Logging
{
    public class FileLogger : ILogger
    {
        private Int64 RollSize { get; }
        private String LogPath { get; }
        private String LogDirectory { get; }
        private String RollingFileFormat { get; }
        private IHttpContextAccessor Accessor { get; }

        private static Object LogWriting { get; } = new Object();

        public FileLogger(String path, Int64 rollSize)
        {
            String file = Path.GetFileNameWithoutExtension(path);
            LogDirectory = Path.GetDirectoryName(path) ?? "";
            String extension = Path.GetExtension(path);
            Accessor = new HttpContextAccessor();

            RollingFileFormat = $"{file}-{{0:yyyyMMdd-HHmmss}}{extension}";
            RollSize = rollSize;
            LogPath = path;
        }

        public Boolean IsEnabled(LogLevel logLevel)
        {
            return logLevel != LogLevel.None;
        }

        public IDisposable? BeginScope<TState>(TState state)
        {
            return null;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, String> formatter)
        {
            if (!IsEnabled(logLevel))
            {
                return;
            }

            StringBuilder log = new StringBuilder();
            log.Append("Id         : ").Append(Accessor.HttpContext?.TraceIdentifier).Append(" [").Append(Accessor.HttpContext?.User.Id()).AppendLine("]");
            log.Append("Time       : ").AppendFormat("{0:yyyy-MM-dd HH:mm:ss.ffffff}", DateTime.Now).AppendLine();
            log.AppendFormat("{0,11}", logLevel).Append(": ").AppendLine(formatter(state, exception));

            if (exception != null)
            {
                log.AppendLine("Stack trace:");
            }

            while (exception != null)
            {
                log.Append("    ").Append(exception.GetType()).Append(": ").AppendLine(exception.Message);

                if (exception.StackTrace is String stackTrace)
                {
                    foreach (String line in stackTrace.Split('\n'))
                    {
                        log.Append("     ").AppendLine(line.TrimEnd('\r'));
                    }
                }

                exception = exception.InnerException;
            }

            log.AppendLine();

            lock (LogWriting)
            {
                Directory.CreateDirectory(LogDirectory);
                File.AppendAllText(LogPath, log.ToString());

                if (RollSize <= new FileInfo(LogPath).Length)
                {
                    File.Move(LogPath, Path.Combine(LogDirectory, String.Format(RollingFileFormat, DateTime.Now)));
                }
            }
        }
    }
}

// ---------------------------------------------------------------------------
// Logging/FileLoggerProvider.cs
// https://github.com/NonFactors/AspNetCore.Template/blob/develop/src/MvcTemplate.Components/Logging/FileLoggerProvider.cs
// ---------------------------------------------------------------------------

using Microsoft.Extensions.Logging;
using System;

namespace Components.Logging
{
    [ProviderAlias("File")]
    public class FileLoggerProvider : ILoggerProvider
    {
        private ILogger Logger { get; }

        public FileLoggerProvider(String path, Int64 rollSize)
        {
            Logger = new FileLogger(path, rollSize);
        }

        public ILogger CreateLogger(String categoryName)
        {
            return Logger;
        }

        public void Dispose()
        {
        }
    }
}

// ---------------------------------------------------------------------------
// Startup.cs
// https://github.com/NonFactors/AspNetCore.Template/blob/develop/src/MvcTemplate.Web/Startup.cs
// ---------------------------------------------------------------------------

// ...
// ...

services.AddLogging(builder =>
{
    builder.AddConfiguration(Config.GetSection("Logging"));

    if (Environment.IsDevelopment())
    {
        builder.AddConsole();
    }
    else
    {
        builder.AddProvider(new FileLoggerProvider(Config["Logging:File:Path"], Config.GetValue<Int64>("Logging:File:RollSize")));
    }
});

// ...
// ...

// ---------------------------------------------------------------------------
// Log Configuration section in "configuration.json"
// https://github.com/NonFactors/AspNetCore.Template/blob/develop/src/MvcTemplate.Web/configuration.json
// ---------------------------------------------------------------------------
//
// ...
// ...
//
// "Logging": {
//   "File": {
//     "Path": "Logs/log.txt",
//     "RollSize": 5242880
//   }
// }
//
// ...
// ...