From c1528db7df9a88667eb5e651790acefa9ff70ec6 Mon Sep 17 00:00:00 2001 From: Rolf Kristensen Date: Tue, 14 May 2019 22:13:40 +0200 Subject: [PATCH] ${whenEmpty} implemented IRawValue and IStringValueRenderer (#3398) * WhenEmptyLayoutRendererWrapper with IRawValue and IStringValueRenderer * refactor * added test --- .../WhenEmptyLayoutRendererWrapper.cs | 67 ++++++++++++++++++- .../Wrappers/WhenEmptyTests.cs | 43 ++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs b/src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs index 13a304550a..505bc0f140 100644 --- a/src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs +++ b/src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs @@ -36,6 +36,7 @@ namespace NLog.LayoutRenderers.Wrappers using System; using System.Text; using NLog.Config; + using NLog.Internal; using NLog.Layouts; /// @@ -43,11 +44,12 @@ namespace NLog.LayoutRenderers.Wrappers /// [LayoutRenderer("whenEmpty")] [AmbientProperty("WhenEmpty")] - [AppDomainFixedOutput] [ThreadAgnostic] [ThreadSafe] - public sealed class WhenEmptyLayoutRendererWrapper : WrapperLayoutRendererBuilderBase + public sealed class WhenEmptyLayoutRendererWrapper : WrapperLayoutRendererBuilderBase, IRawValue, IStringValueRenderer { + private bool _skipStringValueRenderer; + /// /// Gets or sets the layout to be rendered when original layout produced empty result. /// @@ -60,6 +62,7 @@ protected override void InitializeLayoutRenderer() { base.InitializeLayoutRenderer(); WhenEmpty?.Initialize(LoggingConfiguration); + _skipStringValueRenderer = !TryGetStringValue(out _, out _); } /// @@ -73,6 +76,66 @@ protected override void RenderInnerAndTransform(LogEventInfo logEvent, StringBui WhenEmpty.RenderAppendBuilder(logEvent, builder); } + string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) + { + if (_skipStringValueRenderer) + { + return null; + } + + if (TryGetStringValue(out var innerLayout, out var whenEmptyLayout)) + { + var innerValue = innerLayout.Render(logEvent); + if (!string.IsNullOrEmpty(innerValue)) + { + return innerValue; + } + + // render WhenEmpty when the inner layout was empty + return whenEmptyLayout.Render(logEvent); + } + + _skipStringValueRenderer = true; + return null; + } + + private bool TryGetStringValue(out SimpleLayout innerLayout, out SimpleLayout whenEmptyLayout) + { + whenEmptyLayout = WhenEmpty as SimpleLayout; + innerLayout = Inner as SimpleLayout; + + return IsStringLayout(innerLayout) && IsStringLayout(whenEmptyLayout); + } + + private static bool IsStringLayout(SimpleLayout innerLayout) + { + return innerLayout != null && (innerLayout.IsFixedText || innerLayout.IsSimpleStringText); + } + + bool IRawValue.TryGetRawValue(LogEventInfo logEvent, out object value) + { + if (Inner.TryGetRawValue(logEvent, out var innerValue)) + { + if (innerValue != null && !innerValue.Equals(string.Empty)) + { + value = innerValue; + return true; + } + } + else + { + var innerResult = Inner.Render(logEvent); // Beware this can be very expensive call + if (!string.IsNullOrEmpty(innerResult)) + { + value = null; + return false; + } + } + + // render WhenEmpty when the inner layout was empty + return WhenEmpty.TryGetRawValue(logEvent, out value); + } + /// [Obsolete("Inherit from WrapperLayoutRendererBase and override RenderInnerAndTransform() instead. Marked obsolete in NLog 4.6")] protected override void TransformFormattedMesssage(StringBuilder target) diff --git a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/WhenEmptyTests.cs b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/WhenEmptyTests.cs index 6521b42f8b..f29319c86f 100644 --- a/tests/NLog.UnitTests/LayoutRenderers/Wrappers/WhenEmptyTests.cs +++ b/tests/NLog.UnitTests/LayoutRenderers/Wrappers/WhenEmptyTests.cs @@ -31,8 +31,11 @@ // THE POSSIBILITY OF SUCH DAMAGE. // +using NLog.Internal; + namespace NLog.UnitTests.LayoutRenderers.Wrappers { + using System; using NLog; using NLog.Layouts; using Xunit; @@ -73,5 +76,45 @@ public void WhenEmpty_MissingInner_ShouldNotThrow() var le = LogEventInfo.Create(LogLevel.Info, "logger", "message"); Assert.Equal("api.log", l.Render(le)); } + + [Fact] + public void WhenDbNullRawValueShouldWork() + { + SimpleLayout l = @"${event-properties:prop1:whenEmpty=${db-null}}"; + { + var le = LogEventInfo.Create(LogLevel.Info, "logger", "message"); + le.Properties["prop1"] = 1; + var success = l.TryGetRawValue(le, out var rawValue); + Assert.True(success); + Assert.Equal(1, rawValue); + } + // empty log message + { + var le = LogEventInfo.Create(LogLevel.Info, "logger", "message"); + var success = l.TryGetRawValue(le, out var rawValue); + Assert.True(success); + Assert.Equal(DBNull.Value, rawValue); + } + + } + + [Theory] + [InlineData("message", "message")] + [InlineData("", "default")] + public void GetStringValueShouldWork(string message, string expected) + { + // Arrange + SimpleLayout layout = @"${message:whenEmpty=default}"; + var stringValueRenderer = (IStringValueRenderer)layout.Renderers[0]; + var logEvent = LogEventInfo.Create(LogLevel.Info, "logger", message); + + // Act + var result = stringValueRenderer.GetFormattedString(logEvent); + + // Assert + Assert.Equal(expected, result); + + + } } } \ No newline at end of file