| File: LoggerFactoryScopeProvider.cs | Web Access |
| Project: src\runtime\src\libraries\Microsoft.Extensions.Logging\src\Microsoft.Extensions.Logging.csproj (Microsoft.Extensions.Logging) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Threading; namespace Microsoft.Extensions.Logging { /// <summary> /// Default implementation of <see cref="IExternalScopeProvider"/> /// </summary> internal sealed class LoggerFactoryScopeProvider : IExternalScopeProvider { private readonly AsyncLocal<Scope?> _currentScope = new AsyncLocal<Scope?>(); private readonly ActivityTrackingOptions _activityTrackingOption; 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); // Tags and baggage are opt-in and thus we assume that most of the time it will not be used. if ((_activityTrackingOption & ActivityTrackingOptions.Tags) != 0 && activity.TagObjects.GetEnumerator().MoveNext()) { // As TagObjects is a IEnumerable<KeyValuePair<string, object?>> this can be used directly as a scope. // We do this to safe the allocation of a wrapper object. callback(activity.TagObjects, state); } if ((_activityTrackingOption & ActivityTrackingOptions.Baggage) != 0) { // Only access activity.Baggage as every call leads to an allocation IEnumerable<KeyValuePair<string, string?>> baggage = activity.Baggage; if (baggage.GetEnumerator().MoveNext()) { // For the baggage a wrapper object is necessary because we need to be able to overwrite ToString(). // In contrast to the TagsObject, Baggage doesn't have one underlining type where we can do this overwrite. ActivityBaggageLogScopeWrapper scope = GetOrCreateActivityBaggageLogScopeWrapper(activity, baggage); callback(scope, state); } } } } Report(_currentScope.Value); } private static ActivityBaggageLogScopeWrapper GetOrCreateActivityBaggageLogScopeWrapper(Activity activity, IEnumerable<KeyValuePair<string, string?>> items) { const string additionalItemsBaggagePropertyKey = "__ActivityBaggageItemsLogScope__"; var activityBaggageLogScopeWrapper = activity.GetCustomProperty(additionalItemsBaggagePropertyKey) as ActivityBaggageLogScopeWrapper; if (activityBaggageLogScopeWrapper == null) { activityBaggageLogScopeWrapper = new ActivityBaggageLogScopeWrapper(items); activity.SetCustomProperty(additionalItemsBaggagePropertyKey, activityBaggageLogScopeWrapper); } return activityBaggageLogScopeWrapper; } public IDisposable Push(object? state) { Scope? parent = _currentScope.Value; var newScope = new Scope(this, state, parent); _currentScope.Value = newScope; return newScope; } private sealed 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 sealed class ActivityLogScope : IReadOnlyList<KeyValuePair<string, object?>> { private string? _cachedToString; private const int MaxItems = 5; private readonly KeyValuePair<string, object?>[] _items = new KeyValuePair<string, object?>[MaxItems]; public ActivityLogScope(Activity activity, ActivityTrackingOptions activityTrackingOption) { Debug.Assert(activity != null); Debug.Assert(activityTrackingOption != ActivityTrackingOptions.None); int count = 0; if ((activityTrackingOption & ActivityTrackingOptions.SpanId) != 0) { _items[count++] = new KeyValuePair<string, object?>("SpanId", activity.GetSpanId()); } if ((activityTrackingOption & ActivityTrackingOptions.TraceId) != 0) { _items[count++] = new KeyValuePair<string, object?>("TraceId", activity.GetTraceId()); } if ((activityTrackingOption & ActivityTrackingOptions.ParentId) != 0) { _items[count++] = new KeyValuePair<string, object?>("ParentId", activity.GetParentId()); } if ((activityTrackingOption & ActivityTrackingOptions.TraceState) != 0) { _items[count++] = new KeyValuePair<string, object?>("TraceState", activity.TraceStateString); } if ((activityTrackingOption & ActivityTrackingOptions.TraceFlags) != 0) { _items[count++] = new KeyValuePair<string, object?>("TraceFlags", activity.ActivityTraceFlags); } Count = count; } public int Count { get; } public KeyValuePair<string, object?> this[int index] { get { if (index >= Count) { throw new ArgumentOutOfRangeException(nameof(index)); } return _items[index]; } } public override string ToString() { if (_cachedToString == null) { StringBuilder sb = new StringBuilder(); sb.Append(_items[0].Key); sb.Append(':'); sb.Append(_items[0].Value); for (int i = 1; i < Count; i++) { sb.Append(", "); sb.Append(_items[i].Key); sb.Append(':'); sb.Append(_items[i].Value); } _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(); } } private sealed class ActivityBaggageLogScopeWrapper : IEnumerable<KeyValuePair<string, object?>> { private readonly IEnumerable<KeyValuePair<string, string?>> _items; private StringBuilder? _stringBuilder; public ActivityBaggageLogScopeWrapper(IEnumerable<KeyValuePair<string, string?>> items) { _items = items; } public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() => new BaggageEnumerator(_items.GetEnumerator()); IEnumerator IEnumerable.GetEnumerator() => new BaggageEnumerator(_items.GetEnumerator()); public override string ToString() { lock (this) { IEnumerator<KeyValuePair<string, string?>> enumerator = _items.GetEnumerator(); if (!enumerator.MoveNext()) { return string.Empty; } _stringBuilder ??= new StringBuilder(); _stringBuilder.Append(enumerator.Current.Key); _stringBuilder.Append(':'); _stringBuilder.Append(enumerator.Current.Value); while (enumerator.MoveNext()) { _stringBuilder.Append(", "); _stringBuilder.Append(enumerator.Current.Key); _stringBuilder.Append(':'); _stringBuilder.Append(enumerator.Current.Value); } string result = _stringBuilder.ToString(); _stringBuilder.Clear(); return result; } } private struct BaggageEnumerator : IEnumerator<KeyValuePair<string, object?>> { private readonly IEnumerator<KeyValuePair<string, string?>> _enumerator; public BaggageEnumerator(IEnumerator<KeyValuePair<string, string?>> enumerator) { _enumerator = enumerator; } public KeyValuePair<string, object?> Current => new KeyValuePair<string, object?>(_enumerator.Current.Key, _enumerator.Current.Value); object? IEnumerator.Current => Current; public void Dispose() => _enumerator.Dispose(); public bool MoveNext() => _enumerator.MoveNext(); public void Reset() => _enumerator.Reset(); } } } 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; } } }