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).
You can check the below sample.
Test Result
My sample project structure
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");
}
}