David Thielen
David Thielen

Reputation: 32926

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).

Upvotes: 0

Views: 393

Answers (1)

Jason Pan
Jason Pan

Reputation: 21883

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");
    }
}

Upvotes: 1

Related Questions