Skip to content

Commit

Permalink
Improve LoggerFactory and Logger debugging (#88313)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK authored Jul 4, 2023
1 parent 2fbb7eb commit 956be4b
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@ namespace Microsoft.Extensions.Logging
internal static class DebuggerDisplayFormatting
{
internal static string DebuggerToString(string name, ILogger logger)
{
LogLevel? minimumLevel = CalculateEnabledLogLevel(logger);

var debugText = $@"Name = ""{name}""";
if (minimumLevel != null)
{
debugText += $", MinLevel = {minimumLevel}";
}
else
{
// Display "Enabled = false". This makes it clear that the entire ILogger
// is disabled and nothing is written.
//
// If "MinLevel = None" was displayed then someone could think that the
// min level is disabled and everything is written.
debugText += $", Enabled = false";
}

return debugText;
}

internal static LogLevel? CalculateEnabledLogLevel(ILogger logger)
{
ReadOnlySpan<LogLevel> logLevels = stackalloc LogLevel[]
{
Expand All @@ -19,7 +41,7 @@ internal static string DebuggerToString(string name, ILogger logger)
LogLevel.Trace,
};

LogLevel minimumLevel = LogLevel.None;
LogLevel? minimumLevel = null;

// Check log level from highest to lowest. Report the lowest log level.
foreach (LogLevel logLevel in logLevels)
Expand All @@ -32,22 +54,7 @@ internal static string DebuggerToString(string name, ILogger logger)
minimumLevel = logLevel;
}

var debugText = $@"Name = ""{name}""";
if (minimumLevel != LogLevel.None)
{
debugText += $", MinLevel = {minimumLevel}";
}
else
{
// Display "Enabled = false". This makes it clear that the entire ILogger
// is disabled and nothing is written.
//
// If "MinLevel = None" was displayed then someone could think that the
// min level is disabled and everything is written.
debugText += $", Enabled = false";
}

return debugText;
return minimumLevel;
}
}
}
96 changes: 93 additions & 3 deletions src/libraries/Microsoft.Extensions.Logging/src/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Microsoft.Extensions.Logging
{
[DebuggerDisplay("{DebuggerToString(),nq}")]
[DebuggerTypeProxy(typeof(LoggerDebugView))]
internal sealed class Logger : ILogger
{
private readonly string _categoryName;
Expand Down Expand Up @@ -150,15 +152,103 @@ static bool LoggerIsEnabled(LogLevel logLevel, ILogger logger, ref List<Exceptio
return scope;
}

private static void ThrowLoggingError(List<Exception> exceptions)
{
throw new AggregateException(
message: "An error occurred while writing to logger(s).", innerExceptions: exceptions);
}

internal string DebuggerToString()
{
return DebuggerDisplayFormatting.DebuggerToString(_categoryName, this);
}

private static void ThrowLoggingError(List<Exception> exceptions)
private sealed class LoggerDebugView(Logger logger)
{
throw new AggregateException(
message: "An error occurred while writing to logger(s).", innerExceptions: exceptions);
public string Name => logger._categoryName;

// The list of providers includes the full list of configured providers from the logger factory.
// It then mentions the min level and enable status of each provider for this logger.
public List<LoggerProviderDebugView> Providers
{
get
{
List<LoggerProviderDebugView> providers = new List<LoggerProviderDebugView>();
for (int i = 0; i < logger.Loggers.Length; i++)
{
LoggerInformation loggerInfo = logger.Loggers[i];
string providerName = ProviderAliasUtilities.GetAlias(loggerInfo.ProviderType) ?? loggerInfo.ProviderType.Name;
MessageLogger? messageLogger = logger.MessageLoggers?.FirstOrDefault(messageLogger => messageLogger.Logger == loggerInfo.Logger);

providers.Add(new LoggerProviderDebugView(providerName, messageLogger));
}

return providers;
}
}

public List<object?>? Scopes
{
get
{
var scopeProvider = logger.ScopeLoggers?.FirstOrDefault().ExternalScopeProvider;
if (scopeProvider == null)
{
return null;
}

List<object?> scopes = new List<object?>();
scopeProvider.ForEachScope((scope, scopes) => scopes!.Add(scope), scopes);
return scopes;
}
}
public LogLevel? MinLevel => DebuggerDisplayFormatting.CalculateEnabledLogLevel(logger);
public bool Enabled => DebuggerDisplayFormatting.CalculateEnabledLogLevel(logger) != null;
}

[DebuggerDisplay("{DebuggerToString(),nq}")]
private sealed class LoggerProviderDebugView(string providerName, MessageLogger? messageLogger)
{
public string Name => providerName;
public LogLevel LogLevel => CalculateEnabledLogLevel(messageLogger) ?? LogLevel.None;

private static LogLevel? CalculateEnabledLogLevel(MessageLogger? logger)
{
if (logger == null)
{
return null;
}

ReadOnlySpan<LogLevel> logLevels = stackalloc LogLevel[]
{
LogLevel.Critical,
LogLevel.Error,
LogLevel.Warning,
LogLevel.Information,
LogLevel.Debug,
LogLevel.Trace,
};

LogLevel? minimumLevel = null;

// Check log level from highest to lowest. Report the lowest log level.
foreach (LogLevel logLevel in logLevels)
{
if (!logger.Value.IsEnabled(logLevel))
{
break;
}

minimumLevel = logLevel;
}

return minimumLevel;
}

private string DebuggerToString()
{
return $@"Name = ""{providerName}"", LogLevel = {LogLevel}";
}
}

private sealed class Scope : IDisposable
Expand Down
16 changes: 16 additions & 0 deletions src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

Expand All @@ -13,6 +15,8 @@ namespace Microsoft.Extensions.Logging
/// <summary>
/// Produces instances of <see cref="ILogger"/> classes based on the given providers.
/// </summary>
[DebuggerDisplay("{DebuggerToString(),nq}")]
[DebuggerTypeProxy(typeof(LoggerFactoryDebugView))]
public class LoggerFactory : ILoggerFactory
{
private readonly ConcurrentDictionary<string, Logger> _loggers = new ConcurrentDictionary<string, Logger>(StringComparer.Ordinal);
Expand Down Expand Up @@ -314,5 +318,17 @@ public void AddProvider(ILoggerProvider provider)
_loggerFactory.AddProvider(provider);
}
}

private string DebuggerToString()
{
return $"Providers = {_providerRegistrations.Count}, {_filterOptions.DebuggerToString()}";
}

private sealed class LoggerFactoryDebugView(LoggerFactory loggerFactory)
{
public List<ILoggerProvider> Providers => loggerFactory._providerRegistrations.Select(r => r.Provider).ToList();
public bool Disposed => loggerFactory._disposed;
public LoggerFilterOptions FilterOptions => loggerFactory._filterOptions;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

namespace Microsoft.Extensions.Logging
{
/// <summary>
/// The options for a LoggerFactory.
/// </summary>
[DebuggerDisplay("ActivityTrackingOptions = {ActivityTrackingOptions}")]
public class LoggerFactoryOptions
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;

namespace Microsoft.Extensions.Logging
{
/// <summary>
/// The options for a LoggerFilter.
/// </summary>
[DebuggerDisplay("{DebuggerToString(),nq}")]
public class LoggerFilterOptions
{
/// <summary>
Expand All @@ -32,5 +34,30 @@ public LoggerFilterOptions() { }

// Concrete representation of the rule list
internal List<LoggerFilterRule> RulesInternal { get; } = new List<LoggerFilterRule>();

internal string DebuggerToString()
{
string debugText;
if (MinLevel != LogLevel.None)
{
debugText = $"MinLevel = {MinLevel}";
}
else
{
// Display "Enabled = false". This makes it clear that the entire ILogger
// is disabled and nothing is written.
//
// If "MinLevel = None" was displayed then someone could think that the
// min level is disabled and everything is written.
debugText = $"Enabled = false";
}

if (Rules.Count > 0)
{
debugText += $", Rules = {Rules.Count}";
}

return debugText;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.Extensions.Logging
{
internal readonly struct MessageLogger
{
public MessageLogger(ILogger logger, string? category, string? providerTypeFullName, LogLevel? minLevel, Func<string?, string?, LogLevel, bool>? filter)
public MessageLogger(ILogger logger, string category, string? providerTypeFullName, LogLevel? minLevel, Func<string?, string?, LogLevel, bool>? filter)
{
Logger = logger;
Category = category;
Expand All @@ -19,7 +19,7 @@ public MessageLogger(ILogger logger, string? category, string? providerTypeFullN

public ILogger Logger { get; }

public string? Category { get; }
public string Category { get; }

private string? ProviderTypeFullName { get; }

Expand Down

0 comments on commit 956be4b

Please sign in to comment.