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

Add activity Ids and Context to log scopes #37092

Merged
merged 5 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,28 @@ public partial interface ILoggingBuilder
{
Microsoft.Extensions.DependencyInjection.IServiceCollection Services { get; }
}
[System.Flags]
public enum ActivityTrackingOptions
{
None = 0x0000,
SpanId = 0x0001,
TraceId = 0x0002,
ParentId = 0x0004,
TraceState = 0x0008,
TraceFlags = 0x0010
}
public class LoggerFactoryOptions
{
public LoggerFactoryOptions() { }
public ActivityTrackingOptions ActivityTrackingOptions { get {throw null; } set { throw null; } }
}
public partial class LoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory, System.IDisposable
{
public LoggerFactory() { }
public LoggerFactory(System.Collections.Generic.IEnumerable<Microsoft.Extensions.Logging.ILoggerProvider> providers) { }
public LoggerFactory(System.Collections.Generic.IEnumerable<Microsoft.Extensions.Logging.ILoggerProvider> providers, Microsoft.Extensions.Logging.LoggerFilterOptions filterOptions) { }
public LoggerFactory(System.Collections.Generic.IEnumerable<Microsoft.Extensions.Logging.ILoggerProvider> providers, Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Extensions.Logging.LoggerFilterOptions> filterOption) { }
public LoggerFactory(System.Collections.Generic.IEnumerable<Microsoft.Extensions.Logging.ILoggerProvider> providers, Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Extensions.Logging.LoggerFilterOptions> filterOption, Microsoft.Extensions.Options.IOptions<Microsoft.Extensions.Logging.LoggerFactoryOptions> options = null) { }
public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) { }
protected virtual bool CheckDisposed() { throw null; }
public static Microsoft.Extensions.Logging.ILoggerFactory Create(System.Action<Microsoft.Extensions.Logging.ILoggingBuilder> configure) { throw null; }
Expand Down Expand Up @@ -73,6 +89,7 @@ public static partial class LoggingBuilderExtensions
public static Microsoft.Extensions.Logging.ILoggingBuilder AddProvider(this Microsoft.Extensions.Logging.ILoggingBuilder builder, Microsoft.Extensions.Logging.ILoggerProvider provider) { throw null; }
public static Microsoft.Extensions.Logging.ILoggingBuilder ClearProviders(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { throw null; }
public static Microsoft.Extensions.Logging.ILoggingBuilder SetMinimumLevel(this Microsoft.Extensions.Logging.ILoggingBuilder builder, Microsoft.Extensions.Logging.LogLevel level) { throw null; }
public static Microsoft.Extensions.Logging.ILoggingBuilder Configure(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action<Microsoft.Extensions.Logging.LoggerFactoryOptions> action) { throw null; }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=false, Inherited=false)]
public partial class ProviderAliasAttribute : System.Attribute
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;

namespace Microsoft.Extensions.Logging
{
/// <summary>
/// Flags to indicate which trace context parts should be included with the logging scopes.
/// </summary>
[Flags]
public enum ActivityTrackingOptions
{
/// <summary>
/// None of the trace context part wil be included in the logging.
/// </summary>
None = 0x0000,

/// <summary>
/// Span Id wil be included in the logging.
/// </summary>
SpanId = 0x0001,

/// <summary>
/// Trace Id wil be included in the logging.
/// </summary>
TraceId = 0x0002,

/// <summary>
/// Parent Id wil be included in the logging.
/// </summary>
ParentId = 0x0004,

/// <summary>
/// Trace State wil be included in the logging.
/// </summary>
TraceState = 0x0008,

/// <summary>
/// Trace flags wil be included in the logging.
/// </summary>
TraceFlags = 0x0010
}
}
19 changes: 16 additions & 3 deletions src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public class LoggerFactory : ILoggerFactory
private volatile bool _disposed;
private IDisposable _changeTokenRegistration;
private LoggerFilterOptions _filterOptions;
private LoggerExternalScopeProvider _scopeProvider;
private LoggerFactoryScopeProvider _scopeProvider;
private LoggerFactoryOptions _factoryOptions;

/// <summary>
/// Creates a new <see cref="LoggerFactory"/> instance.
Expand Down Expand Up @@ -54,8 +55,20 @@ public LoggerFactory() : this(Enumerable.Empty<ILoggerProvider>())
/// </summary>
/// <param name="providers">The providers to use in producing <see cref="ILogger"/> instances.</param>
/// <param name="filterOption">The filter option to use.</param>
public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption)
public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption) : this(providers, filterOption, null)
{
}

/// <summary>
/// Creates a new <see cref="LoggerFactory"/> instance.
/// </summary>
/// <param name="providers">The providers to use in producing <see cref="ILogger"/> instances.</param>
/// <param name="filterOption">The filter option to use.</param>
/// <param name="options">The <see cref="LoggerFactoryOptions"/>.</param>
public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption, IOptions<LoggerFactoryOptions> options = null)
{
_factoryOptions = options == null || options.Value == null ? new LoggerFactoryOptions() : options.Value;

foreach (var provider in providers)
{
AddProviderRegistration(provider, dispose: false);
Expand Down Expand Up @@ -164,7 +177,7 @@ private void AddProviderRegistration(ILoggerProvider provider, bool dispose)
{
if (_scopeProvider == null)
{
_scopeProvider = new LoggerExternalScopeProvider();
_scopeProvider = new LoggerFactoryScopeProvider(_factoryOptions.ActivityTrackingOptions);
}

supportsExternalScope.SetScopeProvider(_scopeProvider);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;

namespace Microsoft.Extensions.Logging
{
/// <summary>
/// The options for a LoggerFactory.
/// </summary>
public class LoggerFactoryOptions
{
/// <summary>
/// Creates a new <see cref="LoggerFactoryOptions"/> instance.
/// </summary>
public LoggerFactoryOptions() { }
tarekgh marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets or sets <see cref="LoggerFactoryOptions"/> value to indicate which parts of the tracing context information should be included with the logging scopes.
/// </summary>
public ActivityTrackingOptions ActivityTrackingOptions { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Text;
using System.Globalization;
using System.Threading;
using System.Collections;
using System.Diagnostics;
using System.Collections.Generic;

namespace Microsoft.Extensions.Logging
{
/// <summary>
/// Default implementation of <see cref="IExternalScopeProvider"/>
/// </summary>
internal class LoggerFactoryScopeProvider : IExternalScopeProvider
{
private readonly AsyncLocal<Scope> _currentScope = new AsyncLocal<Scope>();
private ActivityTrackingOptions _activityTrackingOption;
tarekgh marked this conversation as resolved.
Show resolved Hide resolved

public LoggerFactoryScopeProvider(ActivityTrackingOptions activityTrackingOption) => _activityTrackingOption = activityTrackingOption;

public void ForEachScope<TState>(Action<object, TState> callback, TState state)
{
void Report(Scope current)
{
if (current == null)
{
return;
}
Report(current.Parent);
callback(current.State, state);
}

if (_activityTrackingOption != ActivityTrackingOptions.None)
{
Activity activity = Activity.Current;
if (activity != null)
{
const string propertyKey = "__ActivityLogScope__";

ActivityLogScope activityLogScope = activity.GetCustomProperty(propertyKey) as ActivityLogScope;
if (activityLogScope == null)
{
activityLogScope = new ActivityLogScope(activity, _activityTrackingOption);
activity.SetCustomProperty(propertyKey, activityLogScope);
}

callback(activityLogScope, state);
}
}

Report(_currentScope.Value);
}

public IDisposable Push(object state)
{
var parent = _currentScope.Value;
var newScope = new Scope(this, state, parent);
_currentScope.Value = newScope;

return newScope;
}

private class Scope : IDisposable
{
private readonly LoggerFactoryScopeProvider _provider;
private bool _isDisposed;

internal Scope(LoggerFactoryScopeProvider provider, object state, Scope parent)
{
_provider = provider;
State = state;
Parent = parent;
}

public Scope Parent { get; }

public object State { get; }

public override string ToString()
{
return State?.ToString();
}

public void Dispose()
{
if (!_isDisposed)
{
_provider._currentScope.Value = Parent;
_isDisposed = true;
}
}
}

private class ActivityLogScope : IReadOnlyList<KeyValuePair<string, object>>
{
private readonly Activity _activity;
private readonly ActivityTrackingOptions _activityTrackingOption;

private string _cachedToString;

public int Count
{
get
{
return 3;
tarekgh marked this conversation as resolved.
Show resolved Hide resolved
}
}

public KeyValuePair<string, object> this[int index]
{
get
{
if (index == 0)
davidfowl marked this conversation as resolved.
Show resolved Hide resolved
{
return new KeyValuePair<string, object>("SpanId", _activity.GetSpanId());
}
else if (index == 1)
{
return new KeyValuePair<string, object>("TraceId", _activity.GetTraceId());
}
else if (index == 2)
{
return new KeyValuePair<string, object>("ParentId", _activity.GetParentId());
}

throw new ArgumentOutOfRangeException(nameof(index));
}
}

public ActivityLogScope(Activity activity, ActivityTrackingOptions activityTrackingOption)
{
Debug.Assert(activityTrackingOption != ActivityTrackingOptions.None);
_activityTrackingOption = activityTrackingOption;
_activity = activity;
}

public override string ToString()
{
if (_cachedToString == null)
{
StringBuilder sb = new StringBuilder();
if ((_activityTrackingOption & ActivityTrackingOptions.SpanId) != 0)
{
sb.Append($"SpanId:{_activity.GetSpanId()}");
}

if ((_activityTrackingOption & ActivityTrackingOptions.TraceId) != 0)
{
sb.Append(sb.Length > 0 ? $", TraceId:{_activity.GetTraceId()}" : $"TraceId:{_activity.GetTraceId()}");
}

if ((_activityTrackingOption & ActivityTrackingOptions.ParentId) != 0)
{
sb.Append(sb.Length > 0 ? $", ParentId:{_activity.GetParentId()}" : $"TraceId:{_activity.GetParentId()}");
}

if ((_activityTrackingOption & ActivityTrackingOptions.TraceFlags) != 0)
{
sb.Append(sb.Length > 0 ? $", TraceFlags:{_activity.ActivityTraceFlags}" : $"TraceFlags:{_activity.ActivityTraceFlags}");
}

if ((_activityTrackingOption & ActivityTrackingOptions.TraceState) != 0 && _activity.TraceStateString != null)
{
sb.Append(sb.Length > 0 ? $", TraceState:{_activity.TraceStateString}" : $"TraceState:{_activity.TraceStateString}");
}

_cachedToString = sb.ToString();

}

return _cachedToString;
}

public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
for (int i = 0; i < Count; ++i)
{
yield return this[i];
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
internal static class ActivityExtensions
{
public static string GetSpanId(this Activity activity)
{
return activity.IdFormat switch
{
ActivityIdFormat.Hierarchical => activity.Id,
ActivityIdFormat.W3C => activity.SpanId.ToHexString(),
_ => null,
} ?? string.Empty;
}

public static string GetTraceId(this Activity activity)
{
return activity.IdFormat switch
{
ActivityIdFormat.Hierarchical => activity.RootId,
ActivityIdFormat.W3C => activity.TraceId.ToHexString(),
_ => null,
} ?? string.Empty;
}

public static string GetParentId(this Activity activity)
{
return activity.IdFormat switch
{
ActivityIdFormat.Hierarchical => activity.ParentId,
ActivityIdFormat.W3C => activity.ParentSpanId.ToHexString(),
_ => null,
} ?? string.Empty;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -48,5 +49,17 @@ public static ILoggingBuilder ClearProviders(this ILoggingBuilder builder)
builder.Services.RemoveAll<ILoggerProvider>();
return builder;
}

/// <summary>
/// Configure the <paramref name="builder"/> with the <see cref="LoggerFactoryOptions"/>.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to be configured with <see cref="LoggerFactoryOptions"/></param>
/// <param name="action">The action used to configure the logger factory</param>
/// <returns>The <see cref="ILoggingBuilder"/> so that additional calls can be chained.</returns>
public static ILoggingBuilder Configure(this ILoggingBuilder builder, Action<LoggerFactoryOptions> action)
{
builder.Services.Configure(action);
return builder;
}
}
}
Loading