Asp.net core has abstracted the logging operations into the ILogger interface and instead of having too many frameworks for logging, you just need to add a logging provider such as Console, SeriLog, Windows Event Viewer. In this post, I will create a custom logging provider that logs to Azure table storage
Why Azure Table Storage?
Azure table storage can store petebytes of semi-structured data with high performance inserts and updates. It is also cheap which makes it very suitable for storing log messages. It also allow us to connect to the log messages from many tools such as Azure Storage explorer unlike the case when you log messages to the file system on an Azure App Service
Structure of a custom logger

Logger
This is where the actual code for logging will exist and it must implement the ILogger
public class AzureTableLogger : ILogger
{
Microsoft.WindowsAzure.Storage.Table.CloudTable loggingTable;
public AzureTableLogger(string connectionString, string tableName)
{
var storageAccount = Microsoft.WindowsAzure.Storage.CloudStorageAccount.Parse(connectionString);
var cloudTableClient = storageAccount.CreateCloudTableClient();
loggingTable = cloudTableClient.GetTableReference(tableName);
loggingTable.CreateIfNotExistsAsync();
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
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 log = new LogEntity
{
EventId = eventId.ToString(),
LogLevel = logLevel.ToString(),
Message = formatter(state, exception),//exception?.ToString(),
PartitionKey = DateTime.Now.ToString("yyyyMMdd"),
RowKey = Guid.NewGuid().ToString()
};
var insertOp = TableOperation.Insert(log);
loggingTable.ExecuteAsync(insertOp).GetAwaiter().GetResult();
}
}
public class LogEntity : Microsoft.WindowsAzure.Storage.Table.TableEntity
{
public string LogLevel { get; set; }
public string EventId { get; set; }
public string Message { get; set; }
}
As you can see, we just use the Azure Table Storage SDK to connect to the storage account and create a cloud table client. The logger accepts the table name and connection string as a constructor parameter which will be injected using the LoggingProvider. This is the minimum version of a logger and it needs a lot of improvement but it is good as a start.
LoggingProvider
public class AzureTableLoggerProvider : ILoggerProvider
{
private readonly string connectionStringName;
private readonly string tableName;
public AzureTableLoggerProvider(string connectionString, string tableName)
{
connectionStringName = connectionString;
this.tableName = tableName;
}
public ILogger CreateLogger(string categoryName)
{
return new AzureTableLogger(connectionStringName, tableName);
}
public void Dispose()
{
}
}
In this provider, we just use the two parameters passed in the constructor to create a new logger instance
LoggingProviderExtension
public static class AzureTableLoggerExtensions
{
public static ILoggerFactory AddTableStorage(this ILoggerFactory loggerFactory, string tableName, string connectionString)
{
loggerFactory.AddProvider(new AzureTableLoggerProvider(connectionString, tableName));
return loggerFactory;
}
}
This last block is not a must but its for the sake of clean code and clarity. In this step, we just inject our logging provider into the ILoggerFactory. When the application starts, we will have to call the AddTableStorage method to be able to use the new logger.
Add the provider in startup.cs
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
loggerFactory.AddTableStorage("LoggingSample", "UseDevelopmentStorage=true");
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
Now, our application is ready to log messages to Azure Table Storage. The sample above is using local storage emulator and will create a table named LoggingSample. Lets create a controller and log some messages
[ApiController]
[Route("[controller]")]
public class CustomersController : Controller
{
private ILogger<CustomersController> logger;
public CustomersController(ILogger<CustomersController> logger)
{
this.logger = logger;
}
public IActionResult Index()
{
logger.LogInformation("This is a message from customers controller");
return Ok();
}
}
In the Index action, we called the logger to add a test message. Notice that you don’t have to specify anything other than the ILogger. Asp.net core DI will inject the proper logger at runtime.

Notice the new columns added to the table such as EventId and LogLevel. These are properties in the LogEntity classes added in the first code snippet. Notice also that it logs all messages logged by the asp.net infrastructure not just the test message that we added
Next steps
Our next step would be adding a middleware that will show a nice UI that will display log messages. This can come handy when you want to debug something and need to see the error messages