diff --git a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorLanguageServerBenchmarkBase.cs b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorLanguageServerBenchmarkBase.cs index 50d6d8246f3..e4bb22ffc79 100644 --- a/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorLanguageServerBenchmarkBase.cs +++ b/src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorLanguageServerBenchmarkBase.cs @@ -19,7 +19,6 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.CommonLanguageServerProtocol.Framework; using Microsoft.Extensions.DependencyInjection; -using Microsoft.VisualStudio.LanguageServer.Protocol; using Nerdbank.Streams; namespace Microsoft.AspNetCore.Razor.Microbenchmarks.LanguageServer; diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.LoggerFactoryWrapper.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.LoggerFactoryWrapper.cs index 479e9d09797..4899cad1fd8 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.LoggerFactoryWrapper.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.LoggerFactoryWrapper.cs @@ -14,7 +14,7 @@ internal partial class RazorLanguageServer /// private sealed class LoggerFactoryWrapper(ILoggerFactory loggerFactory) : ILoggerFactory { - private ILoggerFactory _loggerFactory = loggerFactory; + private readonly ILoggerFactory _loggerFactory = loggerFactory; public void AddLoggerProvider(ILoggerProvider provider) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.AggregateLogger.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.AggregateLogger.cs index 39a4ab4ecdc..30fb12947f5 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.AggregateLogger.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.AggregateLogger.cs @@ -8,15 +8,36 @@ namespace Microsoft.CodeAnalysis.Razor.Logging; internal abstract partial class AbstractLoggerFactory { - private class AggregateLogger(ImmutableArray loggers) : ILogger + private sealed class LazyLogger(Lazy lazyProvider, string categoryName) { - private ImmutableArray _loggers = loggers; + private readonly LoggerProviderMetadata _metadata = lazyProvider.Metadata; + private readonly Lazy _lazyLogger = new(() => lazyProvider.Value.CreateLogger(categoryName)); + + public ILogger Instance => _lazyLogger.Value; + + public bool IsEnabled(LogLevel logLevel) + { + // If the ILoggerProvider's metadata has a minimum log level, we can use that + // rather than forcing the ILoggerProvider to be created. + if (_metadata.MinimumLogLevel is LogLevel minimumLogLevel && + !logLevel.IsAtLeast(minimumLogLevel)) + { + return false; + } + + return Instance.IsEnabled(logLevel); + } + } + + private class AggregateLogger(ImmutableArray lazyLoggers) : ILogger + { + private ImmutableArray _lazyLoggers = lazyLoggers; public bool IsEnabled(LogLevel logLevel) { - foreach (var logger in _loggers) + foreach (var lazyLogger in _lazyLoggers) { - if (logger.IsEnabled(logLevel)) + if (lazyLogger.IsEnabled(logLevel)) { return true; } @@ -27,18 +48,18 @@ public bool IsEnabled(LogLevel logLevel) public void Log(LogLevel logLevel, string message, Exception? exception) { - foreach (var logger in _loggers) + foreach (var lazyLogger in _lazyLoggers) { - if (logger.IsEnabled(logLevel)) + if (lazyLogger.IsEnabled(logLevel)) { - logger.Log(logLevel, message, exception); + lazyLogger.Instance.Log(logLevel, message, exception); } } } - internal void AddLogger(ILogger logger) + internal void AddLogger(LazyLogger lazyLogger) { - ImmutableInterlocked.Update(ref _loggers, (set, l) => set.Add(l), logger); + ImmutableInterlocked.Update(ref _lazyLoggers, (set, l) => set.Add(l), lazyLogger); } } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.cs index 70ae45dbf27..5e4ff3f21c7 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.cs @@ -13,10 +13,15 @@ namespace Microsoft.CodeAnalysis.Razor.Logging; internal abstract partial class AbstractLoggerFactory : ILoggerFactory { + private ImmutableArray> _providers; private ImmutableDictionary _loggers; - private ImmutableArray _providers; protected AbstractLoggerFactory(ImmutableArray providers) + : this(providers.SelectAsArray(p => new Lazy(() => p, LoggerProviderMetadata.Empty))) + { + } + + protected AbstractLoggerFactory(ImmutableArray> providers) { _providers = providers; _loggers = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); @@ -29,24 +34,26 @@ public ILogger GetOrCreateLogger(string categoryName) return logger; } - using var loggers = new PooledArrayBuilder(_providers.Length); + using var lazyLoggers = new PooledArrayBuilder(_providers.Length); foreach (var provider in _providers) { - loggers.Add(provider.CreateLogger(categoryName)); + lazyLoggers.Add(new(provider, categoryName)); } - var result = new AggregateLogger(loggers.DrainToImmutable()); + var result = new AggregateLogger(lazyLoggers.DrainToImmutable()); return ImmutableInterlocked.AddOrUpdate(ref _loggers, categoryName, result, (k, v) => v); } public void AddLoggerProvider(ILoggerProvider provider) { - if (ImmutableInterlocked.Update(ref _providers, (set, p) => set.Add(p), provider)) + var lazyProvider = new Lazy(() => provider, LoggerProviderMetadata.Empty); + + if (ImmutableInterlocked.Update(ref _providers, (set, p) => set.Add(p), lazyProvider)) { - foreach (var (category, logger) in _loggers) + foreach (var (categoryName, logger) in _loggers) { - logger.AddLogger(provider.CreateLogger(category)); + logger.AddLogger(new(lazyProvider, categoryName)); } } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/LoggerProviderMetadata.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/LoggerProviderMetadata.cs new file mode 100644 index 00000000000..bc49df3801e --- /dev/null +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/LoggerProviderMetadata.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.CodeAnalysis.Razor.Logging; + +internal sealed class LoggerProviderMetadata +{ + public static LoggerProviderMetadata Empty { get; } = new(); + + public LogLevel? MinimumLogLevel { get; } + + private LoggerProviderMetadata() + { + } + + public LoggerProviderMetadata(IDictionary data) + : this() + { + MinimumLogLevel = data.TryGetValue(nameof(MinimumLogLevel), out var minimumLogLevel) + ? (LogLevel?)minimumLogLevel + : null; + } +} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLogger.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLogger.cs index 2b30a860fa7..5831dfa19e5 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLogger.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLogger.cs @@ -9,16 +9,10 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Logging; -internal sealed class RazorLogHubLogger : ILogger +internal sealed class RazorLogHubLogger(string categoryName, RazorLogHubTraceProvider traceProvider) : ILogger { - private string _categoryName; - private RazorLogHubTraceProvider _traceProvider; - - public RazorLogHubLogger(string categoryName, RazorLogHubTraceProvider traceProvider) - { - _categoryName = categoryName; - _traceProvider = traceProvider; - } + private readonly string _categoryName = categoryName; + private readonly RazorLogHubTraceProvider _traceProvider = traceProvider; public bool IsEnabled(LogLevel logLevel) { diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLoggerProvider.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLoggerProvider.cs index 1625ff868dc..fee3b5a76ef 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLoggerProvider.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLoggerProvider.cs @@ -7,16 +7,11 @@ namespace Microsoft.VisualStudio.Razor.LanguageClient.Logging; -[Export(typeof(ILoggerProvider))] -internal sealed class RazorLogHubLoggerProvider : ILoggerProvider +[ExportLoggerProvider] +[method: ImportingConstructor] +internal sealed class RazorLogHubLoggerProvider(RazorLogHubTraceProvider traceProvider) : ILoggerProvider { - private readonly RazorLogHubTraceProvider _traceProvider; - - [ImportingConstructor] - public RazorLogHubLoggerProvider(RazorLogHubTraceProvider traceProvider) - { - _traceProvider = traceProvider; - } + private readonly RazorLogHubTraceProvider _traceProvider = traceProvider; public ILogger CreateLogger(string categoryName) { diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/ActivityLogLoggerProvider.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/ActivityLogLoggerProvider.cs index 85f005fd87f..274f9953520 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/ActivityLogLoggerProvider.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/ActivityLogLoggerProvider.cs @@ -10,7 +10,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Logging; /// /// An that logs any warnings or errors to the Visual Studio Activity Log. /// -[Export(typeof(ILoggerProvider))] +[ExportLoggerProvider(minimumLogLevel: LogLevel.Warning)] [method: ImportingConstructor] internal sealed partial class ActivityLogLoggerProvider(RazorActivityLog activityLog) : ILoggerProvider { diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/ExportLoggerProviderAttribute.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/ExportLoggerProviderAttribute.cs new file mode 100644 index 00000000000..804c99b4057 --- /dev/null +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/ExportLoggerProviderAttribute.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Razor.Logging; + +namespace Microsoft.VisualStudio.Razor.Logging; + +[MetadataAttribute] +[AttributeUsage(AttributeTargets.Class)] +internal sealed class ExportLoggerProviderAttribute : ExportAttribute +{ + public LogLevel? MinimumLogLevel { get; } + + public ExportLoggerProviderAttribute() + : base(typeof(ILoggerProvider)) + { + MinimumLogLevel = null; + } + + public ExportLoggerProviderAttribute(LogLevel minimumLogLevel) + : base(typeof(ILoggerProvider)) + { + MinimumLogLevel = minimumLogLevel; + } +} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/MemoryLoggerProvider.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/MemoryLoggerProvider.cs index f60b0644857..1d06f244c8b 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/MemoryLoggerProvider.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/MemoryLoggerProvider.cs @@ -1,14 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. -using System.ComponentModel.Composition; using Microsoft.CodeAnalysis.Razor.Logging; namespace Microsoft.VisualStudio.Razor.Logging; -[Export(typeof(ILoggerProvider))] -[method: ImportingConstructor] -internal partial class MemoryLoggerProvider() : ILoggerProvider +[ExportLoggerProvider] +internal partial class MemoryLoggerProvider : ILoggerProvider { // How many messages will the buffer contain private const int BufferSize = 5000; diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/OutputWindowLoggerProvider.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/OutputWindowLoggerProvider.cs index 0d84a995c64..70a775d6120 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/OutputWindowLoggerProvider.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/OutputWindowLoggerProvider.cs @@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.Razor.Logging; -[Export(typeof(ILoggerProvider))] +[ExportLoggerProvider] [method: ImportingConstructor] internal class OutputWindowLoggerProvider( // Anything this class imports would have a circular dependency if they tried to log anything, diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/VisualStudioLoggerFactory.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/VisualStudioLoggerFactory.cs index 2a68757a076..6475495b043 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/VisualStudioLoggerFactory.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/VisualStudioLoggerFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.Composition; @@ -10,7 +11,7 @@ namespace Microsoft.VisualStudio.Razor.Logging; [Export(typeof(ILoggerFactory))] [method: ImportingConstructor] -internal sealed class VisualStudioLoggerFactory([ImportMany] IEnumerable providers) +internal sealed class VisualStudioLoggerFactory([ImportMany] IEnumerable> providers) : AbstractLoggerFactory(providers.ToImmutableArray()) { } diff --git a/src/Razor/src/rzls/LoggerFactory.cs b/src/Razor/src/rzls/LoggerFactory.cs index d4c5d8425ff..d6213effb6a 100644 --- a/src/Razor/src/rzls/LoggerFactory.cs +++ b/src/Razor/src/rzls/LoggerFactory.cs @@ -1,12 +1,13 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Razor.Logging; namespace Microsoft.AspNetCore.Razor.LanguageServer; -internal sealed class LoggerFactory(ImmutableArray providers) +internal sealed class LoggerFactory(ImmutableArray> providers) : AbstractLoggerFactory(providers) { } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/DictionaryExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/DictionaryExtensions.cs index cb57d0c3aad..3abd74568d3 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/DictionaryExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/DictionaryExtensions.cs @@ -76,21 +76,4 @@ public static TValue GetOrAdd( return value; } } - - public static TValue GetValueOrDefault( - this Dictionary dictionary, - TKey key, - TValue defaultValue) - where TKey : notnull - { - if (dictionary.TryGetValue(key, out var existingValue)) - { - return existingValue; - } - else - { - dictionary.Add(key, defaultValue); - return defaultValue; - } - } }