Skip to content

Commit

Permalink
Merge pull request serilog#974 from nblumhardt/logcontext-push
Browse files Browse the repository at this point in the history
LogContext Push() and Clone()
  • Loading branch information
nblumhardt authored May 28, 2017
2 parents b01f157 + 60b71b4 commit 887a2ed
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 30 deletions.
82 changes: 57 additions & 25 deletions src/Serilog/Context/LogContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@


using System;
using System.ComponentModel;
using Serilog.Core;
using Serilog.Core.Enrichers;
using Serilog.Events;
Expand Down Expand Up @@ -62,7 +63,7 @@ public static class LogContext

/// <summary>
/// Push a property onto the context, returning an <see cref="IDisposable"/>
/// that can later be used to remove the property, along with any others that
/// that must later be used to remove the property, along with any others that
/// may have been pushed on top of it and not yet popped. The property must
/// be popped from the same thread/logical call context.
/// </summary>
Expand All @@ -75,37 +76,80 @@ public static class LogContext
/// <returns>A token that must be disposed, in order, to pop properties back off the stack.</returns>
public static IDisposable PushProperty(string name, object value, bool destructureObjects = false)
{
return Push(new PropertyEnricher(name, value, destructureObjects));
}

/// <summary>
/// Push an enricher onto the context, returning an <see cref="IDisposable"/>
/// that must later be used to remove the property, along with any others that
/// may have been pushed on top of it and not yet popped. The property must
/// be popped from the same thread/logical call context.
/// </summary>
/// <param name="enricher">An enricher to push onto the log context</param>
/// <returns>A token that must be disposed, in order, to pop properties back off the stack.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static IDisposable Push(ILogEventEnricher enricher)
{
if (enricher == null) throw new ArgumentNullException(nameof(enricher));

var stack = GetOrCreateEnricherStack();
var bookmark = new ContextStackBookmark(stack);

Enrichers = stack.Push(new PropertyEnricher(name, value, destructureObjects));
Enrichers = stack.Push(enricher);

return bookmark;
}

/// <summary>
/// Push multiple properties onto the context, returning an <see cref="IDisposable"/>
/// that can later be used to remove the properties. The properties must
/// Push multiple enrichers onto the context, returning an <see cref="IDisposable"/>
/// that must later be used to remove the property, along with any others that
/// may have been pushed on top of it and not yet popped. The property must
/// be popped from the same thread/logical call context.
/// </summary>
/// <param name="properties">Log Properties to push onto the log context</param>
/// <seealso cref="PropertyEnricher"/>.
/// <param name="enrichers">Enrichers to push onto the log context</param>
/// <returns>A token that must be disposed, in order, to pop properties back off the stack.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static IDisposable PushProperties(params ILogEventEnricher[] properties)
public static IDisposable Push(params ILogEventEnricher[] enrichers)
{
if (properties == null) throw new ArgumentNullException(nameof(properties));
if (enrichers == null) throw new ArgumentNullException(nameof(enrichers));

var stack = GetOrCreateEnricherStack();
var bookmark = new ContextStackBookmark(stack);

foreach (var prop in properties)
stack = stack.Push(prop);
for (var i = 0; i < enrichers.Length; ++i)
stack = stack.Push(enrichers[i]);

Enrichers = stack;

return bookmark;
}

/// <summary>
/// Push enrichers onto the log context. This method is obsolete, please
/// use <see cref="Push(Serilog.Core.ILogEventEnricher[])"/> instead.
/// </summary>
/// <param name="properties">Enrichers to push onto the log context</param>
/// <returns>A token that must be disposed, in order, to pop properties back off the stack.</returns>
/// <exception cref="ArgumentNullException"></exception>
[Obsolete("Please use `LogContext.Push(properties)` instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static IDisposable PushProperties(params ILogEventEnricher[] properties)
{
return Push(properties);
}

/// <summary>
/// Obtain an enricher that represents the current contents of the <see cref="LogContext"/>. This
/// can be pushed back onto the context in a different location/thread when required.
/// </summary>
/// <returns>An enricher that represents the current contents of the <see cref="LogContext"/>.</returns>
public static ILogEventEnricher Clone()
{
var stack = GetOrCreateEnricherStack();
return new SafeAggregateEnricher(stack);
}

static ImmutableStack<ILogEventEnricher> GetOrCreateEnricherStack()
{
var enrichers = Enrichers;
Expand Down Expand Up @@ -148,14 +192,8 @@ public void Dispose()

static ImmutableStack<ILogEventEnricher> Enrichers
{
get
{
return Data.Value;
}
set
{
Data.Value = value;
}
get => Data.Value;
set => Data.Value = value;
}

#elif REMOTING
Expand All @@ -178,14 +216,8 @@ static ImmutableStack<ILogEventEnricher> Enrichers

static ImmutableStack<ILogEventEnricher> Enrichers
{
get
{
return Data;
}
set
{
Data = value;
}
get => Data;
set => Data = value;
}
#endif
}
Expand Down
3 changes: 1 addition & 2 deletions src/Serilog/Core/Enrichers/FixedPropertyEnricher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ class FixedPropertyEnricher : ILogEventEnricher

public FixedPropertyEnricher(LogEventProperty logEventProperty)
{
if (logEventProperty == null) throw new ArgumentNullException(nameof(logEventProperty));
_logEventProperty = logEventProperty;
_logEventProperty = logEventProperty ?? throw new ArgumentNullException(nameof(logEventProperty));
}

public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
Expand Down
80 changes: 78 additions & 2 deletions test/Serilog.Tests/Context/LogContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#endif
using System.Threading;
using System.Threading.Tasks;
using Serilog.Core;

namespace Serilog.Tests.Context
{
Expand All @@ -24,6 +25,81 @@ public LogContextTests()
#endif
}

[Fact]
public void PushedPropertiesAreAvailableToLoggers()
{
LogEvent lastEvent = null;

var log = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Sink(new DelegatingSink(e => lastEvent = e))
.CreateLogger();

using (LogContext.PushProperty("A", 1))
using (LogContext.Push(new PropertyEnricher("B", 2)))
using (LogContext.Push(new PropertyEnricher("C", 3), new PropertyEnricher("D", 4))) // Different overload
{
log.Write(Some.InformationEvent());
Assert.Equal(1, lastEvent.Properties["A"].LiteralValue());
Assert.Equal(2, lastEvent.Properties["B"].LiteralValue());
Assert.Equal(3, lastEvent.Properties["C"].LiteralValue());
Assert.Equal(4, lastEvent.Properties["D"].LiteralValue());
}
}

[Fact]
public void LogContextCanBeCloned()
{
LogEvent lastEvent = null;

var log = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Sink(new DelegatingSink(e => lastEvent = e))
.CreateLogger();

ILogEventEnricher clonedContext;
using (LogContext.PushProperty("A", 1))
{
clonedContext = LogContext.Clone();
}

using (LogContext.Push(clonedContext))
{
log.Write(Some.InformationEvent());
Assert.Equal(1, lastEvent.Properties["A"].LiteralValue());
}
}

[Fact]
public void ClonedLogContextCanSharedAcrossThreads()
{
LogEvent lastEvent = null;

var log = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Sink(new DelegatingSink(e => lastEvent = e))
.CreateLogger();

ILogEventEnricher clonedContext;
using (LogContext.PushProperty("A", 1))
{
clonedContext = LogContext.Clone();
}

var t = new Thread(() =>
{
using (LogContext.Push(clonedContext))
{
log.Write(Some.InformationEvent());
}
});

t.Start();
t.Join();

Assert.Equal(1, lastEvent.Properties["A"].LiteralValue());
}

[Fact]
public void MoreNestedPropertiesOverrideLessNestedOnes()
{
Expand Down Expand Up @@ -63,13 +139,13 @@ public void MultipleNestedPropertiesOverrideLessNestedOnes()
.WriteTo.Sink(new DelegatingSink(e => lastEvent = e))
.CreateLogger();

using (LogContext.PushProperties(new PropertyEnricher("A1", 1), new PropertyEnricher("A2", 2)))
using (LogContext.Push(new PropertyEnricher("A1", 1), new PropertyEnricher("A2", 2)))
{
log.Write(Some.InformationEvent());
Assert.Equal(1, lastEvent.Properties["A1"].LiteralValue());
Assert.Equal(2, lastEvent.Properties["A2"].LiteralValue());

using (LogContext.PushProperties(new PropertyEnricher("A1", 10), new PropertyEnricher("A2", 20)))
using (LogContext.Push(new PropertyEnricher("A1", 10), new PropertyEnricher("A2", 20)))
{
log.Write(Some.InformationEvent());
Assert.Equal(10, lastEvent.Properties["A1"].LiteralValue());
Expand Down
2 changes: 1 addition & 1 deletion test/Serilog.Tests/Serilog.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<DefineConstants>$(DefineConstants);APPDOMAIN;REMOTING;GETCURRENTMETHOD</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net46' ">
<DefineConstants>$(DefineConstants);ASYNCLOCAL</DefineConstants>
<DefineConstants>$(DefineConstants);ASYNCLOCAL;GETCURRENTMETHOD</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
<Reference Include="System" />
Expand Down

0 comments on commit 887a2ed

Please sign in to comment.