Last active
February 24, 2025 18:55
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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