asp.net-coreblazor-server-sideilogger

Is there an ILogger provider to write to a local file, or to Azure BLOB?


Is there a sample or library for writing a provider for the ASP.NET Core ILogger system that writes to a local file and/or to Azure BLOB storage?

Please do not suggest Serilog as it does not work (for me at least).

Update: Jason's answer below is great. From his guidance I wrote my own LoggerProvider implementation. I've put my implementation up on GitHub and NuGet (way too much code to post here - 11 .cs files).


Solution

  • You can check the below sample.

    Test Result

    enter image description here

    My sample project structure

    enter image description here

    AzureBlobLogger.cs

    using Azure.Storage.Blobs;
    using System.Text;
    
    namespace ilogger_sink
    {
        public class AzureBlobLogger : ILogger
        {
            private readonly BlobContainerClient _blobContainerClient;
    
            public AzureBlobLogger(BlobContainerClient blobContainerClient)
            {
                _blobContainerClient = blobContainerClient;
            }
    
            public IDisposable? BeginScope<TState>(TState state) => null;
    
            public bool IsEnabled(LogLevel logLevel) => true;
    
            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
            {
                string message = formatter(state, exception);
                string currentDate = DateTime.UtcNow.ToString("yyyyMMdd");
                string blobName = $"log_{currentDate}.txt";
    
                var blobClient = _blobContainerClient.GetBlobClient(blobName);
    
                // In a production environment make sure to handle concurrency and exceptions and use asynchronous methods
                string existingLog = "";
                if (blobClient.Exists())
                {
                    using (var ms = new MemoryStream())
                    {
                        blobClient.DownloadTo(ms);
                        ms.Position = 0;
                        using (var reader = new StreamReader(ms))
                        {
                            existingLog = reader.ReadToEnd();
                        }
                    }
                }
    
                using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(existingLog + message + Environment.NewLine)))
                {
                    blobClient.Upload(ms, true);
                }
            }
    
        }
    }
    

    AzureBlobLoggerProvider.cs

    using Azure.Storage.Blobs;
    
    namespace ilogger_sink
    {
        public class AzureBlobLoggerProvider : ILoggerProvider
        {
            private readonly BlobContainerClient _blobContainerClient;
    
            public AzureBlobLoggerProvider(string connectionString, string containerName)
            {
                var blobServiceClient = new BlobServiceClient(connectionString);
                _blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName);
            }
    
            public ILogger CreateLogger(string categoryName)
            {
                return new AzureBlobLogger(_blobContainerClient);
            }
    
            public void Dispose() { }
        }
    
    }
    

    FileLogger.cs

    namespace ilogger_sink
    {
        public class FileLogger : ILogger
        {
            private readonly string _logDirectory;
    
            public FileLogger(string logDirectory)
            {
                _logDirectory = logDirectory;
            }
    
            public IDisposable BeginScope<TState>(TState state) => null;
    
            public bool IsEnabled(LogLevel logLevel) => true;
    
            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
            {
                string message = formatter(state, exception);
                string currentDate = DateTime.UtcNow.ToString("yyyyMMdd");
                string logFilePath = Path.Combine(_logDirectory, $"log_{currentDate}.txt");
    
                System.IO.File.AppendAllText(logFilePath, message + Environment.NewLine);
            }
        }
    }
    

    FileLoggerProvider.cs

    namespace ilogger_sink
    {
        public class FileLoggerProvider : ILoggerProvider
        {
            private readonly string _filePath;
    
            public FileLoggerProvider(string filePath)
            {
                _filePath = filePath;
            }
    
            public ILogger CreateLogger(string categoryName)
            {
                return new FileLogger(_filePath);
            }
    
            public void Dispose() { }
        }
    }
    

    Program.cs

    using ilogger_sink.Data;
    using Microsoft.AspNetCore.Components;
    using Microsoft.AspNetCore.Components.Web;
    
    namespace ilogger_sink
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                var builder = WebApplication.CreateBuilder(args);
    
                // Add services to the container.
                builder.Services.AddRazorPages();
                builder.Services.AddServerSideBlazor();
                builder.Services.AddSingleton<WeatherForecastService>();
    
                // for testing
                builder.Logging.ClearProviders();
                builder.Logging.AddConsole();
    
                builder.Services.AddLogging(builder =>
                {
                    builder.AddProvider(new FileLoggerProvider("F:\\..T Core...\\ilogger-sink\\LocalLogs\\"));
                    builder.AddProvider(new AzureBlobLoggerProvider(
                    "DefaultEndpointsProt...797iH8QOQ...uffix=core.windows.net",
                    "jasonlogs"
                ));
                });
                // for testing
    
                var app = builder.Build();
    
                ...
    
                app.Run();
            }
        }
    }
    

    Test Code in Index.razor

    @page "/"
    @inject ILogger<Index> Logger
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    <button @onclick="LogMessage">Log a message</button>
    Welcome to your new app.
    
    <SurveyPrompt Title="How is Blazor working for you?" />
    
    @code {
        private void LogMessage()
        {
            Logger.LogInformation("This is a test log message from Index.razor");
        }
    }