diff --git a/README.md b/README.md
index f99b283..259ae32 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Serilog.Extensions.Hosting [](https://ci.appveyor.com/project/serilog/serilog-extensions-hosting) [](https://www.nuget.org/packages/Serilog.Extensions.Hosting/)
-Serilog logging for Microsoft.Extensions.Hosting . This package routes Microsoft.Extensions.Hosting log messages through Serilog, so you can get information about the framework's internal operations logged to the same Serilog sinks as your application events.
+Serilog logging for _Microsoft.Extensions.Hosting_. This package routes framework log messages through Serilog, so you can get information about the framework's internal operations written to the same Serilog sinks as your application events.
### Instructions
diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/AmbientDiagnosticContextCollector.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/AmbientDiagnosticContextCollector.cs
index acc1ccd..68010ee 100644
--- a/src/Serilog.Extensions.Hosting/Extensions/Hosting/AmbientDiagnosticContextCollector.cs
+++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/AmbientDiagnosticContextCollector.cs
@@ -1,4 +1,18 @@
-using System;
+// Copyright 2019 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.Threading;
namespace Serilog.Extensions.Hosting
diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs
index 0e5e602..7c12c19 100644
--- a/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs
+++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs
@@ -13,14 +13,15 @@
// limitations under the License.
using System;
+using System.Threading;
namespace Serilog.Extensions.Hosting
{
///
- /// Implements an ambient/async-local diagnostic context. Consumers should
- /// use in preference to this concrete type.
+ /// Implements an ambient diagnostic context using .
///
- public class DiagnosticContext : IDiagnosticContext
+ /// Consumers should use to set context properties.
+ public sealed class DiagnosticContext : IDiagnosticContext
{
readonly ILogger _logger;
@@ -34,17 +35,17 @@ public DiagnosticContext(ILogger logger)
}
///
- /// Start collecting properties to associate with the current async context. This will replace
+ /// Start collecting properties to associate with the current diagnostic context. This will replace
/// the active collector, if any.
///
- /// A collector that will receive events added in the current async context.
+ /// A collector that will receive properties added in the current diagnostic context.
public DiagnosticContextCollector BeginCollection()
{
return AmbientDiagnosticContextCollector.Begin();
}
- ///
- public void Add(string propertyName, object value, bool destructureObjects = false)
+ ///
+ public void Set(string propertyName, object value, bool destructureObjects = false)
{
if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
@@ -52,7 +53,7 @@ public void Add(string propertyName, object value, bool destructureObjects = fal
if (collector != null &&
(_logger ?? Log.Logger).BindProperty(propertyName, value, destructureObjects, out var property))
{
- collector.Add(property);
+ collector.AddOrUpdate(property);
}
}
}
diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContextCollector.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContextCollector.cs
index dd92af7..d1aba65 100644
--- a/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContextCollector.cs
+++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContextCollector.cs
@@ -9,22 +9,33 @@ namespace Serilog.Extensions.Hosting
///
public sealed class DiagnosticContextCollector : IDisposable
{
- readonly AmbientDiagnosticContextCollector _ambientCollector;
- List _properties = new List();
+ readonly IDisposable _chainedDisposable;
+ readonly object _propertiesLock = new object();
+ Dictionary _properties = new Dictionary();
- internal DiagnosticContextCollector(AmbientDiagnosticContextCollector ambientCollector)
+ ///
+ /// Construct a .
+ ///
+ /// An object that will be disposed to signal completion/disposal of
+ /// the collector.
+ public DiagnosticContextCollector(IDisposable chainedDisposable)
{
- _ambientCollector = ambientCollector ?? throw new ArgumentNullException(nameof(ambientCollector));
+ _chainedDisposable = chainedDisposable ?? throw new ArgumentNullException(nameof(chainedDisposable));
}
///
/// Add the property to the context.
///
/// The property to add.
- public void Add(LogEventProperty property)
+ public void AddOrUpdate(LogEventProperty property)
{
if (property == null) throw new ArgumentNullException(nameof(property));
- _properties?.Add(property);
+
+ lock (_propertiesLock)
+ {
+ if (_properties == null) return;
+ _properties[property.Name] = property;
+ }
}
///
@@ -34,18 +45,21 @@ public void Add(LogEventProperty property)
///
/// The collected properties, or null if no collection is active.
/// True if properties could be collected.
- public bool TryComplete(out List properties)
+ public bool TryComplete(out IEnumerable properties)
{
- properties = _properties;
- _properties = null;
- Dispose();
- return properties != null;
+ lock (_propertiesLock)
+ {
+ properties = _properties?.Values;
+ _properties = null;
+ Dispose();
+ return properties != null;
+ }
}
///
public void Dispose()
{
- _ambientCollector.Dispose();
+ _chainedDisposable.Dispose();
}
}
}
diff --git a/src/Serilog.Extensions.Hosting/IDiagnosticContext.cs b/src/Serilog.Extensions.Hosting/IDiagnosticContext.cs
index 5b472c4..468fa84 100644
--- a/src/Serilog.Extensions.Hosting/IDiagnosticContext.cs
+++ b/src/Serilog.Extensions.Hosting/IDiagnosticContext.cs
@@ -20,12 +20,13 @@ namespace Serilog
public interface IDiagnosticContext
{
///
- /// Add the specified property to the request's diagnostic payload.
+ /// Set the specified property on the current diagnostic context. The property will be collected
+ /// and attached to the event emitted at the completion of the context.
///
/// The name of the property. Must be non-empty.
/// The property value.
/// If true, the value will be serialized as structured
/// data if possible; if false, the object will be recorded as a scalar or simple array.
- void Add(string propertyName, object value, bool destructureObjects = false);
+ void Set(string propertyName, object value, bool destructureObjects = false);
}
}
diff --git a/test/Serilog.Extensions.Hosting.Tests/DiagnosticContextTests.cs b/test/Serilog.Extensions.Hosting.Tests/DiagnosticContextTests.cs
index 834492c..c32f9ea 100644
--- a/test/Serilog.Extensions.Hosting.Tests/DiagnosticContextTests.cs
+++ b/test/Serilog.Extensions.Hosting.Tests/DiagnosticContextTests.cs
@@ -1,17 +1,21 @@
using System;
+using System.Linq;
using System.Threading.Tasks;
+using Serilog.Events;
using Serilog.Extensions.Hosting.Tests.Support;
using Xunit;
+// ReSharper disable PossibleNullReferenceException
+
namespace Serilog.Extensions.Hosting.Tests
{
public class DiagnosticContextTests
{
[Fact]
- public void AddIsSafeWhenNoContextIsActive()
+ public void SetIsSafeWhenNoContextIsActive()
{
var dc = new DiagnosticContext(Some.Logger());
- dc.Add(Some.String("name"), Some.Int32());
+ dc.Set(Some.String("name"), Some.Int32());
}
[Fact]
@@ -20,19 +24,34 @@ public async Task PropertiesAreCollectedInAnActiveContext()
var dc = new DiagnosticContext(Some.Logger());
var collector = dc.BeginCollection();
- dc.Add(Some.String("name"), Some.Int32());
+ dc.Set(Some.String("first"), Some.Int32());
await Task.Delay(TimeSpan.FromMilliseconds(10));
- dc.Add(Some.String("name"), Some.Int32());
+ dc.Set(Some.String("second"), Some.Int32());
Assert.True(collector.TryComplete(out var properties));
- Assert.Equal(2, properties.Count);
+ Assert.Equal(2, properties.Count());
Assert.False(collector.TryComplete(out _));
collector.Dispose();
- dc.Add(Some.String("name"), Some.Int32());
+ dc.Set(Some.String("third"), Some.Int32());
Assert.False(collector.TryComplete(out _));
}
+
+ [Fact]
+ public void ExistingPropertiesCanBeUpdated()
+ {
+ var dc = new DiagnosticContext(Some.Logger());
+
+ var collector = dc.BeginCollection();
+ dc.Set("name", 10);
+ dc.Set("name", 20);
+
+ Assert.True(collector.TryComplete(out var properties));
+ var prop = Assert.Single(properties);
+ var scalar = Assert.IsType(prop.Value);
+ Assert.Equal(20, scalar.Value);
+ }
}
}