diff --git a/src/KafkaFlow.Abstractions/ILogHandler.cs b/src/KafkaFlow.Abstractions/ILogHandler.cs index 94d20f923..a079f653b 100644 --- a/src/KafkaFlow.Abstractions/ILogHandler.cs +++ b/src/KafkaFlow.Abstractions/ILogHandler.cs @@ -22,6 +22,14 @@ public interface ILogHandler /// Additional data related to the warning log void Warning(string message, object data); + /// + /// Writes a warning log entry + /// + /// Warning message + /// Exception thrown + /// Additional data related to the warning log + void Warning(string message, Exception ex, object data); + /// /// Writes a info log entry /// diff --git a/src/KafkaFlow.Abstractions/NullLogHandler.cs b/src/KafkaFlow.Abstractions/NullLogHandler.cs index 2c67ff4e5..445b553c5 100644 --- a/src/KafkaFlow.Abstractions/NullLogHandler.cs +++ b/src/KafkaFlow.Abstractions/NullLogHandler.cs @@ -19,6 +19,12 @@ public void Warning(string message, object data) // Do nothing } + /// + public void Warning(string message, Exception ex, object data) + { + // Do nothing + } + /// public void Info(string message, object data) { diff --git a/src/KafkaFlow.Admin/TelemetryScheduler.cs b/src/KafkaFlow.Admin/TelemetryScheduler.cs index 1bf26e466..a676fdfe2 100644 --- a/src/KafkaFlow.Admin/TelemetryScheduler.cs +++ b/src/KafkaFlow.Admin/TelemetryScheduler.cs @@ -96,7 +96,7 @@ private void ProduceTelemetry( } catch (Exception e) { - _logHandler.Warning("Error producing telemetry data", new { Exception = e }); + _logHandler.Warning("Error producing telemetry data", e, null); } } } diff --git a/src/KafkaFlow.LogHandler.Console/ConsoleLogHandler.cs b/src/KafkaFlow.LogHandler.Console/ConsoleLogHandler.cs index ee72782c6..b6a2ac7d1 100644 --- a/src/KafkaFlow.LogHandler.Console/ConsoleLogHandler.cs +++ b/src/KafkaFlow.LogHandler.Console/ConsoleLogHandler.cs @@ -24,6 +24,21 @@ public void Warning(string message, object data) => Print( $"\nKafkaFlow: {message} | Data: {JsonSerializer.Serialize(data)}", ConsoleColor.Yellow); + public void Warning(string message, Exception ex, object data) + { + var serializedException = JsonSerializer.Serialize( + new + { + Type = ex.GetType().FullName, + ex.Message, + ex.StackTrace, + }); + + Print( + $"\nKafkaFlow: {message} | Data: {JsonSerializer.Serialize(data)} | Exception: {serializedException}", + ConsoleColor.Yellow); + } + public void Info(string message, object data) => Print( $"\nKafkaFlow: {message} | Data: {JsonSerializer.Serialize(data)}", ConsoleColor.Green); diff --git a/src/KafkaFlow.LogHandler.Microsoft/MicrosoftLogHandler.cs b/src/KafkaFlow.LogHandler.Microsoft/MicrosoftLogHandler.cs index 99d42d3f1..07d0697b3 100644 --- a/src/KafkaFlow.LogHandler.Microsoft/MicrosoftLogHandler.cs +++ b/src/KafkaFlow.LogHandler.Microsoft/MicrosoftLogHandler.cs @@ -6,6 +6,8 @@ namespace KafkaFlow; internal class MicrosoftLogHandler : ILogHandler { + private const string SerializationErrorMessage = "Log data serialization error."; + private readonly ILogger _logger; public MicrosoftLogHandler(ILoggerFactory loggerFactory) @@ -15,21 +17,47 @@ public MicrosoftLogHandler(ILoggerFactory loggerFactory) public void Error(string message, Exception ex, object data) { - _logger.LogError(ex, "{Message} | Data: {Data}", message, JsonSerializer.Serialize(data)); + _logger.LogError(ex, "{Message} | Data: {Data}", message, SerializeData(data)); } public void Warning(string message, object data) { - _logger.LogWarning("{Message} | Data: {Data}", message, JsonSerializer.Serialize(data)); + _logger.LogWarning("{Message} | Data: {Data}", message, SerializeData(data)); + } + + public void Warning(string message, Exception ex, object data) + { + _logger.LogWarning(ex, "{Message} | Data: {Data}", message, SerializeData(data)); } public void Info(string message, object data) { - _logger.LogInformation("{Message} | Data: {Data}", message, JsonSerializer.Serialize(data)); + _logger.LogInformation("{Message} | Data: {Data}", message, SerializeData(data)); } public void Verbose(string message, object data) { - _logger.LogDebug("{Message} | Data: {Data}", message, JsonSerializer.Serialize(data)); + _logger.LogDebug("{Message} | Data: {Data}", message, SerializeData(data)); + } + + private string SerializeData(object data) + { + if (data is null) + { + return null; + } + + try + { + return JsonSerializer.Serialize(data); + } + catch(Exception ex) + { + return JsonSerializer.Serialize(new + { + Error = SerializationErrorMessage, + Exception = $"Message: {ex.Message}, Trace: {ex.StackTrace}", + }); + } } } diff --git a/tests/KafkaFlow.IntegrationTests/Core/TraceLogHandler.cs b/tests/KafkaFlow.IntegrationTests/Core/TraceLogHandler.cs index 801137b3c..f6192e766 100644 --- a/tests/KafkaFlow.IntegrationTests/Core/TraceLogHandler.cs +++ b/tests/KafkaFlow.IntegrationTests/Core/TraceLogHandler.cs @@ -29,6 +29,18 @@ public void Warning(string message, object data) })); } + public void Warning(string message, Exception ex, object data) + { + Trace.TraceWarning( + JsonSerializer.Serialize( + new + { + Message = message, + Exception = ex, + Data = data, + })); + } + public void Info(string message, object data) { Trace.TraceInformation( diff --git a/tests/KafkaFlow.UnitTests/LogHandlers/MicrosoftLogHandlerTests.cs b/tests/KafkaFlow.UnitTests/LogHandlers/MicrosoftLogHandlerTests.cs index 8f774cab0..a932385e8 100644 --- a/tests/KafkaFlow.UnitTests/LogHandlers/MicrosoftLogHandlerTests.cs +++ b/tests/KafkaFlow.UnitTests/LogHandlers/MicrosoftLogHandlerTests.cs @@ -1,6 +1,8 @@ using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using System; +using System.Linq; namespace KafkaFlow.UnitTests.LogHandlers; @@ -21,4 +23,31 @@ public void Constructor_CreatesNamedLogger() // Assert loggerFactoryMock.Verify(x => x.CreateLogger("KafkaFlow"), Times.Once); } + + [TestMethod] + public void LogWarning_WithNotSerializableData_DoesNotSerializeObject() + { + // Arrange + var expectedMessage = "Any message"; + var expectedSerializationErrorMessage = "Log data serialization error."; + var loggerMock = new Mock(); + var loggerFactoryMock = new Mock(); + + loggerFactoryMock + .Setup(x => x.CreateLogger("KafkaFlow")) + .Returns(loggerMock.Object); + + var handler = new MicrosoftLogHandler(loggerFactoryMock.Object); + + // Act + handler.Warning(expectedMessage, new + { + In = new IntPtr(1) + }); + + // Assert + Assert.AreEqual(1, loggerMock.Invocations.Count); + Assert.IsTrue(loggerMock.Invocations[0].Arguments.Any(x => x.ToString().Contains(expectedMessage))); + Assert.IsTrue(loggerMock.Invocations[0].Arguments.Any(x => x.ToString().Contains(expectedSerializationErrorMessage))); + } }