From 7fe6273797c9773f0b03ffda6d07e45d77bcdf7f Mon Sep 17 00:00:00 2001 From: Rung Date: Tue, 5 Aug 2025 12:23:40 +0700 Subject: [PATCH 1/4] fix: lower log level when a task operation is canceled --- .../Codehard.Functional.Logger/LoggerExtensions.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs b/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs index 4ca4af8..34af26d 100644 --- a/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs +++ b/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs @@ -17,7 +17,17 @@ private static Unit Log(this ILogger logger, Option errorOpt, LogLevel lo return error.Exception.Match( - Some: ex => logger.LogError(ex, "{Message}", error.Message), + Some: ex => + { + if (ex is OperationCanceledException && logLevel < LogLevel.Error) + { + logger.Log(logLevel, "{Message}", ex.Message); + } + else + { + logger.LogError(ex, "{Message}", error.Message); + } + }, None: () => { if (string.IsNullOrWhiteSpace(error.Message)) From 6574463525049abc982567e68de5203f47ed1269 Mon Sep 17 00:00:00 2001 From: Rung Date: Tue, 5 Aug 2025 16:35:57 +0700 Subject: [PATCH 2/4] Add exception handlers for better exception handling customization --- .../ControllerExtensionTests.cs | 285 ++++++++++++++++++ .../ControllerExtensions.cs | 2 +- .../LoggingExtesions.cs | 43 +++ .../DefaultExceptionHandler.cs | 35 +++ .../IExceptionHandler.cs | 8 + .../LoggerExtensions.cs | 59 ++-- 6 files changed, 400 insertions(+), 32 deletions(-) create mode 100644 src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs create mode 100644 src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests/ControllerExtensionTests.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests/ControllerExtensionTests.cs index 9af2117..78cd8ba 100644 --- a/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests/ControllerExtensionTests.cs +++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore.Tests/ControllerExtensionTests.cs @@ -1,6 +1,9 @@ using System.Net; +using LanguageExt; +using LanguageExt.Common; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Moq; using static LanguageExt.Prelude; @@ -47,4 +50,286 @@ await aff.MapFailToHttpResultError(expectedStatusCode, expectedData) Assert.IsType(actionResult); Assert.Equal(expectedData, headerDictionary["x-error-code"]); } + + [Fact] + public void WhenMatchToResultWithRegularError_ShouldLogWithWebApiExceptionHandler() + { + // Arrange + var error = Error.New(500, "Test error message"); + var fin = FinFail(error); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + + // Verify that Log was called with the error using WebApiExceptionHandler + // The WebApiExceptionHandler will log with Information level since there's no exception + mockLogger.Verify( + x => x.Log( + LogLevel.Information, + It.IsAny(), + It.Is((o, t) => + o.ToString()!.Contains("500") && o.ToString()!.Contains("Test error message")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [Fact] + public void WhenMatchToResultWithHttpResultError_ShouldLogWithoutWebApiExceptionHandler() + { + // Arrange + var httpError = HttpResultError.New(HttpStatusCode.BadRequest, "Bad request error"); + var fin = FinFail(httpError); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + + // Verify that Log was called for HttpResultError (uses different logging path - LoggingExtensions.Log) + mockLogger.Verify( + x => x.Log( + LogLevel.Warning, + It.IsAny(), + It.Is((o, t) => + o.ToString()!.Contains("BadRequest") && o.ToString()!.Contains("Bad request error")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [Fact] + public void WhenMatchToResultWithRegularErrorAndNullLogger_ShouldNotThrow() + { + // Arrange + var error = Error.New(500, "Test error message"); + var fin = FinFail(error); + + // Act & Assert + var exception = Record.Exception(() => fin.MatchToResult(logger: null)); + Assert.Null(exception); + + var result = fin.MatchToResult(logger: null); + Assert.IsType(result); + } + + [Fact] + public void WhenMatchToResultWithErrorContainingException_ShouldLogAsError() + { + // Arrange + var exception = new InvalidOperationException("Test exception"); + var error = Error.New(exception); + var fin = FinFail(error); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + + // Verify that LogError was called due to the exception (WebApiExceptionHandler behavior) + mockLogger.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + It.Is(ex => ex == exception), + It.IsAny>()), + Times.Once); + } + + [Fact] + public void WhenMatchToResultWithTaskCanceledException_ShouldLogAsInformation() + { + // Arrange + var exception = new TaskCanceledException("Operation was canceled"); + var error = Error.New(exception); + var fin = FinFail(error); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + + // Verify that Log was called with Information level for TaskCanceledException + // WebApiExceptionHandler has special handling for TaskCanceledException + mockLogger.Verify( + x => x.Log( + LogLevel.Information, + It.IsAny(), + It.Is((o, t) => + o.ToString()!.Contains("Operation was canceled")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [Fact] + public void WhenMatchToResultWithOperationCanceledException_ShouldLogAsInformation() + { + // Arrange + var exception = new OperationCanceledException("Operation was canceled"); + var error = Error.New(exception); + var fin = FinFail(error); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + + // Verify that Log was called with Information level for OperationCanceledException + // WebApiExceptionHandler has special handling for OperationCanceledException + mockLogger.Verify( + x => x.Log( + LogLevel.Information, + It.IsAny(), + It.Is((o, t) => + o.ToString()!.Contains("Operation was canceled")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [Fact] + public void WhenMatchToResultWithRegularErrorWithoutMessage_ShouldLogCodeOnly() + { + // Arrange + var error = Error.New(404, ""); // Empty message + var fin = FinFail(error); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + + // Verify that Log was called with just the code when message is empty + // This tests the WebApiExceptionHandler behavior for empty messages + mockLogger.Verify( + x => x.Log( + LogLevel.Information, + It.IsAny(), + It.Is((o, t) => + o.ToString()!.Contains("404") && !o.ToString()!.Contains(":")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [Fact] + public void WhenMatchToResultWithSuccessValue_ShouldNotLog() + { + // Arrange + var fin = FinSucc("success value"); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + var objectResult = (ObjectResult)result; + Assert.Equal("success value", objectResult.Value); + Assert.Equal(200, objectResult.StatusCode); + + // Verify that no logging occurred for successful results + mockLogger.Verify( + x => x.Log( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>()), + Times.Never); + } + + [Fact] + public void WhenMatchToResultOptionWithRegularError_ShouldLogWithoutWebApiExceptionHandler() + { + // Arrange + var error = Error.New(500, "Test error message"); + var fin = FinFail>(error); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + + // Verify that Log was called WITHOUT WebApiExceptionHandler (different from regular MatchToResult) + // This method uses the default exception handler, not WebApiExceptionHandler.Instance + mockLogger.Verify( + x => x.Log( + LogLevel.Information, + It.IsAny(), + It.Is((o, t) => + o.ToString()!.Contains("500") && o.ToString()!.Contains("Test error message")), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + [Fact] + public void WhenMatchToResultOptionWithSomeValue_ShouldReturnObjectResult() + { + // Arrange + var fin = FinSucc(Some("test value")); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + var objectResult = (ObjectResult)result; + Assert.Equal("test value", objectResult.Value); + Assert.Equal(200, objectResult.StatusCode); + + // Verify that no logging occurred for successful results + mockLogger.Verify( + x => x.Log( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>()), + Times.Never); + } + + [Fact] + public void WhenMatchToResultOptionWithNoneValue_ShouldReturnNotFound() + { + // Arrange + var fin = FinSucc(Option.None); + var mockLogger = new Mock(); + + // Act + var result = fin.MatchToResult(logger: mockLogger.Object); + + // Assert + Assert.IsType(result); + + // Verify that no logging occurred for None results + mockLogger.Verify( + x => x.Log( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>()), + Times.Never); + } } \ No newline at end of file diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs index b560eaf..e068268 100644 --- a/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs +++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs @@ -61,7 +61,7 @@ public static IActionResult MatchToResult( logger?.Log(hre); return MapErrorToActionResult(hre); default: - logger?.Log(err); + logger?.Log(err, exceptionHandler: WebApiExceptionHandler.Instance); return MapErrorToActionResult(err); } }); diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore/LoggingExtesions.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore/LoggingExtesions.cs index 049362e..ab08aaa 100644 --- a/src/Codehard.Functional/Codehard.Functional.AspNetCore/LoggingExtesions.cs +++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore/LoggingExtesions.cs @@ -43,4 +43,47 @@ public static void Log(this ILogger logger, HttpResultError error) break; } } +} + +/// +/// Provides a centralized mechanism for handling exceptions and logging errors in a web API context. +/// Implements the interface to process and log errors with optional context-specific behavior. +/// +public class WebApiExceptionHandler : IExceptionHandler +{ + private static WebApiExceptionHandler instance = new(); + + public static WebApiExceptionHandler Instance => instance; + + private WebApiExceptionHandler() { } + + public Unit Handle(Error error, ILogger logger, LogLevel logLevel = LogLevel.Information) + { + error.Exception.Match( + Some: ex => + { + if (ex is OperationCanceledException or TaskCanceledException && + logLevel < LogLevel.Error) + { + logger.Log(logLevel, "{Message}", ex.Message); + } + else + { + logger.LogError(ex, "{Message}", error.Message); + } + }, + None: () => + { + if (string.IsNullOrWhiteSpace(error.Message)) + { + logger.Log(logLevel, "{Code}", error.Code); + } + else + { + logger.Log(logLevel, "{Code} : {Message}", error.Code, error.Message); + } + }); + + return unit; + } } \ No newline at end of file diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs b/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs new file mode 100644 index 0000000..90834fb --- /dev/null +++ b/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs @@ -0,0 +1,35 @@ +using LanguageExt.Common; + +namespace Codehard.Functional.Logger; + +public class DefaultExceptionHandler : IExceptionHandler +{ + private static DefaultExceptionHandler instance = new DefaultExceptionHandler(); + + public static DefaultExceptionHandler Instance => instance; + + private DefaultExceptionHandler() { } + + public Unit Handle(Error error, ILogger logger, LogLevel logLevel = LogLevel.Information) + { + error.Exception.Match( + Some: ex => + { + // Exception will always log as an error + logger.LogError(ex, "{Message}", error.Message); + }, + None: () => + { + if (string.IsNullOrWhiteSpace(error.Message)) + { + logger.Log(logLevel, "{Code}", error.Code); + } + else + { + logger.Log(logLevel, "{Code} : {Message}", error.Code, error.Message); + } + }); + + return unit; + } +} \ No newline at end of file diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs b/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs new file mode 100644 index 0000000..ee7a7e3 --- /dev/null +++ b/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs @@ -0,0 +1,8 @@ +using LanguageExt.Common; + +namespace Codehard.Functional.Logger; + +public interface IExceptionHandler +{ + Unit Handle(Error error, ILogger logger, LogLevel logLevel = LogLevel.Information); +} \ No newline at end of file diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs b/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs index 34af26d..2b8062f 100644 --- a/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs +++ b/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs @@ -7,7 +7,11 @@ namespace Codehard.Functional.Logger; /// public static class LoggerExtensions { - private static Unit Log(this ILogger logger, Option errorOpt, LogLevel logLevel = LogLevel.Information) + private static Unit Log( + this ILogger logger, + Option errorOpt, + LogLevel logLevel = LogLevel.Information, + IExceptionHandler? exceptionHandler = null) { return errorOpt.Match( @@ -15,30 +19,8 @@ private static Unit Log(this ILogger logger, Option errorOpt, LogLevel lo { Log(logger, error.Inner, logLevel); - return - error.Exception.Match( - Some: ex => - { - if (ex is OperationCanceledException && logLevel < LogLevel.Error) - { - logger.Log(logLevel, "{Message}", ex.Message); - } - else - { - logger.LogError(ex, "{Message}", error.Message); - } - }, - None: () => - { - if (string.IsNullOrWhiteSpace(error.Message)) - { - logger.Log(logLevel, "{Code}", error.Code); - } - else - { - logger.Log(logLevel, "{Code} : {Message}", error.Code, error.Message); - } - }); + return exceptionHandler?.Handle(error, logger, logLevel) ?? + DefaultExceptionHandler.Instance.Handle(error, logger, logLevel); }, None: unit); } @@ -68,10 +50,15 @@ public static Unit Log( /// /// /// + /// /// - public static Unit Log(this ILogger logger, Error error, LogLevel logLevel = LogLevel.Information) + public static Unit Log( + this ILogger logger, + Error error, + LogLevel logLevel = LogLevel.Information, + IExceptionHandler? exceptionHandler = null) { - Log(logger, Some(error), logLevel); + Log(logger, Some(error), logLevel, exceptionHandler); return unit; } @@ -83,10 +70,15 @@ public static Unit Log(this ILogger logger, Error error, LogLevel logLevel = Log /// /// /// + /// /// - public static Error DoLog(this ILogger logger, Error error, LogLevel logLevel = LogLevel.Information) + public static Error DoLog( + this ILogger logger, + Error error, + LogLevel logLevel = LogLevel.Information, + IExceptionHandler? exceptionHandler = null) { - Log(logger, Some(error), logLevel); + Log(logger, Some(error), logLevel, exceptionHandler); return error; } @@ -97,11 +89,16 @@ public static Error DoLog(this ILogger logger, Error error, LogLevel logLevel = /// /// /// + /// /// /// - public static Fin LogIfFail(this ILogger logger, Fin fin, LogLevel logLevel = LogLevel.Information) + public static Fin LogIfFail( + this ILogger logger, + Fin fin, + LogLevel logLevel = LogLevel.Information, + IExceptionHandler? exceptionHandler = null) { - fin.IfFail(err => Log(logger, err, logLevel)); + fin.IfFail(err => Log(logger, err, logLevel, exceptionHandler)); return fin; } From bf7ad48847f3f5739420df189a3c22aed1624aa8 Mon Sep 17 00:00:00 2001 From: Rung Date: Tue, 5 Aug 2025 16:44:06 +0700 Subject: [PATCH 3/4] doc: add code documents --- .../DefaultExceptionHandler.cs | 6 ++++++ .../IExceptionHandler.cs | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs b/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs index 90834fb..2f31edc 100644 --- a/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs +++ b/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs @@ -2,6 +2,12 @@ namespace Codehard.Functional.Logger; +/// +/// Provides a default implementation for handling exceptions in a logging context. +/// This class is intended to standardize how exceptions and errors are logged +/// based on the provided parameters such as the error details, logger instance, +/// and log level. +/// public class DefaultExceptionHandler : IExceptionHandler { private static DefaultExceptionHandler instance = new DefaultExceptionHandler(); diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs b/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs index ee7a7e3..e4ac398 100644 --- a/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs +++ b/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs @@ -2,7 +2,21 @@ namespace Codehard.Functional.Logger; +/// +/// An interface for handling exceptions and errors in a structured and customizable manner. +/// This interface allows implementations to define how errors are processed and logged +/// using a specified logging framework and log level. +/// public interface IExceptionHandler { + /// + /// Handles an error by logging it using the provided logger at the specified log level. + /// Can use a custom logic defined in the implementing class to process and log errors based + /// on the error details, associated exception, and log level. + /// + /// The error object encapsulating exception details and an optional message. + /// The logger instance used to log the error details. + /// The severity level at which the error should be logged. Defaults to . + /// A value indicating the outcome of the operation. Unit Handle(Error error, ILogger logger, LogLevel logLevel = LogLevel.Information); } \ No newline at end of file From 56e31e82f619a49ecf95d9746b2ddccc81aeb9d8 Mon Sep 17 00:00:00 2001 From: Rung Date: Tue, 5 Aug 2025 18:54:13 +0700 Subject: [PATCH 4/4] refactor: change to delegate --- .../ControllerExtensions.cs | 10 ++++---- .../LoggingExtesions.cs | 22 ++++++++---------- .../DefaultExceptionHandler.cs | 23 ++++++++----------- .../IExceptionHandler.cs | 22 ------------------ .../LoggerExtensions.cs | 20 +++++++++++----- 5 files changed, 38 insertions(+), 59 deletions(-) delete mode 100644 src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs index e068268..5ac9e25 100644 --- a/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs +++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore/ControllerExtensions.cs @@ -48,7 +48,7 @@ private static IActionResult MapErrorToActionResult(Error err) public static IActionResult MatchToResult( this Fin fin, HttpStatusCode successStatusCode = HttpStatusCode.OK, - ILogger? logger = default) + ILogger? logger = null) { return fin .Match( @@ -61,7 +61,7 @@ public static IActionResult MatchToResult( logger?.Log(hre); return MapErrorToActionResult(hre); default: - logger?.Log(err, exceptionHandler: WebApiExceptionHandler.Instance); + logger?.Log(err, exceptionHandler: WebApiExceptionHandler.Handle); return MapErrorToActionResult(err); } }); @@ -74,7 +74,7 @@ public static IActionResult MatchToResult( public static IActionResult MatchToResult( this Fin> fin, HttpStatusCode successStatusCode = HttpStatusCode.OK, - ILogger? logger = default) + ILogger? logger = null) { return fin .Match( @@ -107,7 +107,7 @@ public static IActionResult MatchToResult( public static ValueTask RunToResultAsync( this Aff aff, HttpStatusCode successStatusCode = HttpStatusCode.OK, - ILogger? logger = default) + ILogger? logger = null) { return aff .Run() @@ -123,7 +123,7 @@ public static ValueTask RunToResultAsync( public static IActionResult RunToResult( this Eff eff, HttpStatusCode successStatusCode = HttpStatusCode.OK, - ILogger? logger = default) + ILogger? logger = null) { return eff .Run() diff --git a/src/Codehard.Functional/Codehard.Functional.AspNetCore/LoggingExtesions.cs b/src/Codehard.Functional/Codehard.Functional.AspNetCore/LoggingExtesions.cs index ab08aaa..0d57a2f 100644 --- a/src/Codehard.Functional/Codehard.Functional.AspNetCore/LoggingExtesions.cs +++ b/src/Codehard.Functional/Codehard.Functional.AspNetCore/LoggingExtesions.cs @@ -46,18 +46,18 @@ public static void Log(this ILogger logger, HttpResultError error) } /// -/// Provides a centralized mechanism for handling exceptions and logging errors in a web API context. -/// Implements the interface to process and log errors with optional context-specific behavior. +/// Provides methods to handle and log exceptions in a structured format for web APIs. /// -public class WebApiExceptionHandler : IExceptionHandler +public static class WebApiExceptionHandler { - private static WebApiExceptionHandler instance = new(); - - public static WebApiExceptionHandler Instance => instance; - - private WebApiExceptionHandler() { } - - public Unit Handle(Error error, ILogger logger, LogLevel logLevel = LogLevel.Information) + /// + /// Handles the specified error by logging it using the provided logger + /// and applying the given log level. + /// + /// The logger used to record the error information. + /// The error instance containing details to handle and log. + /// The severity level of the log entry. Defaults to Information. + public static void Handle(ILogger logger, Error error, LogLevel logLevel = LogLevel.Information) { error.Exception.Match( Some: ex => @@ -83,7 +83,5 @@ public Unit Handle(Error error, ILogger logger, LogLevel logLevel = LogLevel.Inf logger.Log(logLevel, "{Code} : {Message}", error.Code, error.Message); } }); - - return unit; } } \ No newline at end of file diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs b/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs index 2f31edc..2fa1c3a 100644 --- a/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs +++ b/src/Codehard.Functional/Codehard.Functional.Logger/DefaultExceptionHandler.cs @@ -3,20 +3,17 @@ namespace Codehard.Functional.Logger; /// -/// Provides a default implementation for handling exceptions in a logging context. -/// This class is intended to standardize how exceptions and errors are logged -/// based on the provided parameters such as the error details, logger instance, -/// and log level. +/// Provides a default exception handling mechanism for logging errors using an instance. /// -public class DefaultExceptionHandler : IExceptionHandler +public static class DefaultExceptionHandler { - private static DefaultExceptionHandler instance = new DefaultExceptionHandler(); - - public static DefaultExceptionHandler Instance => instance; - - private DefaultExceptionHandler() { } - - public Unit Handle(Error error, ILogger logger, LogLevel logLevel = LogLevel.Information) + /// + /// Handles logging of an error using the specified instance. + /// + /// The logger used to log the error information. + /// The error to be logged, containing an optional exception and message. + /// The logging level to use, defaults to . + public static void Handle(ILogger logger, Error error, LogLevel logLevel = LogLevel.Information) { error.Exception.Match( Some: ex => @@ -35,7 +32,5 @@ public Unit Handle(Error error, ILogger logger, LogLevel logLevel = LogLevel.Inf logger.Log(logLevel, "{Code} : {Message}", error.Code, error.Message); } }); - - return unit; } } \ No newline at end of file diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs b/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs deleted file mode 100644 index e4ac398..0000000 --- a/src/Codehard.Functional/Codehard.Functional.Logger/IExceptionHandler.cs +++ /dev/null @@ -1,22 +0,0 @@ -using LanguageExt.Common; - -namespace Codehard.Functional.Logger; - -/// -/// An interface for handling exceptions and errors in a structured and customizable manner. -/// This interface allows implementations to define how errors are processed and logged -/// using a specified logging framework and log level. -/// -public interface IExceptionHandler -{ - /// - /// Handles an error by logging it using the provided logger at the specified log level. - /// Can use a custom logic defined in the implementing class to process and log errors based - /// on the error details, associated exception, and log level. - /// - /// The error object encapsulating exception details and an optional message. - /// The logger instance used to log the error details. - /// The severity level at which the error should be logged. Defaults to . - /// A value indicating the outcome of the operation. - Unit Handle(Error error, ILogger logger, LogLevel logLevel = LogLevel.Information); -} \ No newline at end of file diff --git a/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs b/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs index 2b8062f..8182304 100644 --- a/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs +++ b/src/Codehard.Functional/Codehard.Functional.Logger/LoggerExtensions.cs @@ -11,7 +11,7 @@ private static Unit Log( this ILogger logger, Option errorOpt, LogLevel logLevel = LogLevel.Information, - IExceptionHandler? exceptionHandler = null) + Action? exceptionHandler = null) { return errorOpt.Match( @@ -19,8 +19,16 @@ private static Unit Log( { Log(logger, error.Inner, logLevel); - return exceptionHandler?.Handle(error, logger, logLevel) ?? - DefaultExceptionHandler.Instance.Handle(error, logger, logLevel); + if (exceptionHandler != null) + { + exceptionHandler?.Invoke(logger, error, logLevel); + } + else + { + DefaultExceptionHandler.Handle(logger, error, logLevel); + } + + return unit; }, None: unit); } @@ -56,7 +64,7 @@ public static Unit Log( this ILogger logger, Error error, LogLevel logLevel = LogLevel.Information, - IExceptionHandler? exceptionHandler = null) + Action? exceptionHandler = null) { Log(logger, Some(error), logLevel, exceptionHandler); @@ -76,7 +84,7 @@ public static Error DoLog( this ILogger logger, Error error, LogLevel logLevel = LogLevel.Information, - IExceptionHandler? exceptionHandler = null) + Action? exceptionHandler = null) { Log(logger, Some(error), logLevel, exceptionHandler); @@ -96,7 +104,7 @@ public static Fin LogIfFail( this ILogger logger, Fin fin, LogLevel logLevel = LogLevel.Information, - IExceptionHandler? exceptionHandler = null) + Action? exceptionHandler = null) { fin.IfFail(err => Log(logger, err, logLevel, exceptionHandler));