Skip to content

Commit e6b6741

Browse files
authored
Merge pull request #58 from serilog/dev
5.0.0 Release
2 parents 383bcb1 + 8cb2e94 commit e6b6741

11 files changed

+155
-27
lines changed

Diff for: Build.ps1

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ if(Test-Path .\artifacts) {
1111

1212
$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL];
1313
$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
14-
$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"]
14+
$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"]
1515

1616
echo "build: Version suffix is $suffix"
1717

Diff for: appveyor.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
version: '{build}'
22
skip_tags: true
3-
image: Visual Studio 2019
3+
image: Visual Studio 2022
44
test: off
55
build_script:
66
- ps: ./Build.ps1
@@ -15,11 +15,11 @@ deploy:
1515
api_key:
1616
secure: Q65rY+zaFWOhs8a9IVSaX4Go5HNcIlEXjEFWMB83Y325WE9aXzi0xzDDc0/fJDzk
1717
on:
18-
branch: /^(master|dev)$/
18+
branch: /^(main|dev)$/
1919
- provider: GitHub
2020
auth_token:
2121
secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX
2222
artifact: /Serilog.*\.nupkg/
2323
tag: v$(appveyor_build_version)
2424
on:
25-
branch: master
25+
branch: main

Diff for: global.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"sdk": {
33
"allowPrerelease": false,
4-
"version": "5.0.102",
4+
"version": "6.0.300",
55
"rollForward": "latestFeature"
66
}
77
}

Diff for: src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs

+7
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
}

Diff for: src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContextCollector.cs

+42
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
}

Diff for: src/Serilog.Extensions.Hosting/Extensions/Hosting/ReloadableLogger.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,13 @@ public bool BindProperty(string propertyName, object value, bool destructureObje
368368
[MethodImpl(MethodImplOptions.AggressiveInlining)]
369369
(ILogger, bool) UpdateForCaller(ILogger root, ILogger cached, IReloadableLogger caller, out ILogger newRoot, out ILogger newCached, out bool frozen)
370370
{
371+
// Synchronization on `_sync` is not required in this method; it will be called without a lock
372+
// if `_frozen` and under a lock if not.
373+
371374
if (_frozen)
372375
{
373-
// If we're frozen, then the caller hasn't observed this yet and should update.
376+
// If we're frozen, then the caller hasn't observed this yet and should update. We could optimize a little here
377+
// and only signal an update if the cached logger is stale (as per the next condition below).
374378
newRoot = _logger;
375379
newCached = caller.ReloadLogger();
376380
frozen = true;
@@ -384,7 +388,7 @@ public bool BindProperty(string propertyName, object value, bool destructureObje
384388
frozen = false;
385389
return (cached, false);
386390
}
387-
391+
388392
newRoot = _logger;
389393
newCached = caller.ReloadLogger();
390394
frozen = false;

Diff for: src/Serilog.Extensions.Hosting/IDiagnosticContext.cs

+14-1
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
}

Diff for: src/Serilog.Extensions.Hosting/Serilog.Extensions.Hosting.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Description>Serilog support for .NET Core logging in hosted services</Description>
5-
<VersionPrefix>4.2.0</VersionPrefix>
5+
<VersionPrefix>5.0.0</VersionPrefix>
66
<Authors>Microsoft;Serilog Contributors</Authors>
77
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
88
<LangVersion>8</LangVersion>

Diff for: src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs

+17-17
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,13 @@ public static IHostBuilder UseSerilog(
8080
collection.AddSingleton<ILoggerFactory>(services => new SerilogLoggerFactory(logger, dispose));
8181
}
8282

83-
ConfigureServices(collection, logger);
83+
if (logger != null)
84+
{
85+
// This won't (and shouldn't) take ownership of the logger.
86+
collection.AddSingleton(logger);
87+
}
88+
bool useRegisteredLogger = logger != null;
89+
ConfigureDiagnosticContext(collection, useRegisteredLogger);
8490
});
8591

8692
return builder;
@@ -221,32 +227,26 @@ public static IHostBuilder UseSerilog(
221227

222228
return factory;
223229
});
224-
225-
// Null is passed here because we've already (lazily) registered `ILogger`
226-
ConfigureServices(collection, null);
230+
231+
ConfigureDiagnosticContext(collection, preserveStaticLogger);
227232
});
228233

229234
return builder;
230235
}
231236

232-
static void ConfigureServices(IServiceCollection collection, ILogger logger)
237+
static void ConfigureDiagnosticContext(IServiceCollection collection, bool useRegisteredLogger)
233238
{
234239
if (collection == null) throw new ArgumentNullException(nameof(collection));
235240

236-
if (logger != null)
237-
{
238-
// This won't (and shouldn't) take ownership of the logger.
239-
collection.AddSingleton(logger);
240-
}
241-
242-
// Registered to provide two services...
243-
var diagnosticContext = new DiagnosticContext(logger);
244-
241+
// Registered to provide two services...
245242
// Consumed by e.g. middleware
246-
collection.AddSingleton(diagnosticContext);
247-
243+
collection.AddSingleton(services =>
244+
{
245+
ILogger logger = useRegisteredLogger ? services.GetRequiredService<RegisteredLogger>().Logger : null;
246+
return new DiagnosticContext(logger);
247+
});
248248
// Consumed by user code
249-
collection.AddSingleton<IDiagnosticContext>(diagnosticContext);
249+
collection.AddSingleton<IDiagnosticContext>(services => services.GetRequiredService<DiagnosticContext>());
250250
}
251251
}
252252
}

Diff for: test/Serilog.Extensions.Hosting.Tests/DiagnosticContextTests.cs

+62
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
}

Diff for: test/Serilog.Extensions.Hosting.Tests/Serilog.Extensions.Hosting.Tests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netcoreapp3.1;net5.0;net4.8</TargetFrameworks>
4+
<TargetFrameworks>netcoreapp3.1;net6.0;net4.8</TargetFrameworks>
55
<AssemblyName>Serilog.Extensions.Hosting.Tests</AssemblyName>
66
<AssemblyOriginatorKeyFile>../../assets/Serilog.snk</AssemblyOriginatorKeyFile>
77
<SignAssembly>true</SignAssembly>

0 commit comments

Comments
 (0)