From 1961574964c9518f5f1582193d5dd56771202c59 Mon Sep 17 00:00:00 2001 From: ChiliMonanta Date: Tue, 19 Dec 2017 15:10:37 +0100 Subject: [PATCH] Change, It's now possible to configure number of inner exceptions to be sent Sometimes you need more than the default number of inner exceptions. This is now possible to configurate. Default value is not changed, it's still one. --- README.md | 5 + source/log4net-loggly-console/App.config | 1 + source/log4net-loggly-console/Program.cs | 32 +++++- .../LogglyFormatterTest.cs | 108 +++++++++++++++++- .../log4net-loggly/ILogglyAppenderConfig.cs | 1 + source/log4net-loggly/LogglyAppender.cs | 1 + source/log4net-loggly/LogglyAppenderConfig.cs | 4 + source/log4net-loggly/LogglyFormatter.cs | 33 ++++-- 8 files changed, 171 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 01f6a7a..9bd963e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Add the following code in your web.config to configure LogglyAppender in your ap + ``` @@ -47,6 +48,10 @@ By default, library uses Loggly /bulk end point (https://www.loggly.com/docs/htt ``` +Set number of inner exceptions that should be sent to loggly, if you don't want the default value. +``` + +``` Add the following entry in your AssemblyInfo.cs ``` diff --git a/source/log4net-loggly-console/App.config b/source/log4net-loggly-console/App.config index 7308018..1152a75 100644 --- a/source/log4net-loggly-console/App.config +++ b/source/log4net-loggly-console/App.config @@ -15,6 +15,7 @@ + diff --git a/source/log4net-loggly-console/Program.cs b/source/log4net-loggly-console/Program.cs index b191faf..264df22 100644 --- a/source/log4net-loggly-console/Program.cs +++ b/source/log4net-loggly-console/Program.cs @@ -77,7 +77,7 @@ static void Main(string[] argArray) }); newThread2.Start(); - + //Test self referencing var parent = new Person { Name = "John Smith" }; var child1 = new Person { Name = "Bob Smith", Parent = parent }; @@ -91,6 +91,36 @@ static void Main(string[] argArray) log.Info(new TestObject()); log.Info(null); + try + { + try + { + try + { + try + { + throw new Exception("1"); + } + catch (Exception e) + { + throw new Exception("2", e); + } + } + catch (Exception e) + { + throw new Exception("3", e); + } + } + catch (Exception e) + { + throw new Exception("4", e); + } + } + catch (Exception e) + { + log.Error("Exception", e); + } + Console.ReadKey(); } } diff --git a/source/log4net-loggly.UnitTests/LogglyFormatterTest.cs b/source/log4net-loggly.UnitTests/LogglyFormatterTest.cs index a3fc05e..833c82d 100644 --- a/source/log4net-loggly.UnitTests/LogglyFormatterTest.cs +++ b/source/log4net-loggly.UnitTests/LogglyFormatterTest.cs @@ -7,7 +7,7 @@ using log4net.Core; using log4net.loggly; using log4net.Repository; - using log4net_loggly.UnitTests.Models; + using Models; using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -304,6 +304,58 @@ public void ShouldSerializeTheException() stacktrace.Should().NotBeNull("because the exception has a stacktrace"); } + [Theory] + [InlineData(0, 0, 1)] + [InlineData(1, 1, 1)] + [InlineData(2, 1, 1)] + [InlineData(2, 2, 2)] + [InlineData(2, 2, 3)] + [InlineData(3, 3, 3)] + [InlineData(5, 5, 5)] + [InlineData(5, 5, 10)] + public void ShouldSerializeInnerExceptions(int configurationNumberOfInnerExceptions, int expectedNumberOfException, int innerExceptionsToCreate) + { + Exception ex = GetArgumentException(innerExceptionsToCreate + 1); + + var evt = new LoggingEvent( + GetType(), + Mock.Of(), + _fixture.Create("loggerName"), + _fixture.Create(), + _fixture.Create("message"), + ex); + var instance = _fixture.Create(); + _fixture.Freeze>().SetupGet(x => x.NumberOfInnerExceptions).Returns(configurationNumberOfInnerExceptions); + + var result = instance.ToJson(evt); + dynamic json = JObject.Parse(result); + + var exception = json.exception; + + ((object)exception).Should().NotBeNull("because an exception was specified in the event"); + + // Validate first level + var message = (string)exception.exceptionMessage; + var type = (string)exception.exceptionType; + var stacktrace = (string)exception.stacktrace; + AssertException(message, type, stacktrace, 0); + + // Validate inner exceptions + var count = 0; + var innerException = exception.innerException; + while (innerException != null) + { + count++; + message = (string)innerException.innerExceptionMessage; + type = (string)innerException.innerExceptionType; + stacktrace = (string)innerException.innerStacktrace; + AssertException(message, type, stacktrace, count); + innerException = innerException.innerException; + } + + count.Should().Be(expectedNumberOfException, "Expects all stacktraces"); + } + [Fact] public void ShouldSerializeThreadContextProperties() { @@ -355,6 +407,60 @@ public void ShouldSetMessagePropertyWhenMessageObjectIsString() message.Should().StartWith("message", "because the MessageObject property value is used"); } + + private static ArgumentException GetArgumentException(int numberOfExceptions) + { + try + { + if (--numberOfExceptions > 0) + { + try + { + GetNestedArgumentException(numberOfExceptions); + } + catch (ArgumentException e) + { + throw new ArgumentException("Exception 0", e); + } + } + else + { + throw new ArgumentException("Exception 0"); + } + } + catch (ArgumentException e) + { + return e; + } + return null; + } + + private static void GetNestedArgumentException(int numberOfExceptions, int deep = 0) + { + deep++; + if (--numberOfExceptions > 0) + { + try + { + GetNestedArgumentException(numberOfExceptions, deep); + } + catch (ArgumentException e) + { + throw new ArgumentException($"Exception {deep}", e); + } + } + else + { + throw new ArgumentException($"Exception {deep}"); + } + } + + private static void AssertException(string message, string type, string stacktrace, int stackLevel) + { + message.Should().Be($"Exception {stackLevel}", "because an argument exception has a default message"); + type.Should().Be(typeof(ArgumentException).FullName, "because we logged an argument exception"); + stacktrace.Should().NotBeNull("because the exception has a stacktrace"); + } } } } diff --git a/source/log4net-loggly/ILogglyAppenderConfig.cs b/source/log4net-loggly/ILogglyAppenderConfig.cs index 6a63d1e..5d081a1 100644 --- a/source/log4net-loggly/ILogglyAppenderConfig.cs +++ b/source/log4net-loggly/ILogglyAppenderConfig.cs @@ -11,5 +11,6 @@ public interface ILogglyAppenderConfig string LogicalThreadContextKeys { get; set; } string GlobalContextKeys { get; set; } int BufferSize { get; set; } + int NumberOfInnerExceptions { get; set; } } } \ No newline at end of file diff --git a/source/log4net-loggly/LogglyAppender.cs b/source/log4net-loggly/LogglyAppender.cs index 11f7993..cd29b16 100644 --- a/source/log4net-loggly/LogglyAppender.cs +++ b/source/log4net-loggly/LogglyAppender.cs @@ -26,6 +26,7 @@ public class LogglyAppender : AppenderSkeleton public string LogicalThreadContextKeys { set { Config.LogicalThreadContextKeys = value; } } public string GlobalContextKeys { set { Config.GlobalContextKeys = value; } } public int BufferSize { set { Config.BufferSize = value; } } + public int NumberOfInnerExceptions { set { Config.NumberOfInnerExceptions = value; } } private LogglyAsyncHandler LogglyAsync; diff --git a/source/log4net-loggly/LogglyAppenderConfig.cs b/source/log4net-loggly/LogglyAppenderConfig.cs index 3d2b316..9cbaf85 100644 --- a/source/log4net-loggly/LogglyAppenderConfig.cs +++ b/source/log4net-loggly/LogglyAppenderConfig.cs @@ -44,6 +44,9 @@ public string LogMode public string GlobalContextKeys { get; set; } public int BufferSize { get; set; } + + public int NumberOfInnerExceptions { get; set; } + public LogglyAppenderConfig() { UserAgent = "loggly-log4net-appender"; @@ -53,6 +56,7 @@ public LogglyAppenderConfig() LogicalThreadContextKeys = null; GlobalContextKeys = null; BufferSize = 500; + NumberOfInnerExceptions = 1; } } } \ No newline at end of file diff --git a/source/log4net-loggly/LogglyFormatter.cs b/source/log4net-loggly/LogglyFormatter.cs index 401c36a..e92d63e 100644 --- a/source/log4net-loggly/LogglyFormatter.cs +++ b/source/log4net-loggly/LogglyFormatter.cs @@ -80,20 +80,29 @@ private object GetExceptionInfo(LoggingEvent loggingEvent) exceptionInfo.exceptionType = loggingEvent.ExceptionObject.GetType().FullName; exceptionInfo.exceptionMessage = loggingEvent.ExceptionObject.Message; exceptionInfo.stacktrace = loggingEvent.ExceptionObject.StackTrace; + exceptionInfo.innerException = + GetInnerExceptions(loggingEvent.ExceptionObject.InnerException, Config.NumberOfInnerExceptions); + + return exceptionInfo; + } - //most of the times dotnet exceptions contain important messages in the inner exceptions - if (loggingEvent.ExceptionObject.InnerException != null) + /// + /// Return nested exceptions + /// + /// The inner exception + /// The number of inner exceptions that should be included. + /// + private object GetInnerExceptions(Exception innerException, int deep) + { + if (innerException == null || deep <= 0) return null; + dynamic ex = new { - dynamic innerException = new - { - innerExceptionType = loggingEvent.ExceptionObject.InnerException.GetType().FullName, - innerExceptionMessage = loggingEvent.ExceptionObject.InnerException.Message, - innerStacktrace = loggingEvent.ExceptionObject.InnerException.StackTrace - }; - exceptionInfo.innerException = innerException; - } - - return exceptionInfo; + innerExceptionType = innerException.GetType().FullName, + innerExceptionMessage = innerException.Message, + innerStacktrace = innerException.StackTrace, + innerException = --deep > 0 ? GetInnerExceptions(innerException.InnerException, deep) : null + }; + return ex; } ///