From a12e4c7b41923a79ee94f189d72e9fcc09fcd1b2 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Fri, 12 Aug 2016 16:24:36 +1000 Subject: [PATCH 1/5] Support for audit logging (propagated exceptions) #820 --- .../LoggerAuditSinkConfiguration.cs | 106 ++++++++++++++++++ .../Configuration/LoggerSinkConfiguration.cs | 7 +- src/Serilog/Core/Sinks/AggregateSink.cs | 54 +++++++++ src/Serilog/Core/Sinks/FilteringSink.cs | 9 +- src/Serilog/Core/Sinks/SafeAggregateSink.cs | 8 +- src/Serilog/LoggerConfiguration.cs | 48 ++++++-- .../Parameters/PropertyValueConverter.cs | 15 ++- .../Core/LogEventPropertyCapturingTests.cs | 2 +- .../Core/MessageTemplateTests.cs | 2 +- .../Events/LogEventPropertyValueTests.cs | 3 +- .../Serilog.Tests/LoggerConfigurationTests.cs | 72 +++++++++++- .../Parameters/PropertyValueConverterTests.cs | 3 +- 12 files changed, 301 insertions(+), 28 deletions(-) create mode 100644 src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs create mode 100644 src/Serilog/Core/Sinks/AggregateSink.cs diff --git a/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs b/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs new file mode 100644 index 000000000..2a0096556 --- /dev/null +++ b/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs @@ -0,0 +1,106 @@ +// Copyright 2013-2015 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Serilog.Core; +using Serilog.Events; + +namespace Serilog.Configuration +{ + /// + /// Controls sink configuration. + /// + public class LoggerAuditSinkConfiguration + { + LoggerSinkConfiguration _sinkConfiguration; + + internal LoggerAuditSinkConfiguration(LoggerConfiguration loggerConfiguration, Action addSink, Action applyInheritedConfiguration) + { + _sinkConfiguration = new LoggerSinkConfiguration(loggerConfiguration, addSink, applyInheritedConfiguration); + } + + /// + /// Audits log events to the specified . + /// + /// The sink. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Configuration object allowing method chaining. + public LoggerConfiguration Sink( + ILogEventSink logEventSink, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + // ReSharper disable once MethodOverloadWithOptionalParameter + LoggingLevelSwitch levelSwitch = null) + { + return _sinkConfiguration.Sink(logEventSink, restrictedToMinimumLevel, levelSwitch); + } + + /// + /// Write log events to the specified . + /// + /// The sink. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Configuration object allowing method chaining. + public LoggerConfiguration Sink( + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + LoggingLevelSwitch levelSwitch = null) + where TSink : ILogEventSink, new() + { + return _sinkConfiguration.Sink(restrictedToMinimumLevel, levelSwitch); + } + + /// + /// Audit log events to a sub-logger, where further processing may occur. Events through + /// the sub-logger will be constrained by filters and enriched by enrichers that are + /// active in the parent. A sub-logger cannot be used to log at a more verbose level, but + /// a less verbose level is possible. + /// + /// An action that configures the sub-logger. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Configuration object allowing method chaining. + public LoggerConfiguration Logger( + Action configureLogger, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + LoggingLevelSwitch levelSwitch = null) + { + return _sinkConfiguration.Logger(configureLogger, restrictedToMinimumLevel, levelSwitch); + } + + /// + /// Audit log events to a sub-logger, where further processing may occur. Events through + /// the sub-logger will be constrained by filters and enriched by enrichers that are + /// active in the parent. A sub-logger cannot be used to log at a more verbose level, but + /// a less verbose level is possible. + /// + /// The sub-logger. This will not be shut down automatically when the + /// parent logger is disposed. + /// The minimum level for + /// events passed through the sink. + /// Configuration object allowing method chaining. + public LoggerConfiguration Logger( + ILogger logger, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum) + { + return _sinkConfiguration.Logger(logger, restrictedToMinimumLevel); + } + } +} diff --git a/src/Serilog/Configuration/LoggerSinkConfiguration.cs b/src/Serilog/Configuration/LoggerSinkConfiguration.cs index f1b7e94a4..0552387cd 100644 --- a/src/Serilog/Configuration/LoggerSinkConfiguration.cs +++ b/src/Serilog/Configuration/LoggerSinkConfiguration.cs @@ -14,7 +14,6 @@ using System; using System.ComponentModel; - using Serilog.Core; using Serilog.Core.Sinks; using Serilog.Debugging; @@ -31,8 +30,6 @@ public class LoggerSinkConfiguration readonly Action _addSink; readonly Action _applyInheritedConfiguration; - const string DefaultOutputTemplate = "{Timestamp} [{Level}] {Message}{NewLine}{Exception}"; - internal LoggerSinkConfiguration(LoggerConfiguration loggerConfiguration, Action addSink, Action applyInheritedConfiguration) { if (loggerConfiguration == null) throw new ArgumentNullException(nameof(loggerConfiguration)); @@ -51,7 +48,7 @@ internal LoggerSinkConfiguration(LoggerConfiguration loggerConfiguration, Action /// events passed through the sink. /// Configuration object allowing method chaining. /// Provided for binary compatibility for earlier versions, - /// should be removed in 2.0. Not marked obsolete because warnings + /// should be removed in 3.0. Not marked obsolete because warnings /// would be syntactically annoying to avoid. [EditorBrowsable(EditorBrowsableState.Never)] public LoggerConfiguration Sink( @@ -110,8 +107,6 @@ public LoggerConfiguration Sink( return Sink(new TSink(), restrictedToMinimumLevel, levelSwitch); } - - /// /// Write log events to a sub-logger, where further processing may occur. Events through /// the sub-logger will be constrained by filters and enriched by enrichers that are diff --git a/src/Serilog/Core/Sinks/AggregateSink.cs b/src/Serilog/Core/Sinks/AggregateSink.cs new file mode 100644 index 000000000..2fcbc24df --- /dev/null +++ b/src/Serilog/Core/Sinks/AggregateSink.cs @@ -0,0 +1,54 @@ +// Copyright 2016 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Serilog.Debugging; +using Serilog.Events; + +namespace Serilog.Core.Sinks +{ + class AggregateSink : ILogEventSink + { + readonly ILogEventSink[] _sinks; + + public AggregateSink(IEnumerable sinks) + { + if (sinks == null) throw new ArgumentNullException(nameof(sinks)); + _sinks = sinks.ToArray(); + } + + public void Emit(LogEvent logEvent) + { + List exceptions = null; + foreach (var sink in _sinks) + { + try + { + sink.Emit(logEvent); + } + catch (Exception ex) + { + SelfLog.WriteLine("Caught exception while emitting to sink {0}: {1}", sink, ex); + exceptions = exceptions ?? new List(); + exceptions.Add(ex); + } + } + + if (exceptions != null) + throw new AggregateException("Failed to emit a log event", exceptions); + } + } +} diff --git a/src/Serilog/Core/Sinks/FilteringSink.cs b/src/Serilog/Core/Sinks/FilteringSink.cs index 9a5d4102b..13a898491 100644 --- a/src/Serilog/Core/Sinks/FilteringSink.cs +++ b/src/Serilog/Core/Sinks/FilteringSink.cs @@ -23,13 +23,15 @@ namespace Serilog.Core.Sinks class FilteringSink : ILogEventSink { readonly ILogEventSink _sink; + readonly bool _propagateExceptions; readonly ILogEventFilter[] _filters; - public FilteringSink(ILogEventSink sink, IEnumerable filters) + public FilteringSink(ILogEventSink sink, IEnumerable filters, bool propagateExceptions) { if (sink == null) throw new ArgumentNullException(nameof(sink)); if (filters == null) throw new ArgumentNullException(nameof(filters)); _sink = sink; + _propagateExceptions = propagateExceptions; _filters = filters.ToArray(); } @@ -47,7 +49,10 @@ public void Emit(LogEvent logEvent) } catch (Exception ex) { - SelfLog.WriteLine("Caught exception {0} while applying filters.", ex); + SelfLog.WriteLine("Caught exception while applying filters: {0}", ex); + + if (_propagateExceptions) + throw; } } } diff --git a/src/Serilog/Core/Sinks/SafeAggregateSink.cs b/src/Serilog/Core/Sinks/SafeAggregateSink.cs index 857917134..da8ad6505 100644 --- a/src/Serilog/Core/Sinks/SafeAggregateSink.cs +++ b/src/Serilog/Core/Sinks/SafeAggregateSink.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Serilog.Debugging; using Serilog.Events; @@ -21,12 +22,12 @@ namespace Serilog.Core.Sinks { class SafeAggregateSink : ILogEventSink { - readonly IEnumerable _sinks; + readonly ILogEventSink[] _sinks; public SafeAggregateSink(IEnumerable sinks) { if (sinks == null) throw new ArgumentNullException(nameof(sinks)); - _sinks = sinks; + _sinks = sinks.ToArray(); } public void Emit(LogEvent logEvent) @@ -39,10 +40,9 @@ public void Emit(LogEvent logEvent) } catch (Exception ex) { - SelfLog.WriteLine("Caught exception {0} while emitting to sink {1}.", ex, sink); + SelfLog.WriteLine("Caught exception while emitting to sink {0}: {1}", sink, ex); } } } } } - diff --git a/src/Serilog/LoggerConfiguration.cs b/src/Serilog/LoggerConfiguration.cs index 3d8b0b646..df5dd0992 100644 --- a/src/Serilog/LoggerConfiguration.cs +++ b/src/Serilog/LoggerConfiguration.cs @@ -30,6 +30,7 @@ namespace Serilog public class LoggerConfiguration { readonly List _logEventSinks = new List(); + readonly List _auditSinks = new List(); readonly List _enrichers = new List(); readonly List _filters = new List(); readonly List _additionalScalarTypes = new List(); @@ -40,6 +41,14 @@ public class LoggerConfiguration int _maximumDestructuringDepth = 10; bool _loggerCreated; + void ApplyInheritedConfiguration(LoggerConfiguration child) + { + if (_levelSwitch != null) + child.MinimumLevel.ControlledBy(_levelSwitch); + else + child.MinimumLevel.Is(_minimumLevel); + } + /// /// Configures the sinks that log events will be emitted to. /// @@ -47,13 +56,26 @@ public LoggerSinkConfiguration WriteTo { get { - return new LoggerSinkConfiguration(this, s => _logEventSinks.Add(s), child => - { - if (_levelSwitch != null) - child.MinimumLevel.ControlledBy(_levelSwitch); - else - child.MinimumLevel.Is(_minimumLevel); - }); + return new LoggerSinkConfiguration(this, s => _logEventSinks.Add(s), ApplyInheritedConfiguration); + } + } + + /// + /// Configures sinks for auditing, instead of regular (safe) logging. When auditing is used, + /// exceptions from sinks and any intermediate filters propagate back to the caller. Most callers + /// should use instead. + /// + /// + /// Not all sinks are compatible with transactional auditing requirements (many will use asynchronous + /// batching to improve write throughput and latency). Sinks need to opt-in to auditing support by + /// extending , though the generic + /// method allows any sink class to be adapted for auditing. + /// + public LoggerAuditSinkConfiguration AuditTo + { + get + { + return new LoggerAuditSinkConfiguration(this, s => _auditSinks.Add(s), ApplyInheritedConfiguration); } } @@ -129,10 +151,18 @@ public Logger CreateLogger() ILogEventSink sink = new SafeAggregateSink(_logEventSinks); + var auditing = _auditSinks.Any(); + if (auditing) + sink = new AggregateSink(new[] { sink }.Concat(_auditSinks)); + if (_filters.Any()) - sink = new FilteringSink(sink, _filters); + { + // A throwing filter could drop an auditable event, so exceptions in filters must be propagated + // if auditing is used. + sink = new FilteringSink(sink, _filters, auditing); + } - var converter = new PropertyValueConverter(_maximumDestructuringDepth, _additionalScalarTypes, _additionalDestructuringPolicies); + var converter = new PropertyValueConverter(_maximumDestructuringDepth, _additionalScalarTypes, _additionalDestructuringPolicies, auditing); var processor = new MessageTemplateProcessor(converter); ILogEventEnricher enricher; diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index beeabcac0..6dae2e134 100644 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -46,14 +46,20 @@ partial class PropertyValueConverter : ILogEventPropertyFactory, ILogEventProper readonly IDestructuringPolicy[] _destructuringPolicies; readonly IScalarConversionPolicy[] _scalarConversionPolicies; readonly int _maximumDestructuringDepth; + readonly bool _propagateExceptions; - public PropertyValueConverter(int maximumDestructuringDepth, IEnumerable additionalScalarTypes, IEnumerable additionalDestructuringPolicies) + public PropertyValueConverter( + int maximumDestructuringDepth, + IEnumerable additionalScalarTypes, + IEnumerable additionalDestructuringPolicies, + bool propagateExceptions) { if (additionalScalarTypes == null) throw new ArgumentNullException(nameof(additionalScalarTypes)); if (additionalDestructuringPolicies == null) throw new ArgumentNullException(nameof(additionalDestructuringPolicies)); if (maximumDestructuringDepth < 0) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); _maximumDestructuringDepth = maximumDestructuringDepth; + _propagateExceptions = propagateExceptions; _scalarConversionPolicies = new IScalarConversionPolicy[] { @@ -177,7 +183,7 @@ bool IsValidDictionaryKeyType(Type valueType) valueType.GetTypeInfo().IsEnum; } - static IEnumerable GetProperties(object value, ILogEventPropertyValueFactory recursive) + IEnumerable GetProperties(object value, ILogEventPropertyValueFactory recursive) { foreach (var prop in value.GetType().GetPropertiesRecursive()) { @@ -188,6 +194,8 @@ static IEnumerable GetProperties(object value, ILogEventProper } catch (TargetParameterCountException) { + // These properties would ideally be ignored; since they never produce values they're not + // of concern to auditing and exceptions can be suppressed. SelfLog.WriteLine("The property accessor {0} is a non-default indexer", prop); continue; } @@ -195,6 +203,9 @@ static IEnumerable GetProperties(object value, ILogEventProper { SelfLog.WriteLine("The property accessor {0} threw exception {1}", prop, ex); propValue = "The property accessor threw an exception: " + ex.InnerException.GetType().Name; + + if (_propagateExceptions) + throw; } yield return new LogEventProperty(prop.Name, recursive.CreatePropertyValue(propValue, true)); } diff --git a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs index b4fb49184..b81834b43 100644 --- a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs +++ b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs @@ -179,7 +179,7 @@ static IEnumerable Capture(string messageTemplate, params obje { var mt = new MessageTemplateParser().Parse(messageTemplate); var binder = new PropertyBinder( - new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty())); + new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty(), false)); return binder.ConstructProperties(mt, properties); } diff --git a/test/Serilog.Tests/Core/MessageTemplateTests.cs b/test/Serilog.Tests/Core/MessageTemplateTests.cs index 804e4bc22..75bcee236 100644 --- a/test/Serilog.Tests/Core/MessageTemplateTests.cs +++ b/test/Serilog.Tests/Core/MessageTemplateTests.cs @@ -126,7 +126,7 @@ static string Render(string messageTemplate, params object[] properties) static string Render(IFormatProvider formatProvider, string messageTemplate, params object[] properties) { var mt = new MessageTemplateParser().Parse(messageTemplate); - var binder = new PropertyBinder(new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty())); + var binder = new PropertyBinder(new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty(), false)); var props = binder.ConstructProperties(mt, properties); var output = new StringBuilder(); var writer = new StringWriter(output); diff --git a/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs b/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs index 8d5ee101f..170dd2fe2 100644 --- a/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs +++ b/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs @@ -26,7 +26,8 @@ namespace Serilog.Tests.Events { public class LogEventPropertyValueTests { - readonly PropertyValueConverter _converter = new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty()); + readonly PropertyValueConverter _converter = + new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty(), false); [Fact] public void AnEnumIsConvertedToANonStringScalarValue() diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index a332d3c7b..eabb8386f 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -363,5 +363,75 @@ public void LowerMinimumLevelOverridesArePropagated() Assert.Equal(1, sink.Events.Count); } + + [Fact] + public void ExceptionsThrownBySinksAreNotPropagated() + { + var logger = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(e => { throw new Exception("Boom!"); })) + .CreateLogger(); + + logger.Write(Some.InformationEvent()); + + Assert.True(true, "No exception reached the caller"); + } + + [Fact] + public void ExceptionsThrownBySinksAreNotPropagatedEvenWhenAuditingIsPresent() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new CollectingSink()) + .WriteTo.Sink(new DelegatingSink(e => { throw new Exception("Boom!"); })) + .CreateLogger(); + + logger.Write(Some.InformationEvent()); + + Assert.True(true, "No exception reached the caller"); + } + + [Fact] + public void ExceptionsThrownByFiltersAreNotPropagated() + { + var logger = new LoggerConfiguration() + .Filter.ByExcluding(e => { throw new Exception("Boom!"); }) + .CreateLogger(); + + logger.Write(Some.InformationEvent()); + + Assert.True(true, "No exception reached the caller"); + } + + [Fact] + public void ExceptionsThrownByAuditSinksArePropagated() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new DelegatingSink(e => { throw new Exception("Boom!"); })) + .CreateLogger(); + + Assert.Throws(() => logger.Write(Some.InformationEvent())); + } + + [Fact] + public void ExceptionsThrownByFiltersArePropagatedIfAuditingEnabled() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new DelegatingSink(e => { })) + .Filter.ByExcluding(e => { throw new Exception("Boom!"); }) + .CreateLogger(); + + Assert.Throws(() => logger.Write(Some.InformationEvent())); + } + + [Fact] + public void ExceptionsThrownByAuditSinksArePropagatedFromChildLoggers() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new DelegatingSink(e => { throw new Exception("Boom!"); })) + .CreateLogger(); + + Assert.Throws(() => logger + .ForContext() + .Write(Some.InformationEvent())); + } } -} \ No newline at end of file +} diff --git a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs b/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs index 438011981..354202eab 100644 --- a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs +++ b/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs @@ -12,7 +12,8 @@ namespace Serilog.Tests.Parameters { public class PropertyValueConverterTests { - readonly PropertyValueConverter _converter = new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty()); + readonly PropertyValueConverter _converter = + new PropertyValueConverter(10, Enumerable.Empty(), Enumerable.Empty(), false); [Fact] public void UnderDestructuringAByteArrayIsAScalarValue() From f0ceed51a3e9cec1ac8ded0281bb2bccadb66ed8 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Fri, 12 Aug 2016 16:27:23 +1000 Subject: [PATCH 2/5] Exception message punctuation --- src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs | 2 +- src/Serilog/Core/Sinks/AggregateSink.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs b/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs index 2a0096556..6054bf6a3 100644 --- a/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs +++ b/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2015 Serilog Contributors +// Copyright 2016 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/Serilog/Core/Sinks/AggregateSink.cs b/src/Serilog/Core/Sinks/AggregateSink.cs index 2fcbc24df..c43cd3889 100644 --- a/src/Serilog/Core/Sinks/AggregateSink.cs +++ b/src/Serilog/Core/Sinks/AggregateSink.cs @@ -48,7 +48,7 @@ public void Emit(LogEvent logEvent) } if (exceptions != null) - throw new AggregateException("Failed to emit a log event", exceptions); + throw new AggregateException("Failed to emit a log event.", exceptions); } } } From d8cee48b086fa4f3c8c1e1e8c8ca230c5b0c3c16 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Fri, 12 Aug 2016 16:28:35 +1000 Subject: [PATCH 3/5] Consistent XML doc messages --- src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs b/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs index 6054bf6a3..9381f7121 100644 --- a/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs +++ b/src/Serilog/Configuration/LoggerAuditSinkConfiguration.cs @@ -31,7 +31,7 @@ internal LoggerAuditSinkConfiguration(LoggerConfiguration loggerConfiguration, A } /// - /// Audits log events to the specified . + /// Audit log events to the specified . /// /// The sink. /// The minimum level for @@ -49,7 +49,7 @@ public LoggerConfiguration Sink( } /// - /// Write log events to the specified . + /// Audit log events to the specified . /// /// The sink. /// The minimum level for From 59259baa71bf3668ad1940e593ad1582c4b0f2a3 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Fri, 12 Aug 2016 17:29:52 +1000 Subject: [PATCH 4/5] Additional exception behavior tests and fixes --- src/Serilog/LoggerConfiguration.cs | 16 +----- .../Parameters/PropertyValueConverter.cs | 21 +++++-- .../Serilog.Tests/LoggerConfigurationTests.cs | 57 +++++++++++++++++++ 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/Serilog/LoggerConfiguration.cs b/src/Serilog/LoggerConfiguration.cs index df5dd0992..764d937e2 100644 --- a/src/Serilog/LoggerConfiguration.cs +++ b/src/Serilog/LoggerConfiguration.cs @@ -52,13 +52,7 @@ void ApplyInheritedConfiguration(LoggerConfiguration child) /// /// Configures the sinks that log events will be emitted to. /// - public LoggerSinkConfiguration WriteTo - { - get - { - return new LoggerSinkConfiguration(this, s => _logEventSinks.Add(s), ApplyInheritedConfiguration); - } - } + public LoggerSinkConfiguration WriteTo => new LoggerSinkConfiguration(this, s => _logEventSinks.Add(s), ApplyInheritedConfiguration); /// /// Configures sinks for auditing, instead of regular (safe) logging. When auditing is used, @@ -71,13 +65,7 @@ public LoggerSinkConfiguration WriteTo /// extending , though the generic /// method allows any sink class to be adapted for auditing. /// - public LoggerAuditSinkConfiguration AuditTo - { - get - { - return new LoggerAuditSinkConfiguration(this, s => _auditSinks.Add(s), ApplyInheritedConfiguration); - } - } + public LoggerAuditSinkConfiguration AuditTo => new LoggerAuditSinkConfiguration(this, s => _auditSinks.Add(s), ApplyInheritedConfiguration); /// /// Configures the minimum level at which events will be passed to sinks. If diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Parameters/PropertyValueConverter.cs index 6dae2e134..49bf9a825 100644 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Parameters/PropertyValueConverter.cs @@ -90,7 +90,19 @@ public LogEventPropertyValue CreatePropertyValue(object value, bool destructureO public LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructuring) { - return CreatePropertyValue(value, destructuring, 1); + try + { + return CreatePropertyValue(value, destructuring, 1); + } + catch (Exception ex) + { + SelfLog.WriteLine("Exception caught while converting property value: {0}", ex); + + if (_propagateExceptions) + throw; + + return new ScalarValue("Capturing the property value threw an exception: " + ex.GetType().Name); + } } LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects, int depth) @@ -115,7 +127,7 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur var limiter = new DepthLimiter(depth, _maximumDestructuringDepth, this); foreach (var scalarConversionPolicy in _scalarConversionPolicies) - { + { ScalarValue converted; if (scalarConversionPolicy.TryConvertToScalar(value, limiter, out converted)) return converted; @@ -201,11 +213,12 @@ IEnumerable GetProperties(object value, ILogEventPropertyValue } catch (TargetInvocationException ex) { - SelfLog.WriteLine("The property accessor {0} threw exception {1}", prop, ex); - propValue = "The property accessor threw an exception: " + ex.InnerException.GetType().Name; + SelfLog.WriteLine("The property accessor {0} threw exception: {1}", prop, ex); if (_propagateExceptions) throw; + + propValue = "The property accessor threw an exception: " + ex.InnerException.GetType().Name; } yield return new LogEventProperty(prop.Name, recursive.CreatePropertyValue(propValue, true)); } diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index eabb8386f..a755bda26 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -433,5 +433,62 @@ public void ExceptionsThrownByAuditSinksArePropagatedFromChildLoggers() .ForContext() .Write(Some.InformationEvent())); } + + class Value { } + + [Fact] + public void ExceptionsThrownByDestructuringPoliciesAreNotPropagated() + { + var logger = new LoggerConfiguration() + .WriteTo.Sink(new CollectingSink()) + .Destructure.ByTransforming(v => { throw new Exception("Boom!"); }) + .CreateLogger(); + + logger.Information("{@Value}", new Value()); + + Assert.True(true, "No exception reached the caller"); + } + + [Fact] + public void ExceptionsThrownByDestructuringPoliciesArePropagatedIfAuditingEnabled() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new CollectingSink()) + .Destructure.ByTransforming(v => { throw new Exception("Boom!"); }) + .CreateLogger(); + + Assert.Throws(() => logger.Information("{@Value}", new Value())); + } + + class ThrowingProperty + { + // ReSharper disable once UnusedMember.Local + public string Property + { + get { throw new Exception("Boom!"); } + } + } + + [Fact] + public void ExceptionsThrownByPropertyAccessorsAreNotPropagated() + { + var logger = new LoggerConfiguration() + .WriteTo.Sink(new CollectingSink()) + .CreateLogger(); + + logger.Information("{@Value}", new ThrowingProperty()); + + Assert.True(true, "No exception reached the caller"); + } + + [Fact] + public void ExceptionsThrownByPropertyAccessorsArePropagatedIfAuditingEnabled() + { + var logger = new LoggerConfiguration() + .AuditTo.Sink(new CollectingSink()) + .CreateLogger(); + + Assert.Throws(() => logger.Information("{@Value}", new ThrowingProperty())); + } } } From 3089b5f16d514be931b9df9c742dbde93c11e627 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 16 Aug 2016 07:36:54 +1000 Subject: [PATCH 5/5] Bump dev version to 2.2 (features added) --- src/Serilog/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog/project.json b/src/Serilog/project.json index c7bede7d0..692b5faf5 100644 --- a/src/Serilog/project.json +++ b/src/Serilog/project.json @@ -1,5 +1,5 @@ { - "version": "2.1.1-*", + "version": "2.2.0-*", "description": "Simple .NET logging with fully-structured events", "authors": [ "Serilog Contributors" ],