Skip to content

Commit 88191b0

Browse files
authored
Merge pull request #56 from angularsen/agl/diagnostic-exception
Add exception to IDiagnosticContext
2 parents c729bae + f13731a commit 88191b0

File tree

4 files changed

+125
-1
lines changed

4 files changed

+125
-1
lines changed

src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,12 @@ public void Set(string propertyName, object value, bool destructureObjects = fal
5656
collector.AddOrUpdate(property);
5757
}
5858
}
59+
60+
/// <inheritdoc cref="IDiagnosticContext.SetException"/>
61+
public void SetException(Exception exception)
62+
{
63+
var collector = AmbientDiagnosticContextCollector.Current;
64+
collector?.SetException(exception);
65+
}
5966
}
6067
}

src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContextCollector.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public sealed class DiagnosticContextCollector : IDisposable
1111
{
1212
readonly IDisposable _chainedDisposable;
1313
readonly object _propertiesLock = new object();
14+
Exception _exception;
1415
Dictionary<string, LogEventProperty> _properties = new Dictionary<string, LogEventProperty>();
1516

1617
/// <summary>
@@ -38,19 +39,60 @@ public void AddOrUpdate(LogEventProperty property)
3839
}
3940
}
4041

42+
/// <summary>
43+
/// Set the exception associated with the current diagnostic context.
44+
/// </summary>
45+
/// <example>
46+
/// Passing an exception to the diagnostic context is useful when unhandled exceptions are handled before reaching Serilog's
47+
/// RequestLoggingMiddleware. One example is using https://www.nuget.org/packages/Hellang.Middleware.ProblemDetails to transform
48+
/// exceptions to ProblemDetails responses.
49+
/// </example>
50+
/// <remarks>
51+
/// If an unhandled exception reaches Serilog's RequestLoggingMiddleware, then the unhandled exception takes precedence.<br/>
52+
/// If <c>null</c> is given, it clears any previously assigned exception.
53+
/// </remarks>
54+
/// <param name="exception">The exception to log.</param>
55+
public void SetException(Exception exception)
56+
{
57+
lock (_propertiesLock)
58+
{
59+
if (_properties == null) return;
60+
_exception = exception;
61+
}
62+
}
63+
4164
/// <summary>
4265
/// Complete the context and retrieve the properties added to it, if any. This will
4366
/// stop collection and remove the collector from the original execution context and
4467
/// any of its children.
4568
/// </summary>
4669
/// <param name="properties">The collected properties, or null if no collection is active.</param>
4770
/// <returns>True if properties could be collected.</returns>
71+
/// <seealso cref="IDiagnosticContext.Set"/>
72+
[Obsolete("Replaced by TryComplete(out IEnumerable<LogEventProperty> properties, out Exception exception).")]
4873
public bool TryComplete(out IEnumerable<LogEventProperty> properties)
74+
{
75+
return TryComplete(out properties, out _);
76+
}
77+
78+
/// <summary>
79+
/// Complete the context and retrieve the properties and exception added to it, if any. This will
80+
/// stop collection and remove the collector from the original execution context and
81+
/// any of its children.
82+
/// </summary>
83+
/// <param name="properties">The collected properties, or null if no collection is active.</param>
84+
/// <param name="exception">The collected exception, or null if none has been collected or if no collection is active.</param>
85+
/// <returns>True if properties could be collected.</returns>
86+
/// <seealso cref="IDiagnosticContext.Set"/>
87+
/// <seealso cref="Serilog.IDiagnosticContext.SetException"/>
88+
public bool TryComplete(out IEnumerable<LogEventProperty> properties, out Exception exception)
4989
{
5090
lock (_propertiesLock)
5191
{
5292
properties = _properties?.Values;
93+
exception = _exception;
5394
_properties = null;
95+
_exception = null;
5496
Dispose();
5597
return properties != null;
5698
}

src/Serilog.Extensions.Hosting/IDiagnosticContext.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
using System;
16+
1517
namespace Serilog
1618
{
1719
/// <summary>
@@ -27,6 +29,17 @@ public interface IDiagnosticContext
2729
/// <param name="value">The property value.</param>
2830
/// <param name="destructureObjects">If true, the value will be serialized as structured
2931
/// data if possible; if false, the object will be recorded as a scalar or simple array.</param>
30-
void Set(string propertyName, object value, bool destructureObjects = false);
32+
void Set(string propertyName, object value, bool destructureObjects = false);
33+
34+
/// <summary>
35+
/// Set the specified exception on the current diagnostic context.
36+
/// </summary>
37+
/// <remarks>
38+
/// This method is useful when unhandled exceptions do not reach <c>Serilog.AspNetCore.RequestLoggingMiddleware</c>,
39+
/// such as when using <a href="https://www.nuget.org/packages/Hellang.Middleware.ProblemDetails">Hellang.Middleware.ProblemDetails</a>
40+
/// to transform exceptions to ProblemDetails responses.
41+
/// </remarks>
42+
/// <param name="exception">The exception to log. If <c>null</c> is given, it clears any previously assigned exception.</param>
43+
void SetException(Exception exception);
3144
}
3245
}

test/Serilog.Extensions.Hosting.Tests/DiagnosticContextTests.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ public void SetIsSafeWhenNoContextIsActive()
1818
dc.Set(Some.String("name"), Some.Int32());
1919
}
2020

21+
[Fact]
22+
public void SetExceptionIsSafeWhenNoContextIsActive()
23+
{
24+
var dc = new DiagnosticContext(Some.Logger());
25+
dc.SetException(new Exception("test"));
26+
}
27+
2128
[Fact]
2229
public async Task PropertiesAreCollectedInAnActiveContext()
2330
{
@@ -39,6 +46,48 @@ public async Task PropertiesAreCollectedInAnActiveContext()
3946
Assert.False(collector.TryComplete(out _));
4047
}
4148

49+
[Fact]
50+
public void ExceptionIsCollectedInAnActiveContext()
51+
{
52+
var dc = new DiagnosticContext(Some.Logger());
53+
var collector = dc.BeginCollection();
54+
55+
var setException = new Exception("before collect");
56+
dc.SetException(setException);
57+
58+
Assert.True(collector.TryComplete(out _, out var collectedException));
59+
Assert.Same(setException, collectedException);
60+
}
61+
62+
[Fact]
63+
public void ExceptionIsNotCollectedAfterTryComplete()
64+
{
65+
var dc = new DiagnosticContext(Some.Logger());
66+
var collector = dc.BeginCollection();
67+
collector.TryComplete(out _, out _);
68+
dc.SetException(new Exception(Some.String("after collect")));
69+
70+
var tryComplete2 = collector.TryComplete(out _, out var collectedException2);
71+
72+
Assert.False(tryComplete2);
73+
Assert.Null(collectedException2);
74+
}
75+
76+
[Fact]
77+
public void ExceptionIsNotCollectedAfterDispose()
78+
{
79+
var dc = new DiagnosticContext(Some.Logger());
80+
var collector = dc.BeginCollection();
81+
collector.Dispose();
82+
83+
dc.SetException(new Exception("after dispose"));
84+
85+
var tryComplete = collector.TryComplete(out _, out var collectedException);
86+
87+
Assert.True(tryComplete);
88+
Assert.Null(collectedException);
89+
}
90+
4291
[Fact]
4392
public void ExistingPropertiesCanBeUpdated()
4493
{
@@ -53,5 +102,18 @@ public void ExistingPropertiesCanBeUpdated()
53102
var scalar = Assert.IsType<ScalarValue>(prop.Value);
54103
Assert.Equal(20, scalar.Value);
55104
}
105+
106+
[Fact]
107+
public void ExistingExceptionCanBeUpdated()
108+
{
109+
var dc = new DiagnosticContext(Some.Logger());
110+
var collector = dc.BeginCollection();
111+
112+
dc.SetException(new Exception("ex1"));
113+
dc.SetException(new Exception("ex2"));
114+
115+
Assert.True(collector.TryComplete(out _, out var collectedException));
116+
Assert.Equal("ex2", collectedException.Message);
117+
}
56118
}
57119
}

0 commit comments

Comments
 (0)