Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable LogContext for .Net Core. #648

Merged
merged 12 commits into from
Feb 16, 2016
15 changes: 12 additions & 3 deletions src/Serilog/Context/ImmutableStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,34 @@
#if LOGCONTEXT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we now have dotnet5.1 TFM support, there's no need for #if LOGCONTEXT anymore - that's all the target platforms covered (I hope :-)) - they should all support LogContext now in one form or another.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha ha. You're right. I'll see how far I can get on my Mac tonight, otherwise it will be tomorrow morning. I've been meaning to see how the cross-platform bits are working.

using System;
using System.Collections.Generic;
#if REMOTING
using System.Runtime.Serialization;
#endif

namespace Serilog.Context
{
// Needed because of the shallow-copying behaviour of
// LogicalCallContext.
#if REMOTING
[Serializable]
class ImmutableStack<T> : IEnumerable<T>, ISerializable
#else
class ImmutableStack<T> : IEnumerable<T>
#endif
{
readonly ImmutableStack<T> _under;
readonly T _top;

#if REMOTING
public ImmutableStack(SerializationInfo info, StreamingContext context)
{
}

void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
}
#endif

ImmutableStack()
{
}
Expand Down Expand Up @@ -68,9 +80,6 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

public T Top => _top;

void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
}
}
}
#endif
101 changes: 85 additions & 16 deletions src/Serilog/Context/LogContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.


#if LOGCONTEXT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if this is a duplicate, I may have already asked - but are there any platforms on which LogContext is unsupported now? (I.e. is AsyncLocal on 5.1?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely clear on what 5.1 is. I thought from standard platform that it was 4.5, but if I try to compile with either AsyncLocal or CallContext it fails on 5.1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's a shame. What do you think (+ @merbla @khellang) about using [ThreadStatic] for this on 5.1?

There are two benefits:

  1. LogContext is a pretty fundamental API, and it is still useful even without async support (better than nothing, if you're targeting very limited surface area!), but more importantly:
  2. Libraries may build against the 5.1 surface area, but then be installed in applications that do in fact target 4.5+ or 5.4+ - in these cases, the binary used at runtime would be the version with full LogContext support (bait-and-switch)

Worth a shot?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This a site @khellang pointed me to. Not always 100% however a great start.

These would indicate 5.1 could be difficult.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@merbla thanks for the refs. The idea is not to use either of those, but something like:

[ThreadStatic]
static ImmutableStack<ILogEventEnricher> Data;

This should work on all platforms and does the job except for carrying state across async calls.

using System;
#if REMOTING
using System.Runtime.Remoting.Messaging;
#endif
#if ASYNCLOCAL
using System.Collections.Generic;
using System.Threading;
#endif
using Serilog.Core;
using Serilog.Core.Enrichers;
using Serilog.Events;
Expand Down Expand Up @@ -42,19 +49,24 @@ namespace Serilog.Context
/// </code>
/// </example>
/// <remarks>The scope of the context is the current logical thread, using
/// <see cref="CallContext.LogicalGetData"/> (and so is
/// preserved across async/await calls).</remarks>
#if ASYNCLOCAL
/// <seealso cref="AsyncLocal{T}"/>
#else
/// <seealso cref="CallContext.LogicalGetData"/>
#endif
/// (and so is preserved across async/await calls).</remarks>
public static class LogContext
{
#if ASYNCLOCAL
static readonly AsyncLocal<ImmutableStack<ILogEventEnricher>> Data = new AsyncLocal<ImmutableStack<ILogEventEnricher>>();
#else
#if DOTNET5_1
[ThreadStatic]
static ImmutableStack<ILogEventEnricher> Data;
#else
static readonly string DataSlotName = typeof(LogContext).FullName;

/// <summary>
/// When calling into appdomains without Serilog loaded, e.g. via remoting or during unit testing,
/// it may be necesary to set this value to true so that serialization exceptions are avoided. When possible,
/// using the <see cref="Suspend"/> method in a using block around the call has a lower overhead and
/// should be preferred.
/// </summary>
public static bool PermitCrossAppDomainCalls { get; set; }
#endif
#endif

/// <summary>
/// Push a property onto the context, returning an <see cref="IDisposable"/>
Expand Down Expand Up @@ -132,33 +144,87 @@ static ImmutableStack<ILogEventEnricher> GetOrCreateEnricherStack()
return enrichers;
}

static ImmutableStack<ILogEventEnricher> Enrichers
#if ASYNCLOCAL
static ImmutableStack<ILogEventEnricher> Enrichers
{
get
{
return Data.Value;
}
set
{
Data.Value = GetContext(value);
}
}
#else

#if DOTNET5_1
static ImmutableStack<ILogEventEnricher> Enrichers
{
get
{
return Data;
}
set
{
Data = GetContext(value);
}
}

#else
static ImmutableStack<ILogEventEnricher> Enrichers
{
get
{
var data = CallContext.LogicalGetData(DataSlotName);

ImmutableStack<ILogEventEnricher> context;
#if REMOTING
if (PermitCrossAppDomainCalls)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PermitCrossAppDomainCalls should be #if REMOTING since AppDomain doesn't appear on the modern targets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the property itself, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry about the vague comment 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved PermitCrossAppDomainCalls property to inside the REMOTING block at the bottom of the file.

{
context = ((Wrapper) data)?.Value;
context = ((Wrapper)data)?.Value;
}
else
{
context = (ImmutableStack<ILogEventEnricher>)data;
}

#else
context = data;
#endif
return context;
}
set
{

var context = !PermitCrossAppDomainCalls ? (object)value : new Wrapper { Value = value };

var context = GetContext(value);
CallContext.LogicalSetData(DataSlotName, context);
}
}
#endif
#endif

#if REMOTING
/// <summary>
/// When calling into appdomains without Serilog loaded, e.g. via remoting or during unit testing,
/// it may be necesary to set this value to true so that serialization exceptions are avoided. When possible,
/// using the <see cref="Suspend"/> method in a using block around the call has a lower overhead and
/// should be preferred.
/// </summary>
public static bool PermitCrossAppDomainCalls { get; set; }

static object GetContext(ImmutableStack<ILogEventEnricher> value)
{
var context = !PermitCrossAppDomainCalls ? (object) value : new Wrapper
{
Value = value
};
return context;
}
#else
static ImmutableStack<ILogEventEnricher> GetContext(ImmutableStack<ILogEventEnricher> value)
{
return value;
}
#endif

internal static void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
Expand Down Expand Up @@ -187,10 +253,13 @@ public void Dispose()
}
}

#if REMOTING
sealed class Wrapper : MarshalByRefObject
{
public ImmutableStack<ILogEventEnricher> Value { get; set; }
}
#endif
}
}

#endif
21 changes: 6 additions & 15 deletions src/Serilog/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,10 @@
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
"iconUrl": "http://serilog.net/images/serilog-nuget.png",
"frameworks": {
"net40": {
"compilationOptions": {
"keyFile": "../../assets/Serilog.snk",
"define": [ "APPSETTINGS", "LOGCONTEXT", "PROCESS", "FILE_IO", "PERIODIC_BATCHING" ]
},
"frameworkAssemblies": {
"System.Configuration": ""
},
"dependencies": {
"Microsoft.Bcl.Async": "1.0.168"
}
},
"net45": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change is inevitable - 👍

"compilationOptions": {
"keyFile": "../../assets/Serilog.snk",
"define": [ "APPSETTINGS", "LOGCONTEXT", "PROCESS", "FILE_IO", "PERIODIC_BATCHING" ]
"define": [ "APPSETTINGS", "LOGCONTEXT", "PROCESS", "FILE_IO", "PERIODIC_BATCHING", "REMOTING" ]
},
"frameworkAssemblies": {
"System.Configuration": ""
Expand All @@ -31,7 +19,7 @@
"dotnet5.1": {
"compilationOptions": {
"keyFile": "../../assets/Serilog.snk",
"define": [ "NO_APPDOMAIN" ]
"define": [ "NO_APPDOMAIN", "LOGCONTEXT" ]
},
"dependencies": {
"Microsoft.CSharp": "4.0.1-beta-23516",
Expand All @@ -42,6 +30,7 @@
"System.Linq": "4.0.1-beta-23516",
"System.Reflection.Extensions": "4.0.1-beta-23516",
"System.Runtime.Extensions": "4.0.11-beta-23516",
"System.Runtime.Serialization.Primitives": "4.1.0-beta-23516",
"System.Text.RegularExpressions": "4.0.11-beta-23516",
"System.Threading": "4.0.11-beta-23516",
"System.Threading.Thread": "4.0.0-beta-23516"
Expand All @@ -50,7 +39,8 @@
"dotnet5.4": {
"compilationOptions": {
"keyFile": "../../assets/Serilog.snk",
"define": [ "PROCESS", "FILE_IO", "PERIODIC_BATCHING", "NO_TIMER", "NO_APPDOMAIN" ]
"define": [ "ASYNCLOCAL", "LOGCONTEXT", "PROCESS", "FILE_IO", "PERIODIC_BATCHING", "NO_TIMER", "NO_APPDOMAIN", "USERNAMEFROMENV" ]

},
"dependencies": {
"Microsoft.CSharp": "4.0.1-beta-23516",
Expand All @@ -64,6 +54,7 @@
"System.Linq": "4.0.1-beta-23516",
"System.Reflection.Extensions": "4.0.1-beta-23516",
"System.Runtime.Extensions": "4.0.11-beta-23516",
"System.Runtime.Serialization.Primitives": "4.1.0-beta-23516",
"System.Text.RegularExpressions": "4.0.11-beta-23516",
"System.Threading": "4.0.11-beta-23516",
"System.Threading.Thread": "4.0.0-beta-23516"
Expand Down
18 changes: 15 additions & 3 deletions test/Serilog.Tests/Context/LogContextTests.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#if LOGCONTEXT
using System;
using System;
using System.IO;
using System.Runtime.Remoting.Messaging;
#if LOGCONTEXT
using Xunit;
using Serilog.Context;
using Serilog.Events;
using Serilog.Core.Enrichers;
using Serilog.Tests.Support;
#if REMOTING
using System.Runtime.Remoting.Messaging;
#endif
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -16,8 +18,12 @@ public class LogContextTests
{
public LogContextTests()
{
#if REMOTING
LogContext.PermitCrossAppDomainCalls = false;
#endif
#if !ASYNCLOCAL
CallContext.LogicalSetData(typeof(LogContext).FullName, null);
#endif
}

[Fact]
Expand Down Expand Up @@ -109,6 +115,7 @@ public async Task ContextPropertiesCrossAsyncCalls()
}
}

#if REMOTING
[Fact]
public async Task ContextPropertiesPersistWhenCrossAppDomainCallsAreEnabled()
{
Expand Down Expand Up @@ -137,7 +144,9 @@ public async Task ContextPropertiesPersistWhenCrossAppDomainCallsAreEnabled()
Assert.NotSame(pre, post);
}
}
#endif

#if !NO_APPDOMAIN
// Must not actually try to pass context across domains,
// since user property types may not be serializable.
// Fails if the Serilog assemblies cannot be loaded in the
Expand Down Expand Up @@ -180,6 +189,7 @@ public void DoesNotPreventCrossDomainCalls()
AppDomain.Unload(domain);
}
}
#endif

[Fact]
public void WhenSuspendedAllPropertiesAreRemovedFromTheContext()
Expand All @@ -205,6 +215,7 @@ public void WhenSuspendedAllPropertiesAreRemovedFromTheContext()
}
}

#if REMOTING
public class RemotelyCallable : MarshalByRefObject
{
public bool IsCallable()
Expand All @@ -223,5 +234,6 @@ public bool IsCallable()
return s == "42";
}
}
#endif
}
#endif
6 changes: 3 additions & 3 deletions test/Serilog.Tests/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@
"Serilog.Sinks.TextWriter": {"target": "project"}
},
"frameworks": {
"dnx451": {
"dnx452": {
"compilationOptions": {
"keyFile": "../../assets/Serilog.snk",
"define": [ "APPSETTINGS", "LOGCONTEXT", "PROCESS", "FILE_IO", "PERIODIC_BATCHING", "INTERNAL_TESTS" ]
"define": [ "APPSETTINGS", "LOGCONTEXT", "PROCESS", "FILE_IO", "PERIODIC_BATCHING", "INTERNAL_TESTS", "REMOTING" ]
},
"frameworkAssemblies": {
"System.Configuration": ""
}
},
"dnxcore50": {
"compilationOptions": {
"define": [ "FILE_IO", "PERIODIC_BATCHING" ]
"define": [ "ASYNCLOCAL", "LOGCONTEXT", "FILE_IO", "PERIODIC_BATCHING", "NO_APPDOMAIN" ]
},
"dependencies": {
"Microsoft.CSharp": "4.0.1-beta-23516",
Expand Down