Skip to content

Instantly share code, notes, and snippets.

@GrimRob
Last active February 24, 2025 18:55
Show Gist options
  • Select an option

  • Save GrimRob/7cf4867efe1fd505940bd14fcb177fdc to your computer and use it in GitHub Desktop.

Select an option

Save GrimRob/7cf4867efe1fd505940bd14fcb177fdc to your computer and use it in GitHub Desktop.
Azure ExceptionLoggingMiddleware. App Insights is cumbersome and expensive. This middleware just logs exceptions to an Azure storage table.
using Azure.Data.Tables;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;
using Microsoft.Extensions.Logging;
using System.Reflection;
public class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
private readonly TableClient? _tableClient;
private readonly string _appName;
private readonly string _appVersion;
private readonly string _hostName;
public ExceptionLoggingMiddleware(ITableClientHelper tableClientHelper)
{
_tableClient = tableClientHelper.GetTableClient(TableClientHelper.ExceptionHandlingTable);
_appName = Assembly.GetEntryAssembly()!.GetName().Name!;
_appVersion = Assembly.GetEntryAssembly()!.GetName().Version?.ToString() ?? "N/A";
_hostName = Environment.MachineName;
}
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
var logger = context.GetLogger("GlobalExceptionHandler");
logger.LogError(ex, "An unhandled exception occurred.");
if (_tableClient != null) await LogExceptionToTableAsync(ex);
}
}
private async Task LogExceptionToTableAsync(Exception exception)
{
if (_tableClient == null) return;
var logEntry = new ExceptionLogEntity
{
PartitionKey = _appName,
Version = _appVersion,
RowKey = Guid.NewGuid().ToString(),
Timestamp = DateTime.UtcNow,
ExceptionType = exception.GetType().FullName,
ExceptionMessage = exception.Message,
StackTrace = exception.StackTrace,
Source = exception.Source,
HostName = _hostName,
};
await _tableClient.CreateIfNotExistsAsync();
await _tableClient.AddEntityAsync(logEntry);
}
}
public class ExceptionLogEntity : Azure.Data.Tables.ITableEntity
{
public required string PartitionKey { get; set; }
public required string Version { get; set; }
public required string RowKey { get; set; }
public required DateTimeOffset? Timestamp { get; set; }
public string? ExceptionType { get; internal set; }
public required string ExceptionMessage { get; set; }
public string? StackTrace { get; set; }
public string? Source { get; set; }
public string? HostName { get; set; }
public ETag ETag { get; set; } = new ETag("*");
public void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext)
{
PartitionKey = properties[nameof(PartitionKey)].StringValue;
RowKey = properties[nameof(RowKey)].StringValue;
Timestamp = properties[nameof(Timestamp)].DateTimeOffsetValue ?? default;
ExceptionType = properties[nameof(ExceptionType)].StringValue;
ExceptionMessage = properties[nameof(ExceptionMessage)].StringValue;
StackTrace = properties[nameof(StackTrace)].StringValue;
Source = properties[nameof(Source)].StringValue;
HostName = properties[nameof(HostName)].StringValue;
}
public IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{
var properties = new Dictionary<string, EntityProperty>
{
{ nameof(PartitionKey), new EntityProperty(PartitionKey) },
{ nameof(RowKey), new EntityProperty(RowKey) },
{ nameof(Timestamp), new EntityProperty(Timestamp) },
{ nameof(ExceptionMessage), new EntityProperty(ExceptionMessage) },
{ nameof(ExceptionType), new EntityProperty(ExceptionType) },
{ nameof(StackTrace), new EntityProperty(StackTrace) },
{ nameof(Source), new EntityProperty(Source) },
{ nameof(HostName), new EntityProperty(HostName) },
};
return properties;
}
}
public static class ApplicationBuilderExtensions
{
public static IFunctionsWorkerApplicationBuilder UseExceptionLoggingMiddleware(this IFunctionsWorkerApplicationBuilder worker)
{
worker.Services.AddSingleton(provider =>
{
var blobServiceClient = provider.GetRequiredService<BlobServiceClient>();
var tableClientHelper = provider.GetRequiredService<ITableClientHelper>();
return new ExceptionLoggingMiddleware(blobServiceClient, tableClientHelper);
});
return worker.UseMiddleware<ExceptionLoggingMiddleware>();
}
}
public class TableClientHelper : ITableClientHelper
{
public const string ExceptionHandlingTable = "exceptionHandling";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment