Skip to content

Commit 2f71d65

Browse files
Lazily import ILoggerProviders (#10327)
Fixes #10326 This change makes our `AbstractLoggerFactory` take a set of `Lazy<ILoggerProvider>` instances and only realize them the first time a message is logged. 🤔 _I wonder if it would be possible to change our `ILoggerProvider` instances to declare their minimum `LogLevel` via MEF and import that as metadata? That would allow us to avoid instantiating all the logger providers just to call `IsEnabled`._
2 parents 05aed40 + 66566cd commit 2f71d65

File tree

14 files changed

+112
-61
lines changed

14 files changed

+112
-61
lines changed

src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/RazorLanguageServerBenchmarkBase.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
using Microsoft.CodeAnalysis.Text;
2020
using Microsoft.CommonLanguageServerProtocol.Framework;
2121
using Microsoft.Extensions.DependencyInjection;
22-
using Microsoft.VisualStudio.LanguageServer.Protocol;
2322
using Nerdbank.Streams;
2423

2524
namespace Microsoft.AspNetCore.Razor.Microbenchmarks.LanguageServer;

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorLanguageServer.LoggerFactoryWrapper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal partial class RazorLanguageServer
1414
/// </summary>
1515
private sealed class LoggerFactoryWrapper(ILoggerFactory loggerFactory) : ILoggerFactory
1616
{
17-
private ILoggerFactory _loggerFactory = loggerFactory;
17+
private readonly ILoggerFactory _loggerFactory = loggerFactory;
1818

1919
public void AddLoggerProvider(ILoggerProvider provider)
2020
{

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.AggregateLogger.cs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,36 @@ namespace Microsoft.CodeAnalysis.Razor.Logging;
88

99
internal abstract partial class AbstractLoggerFactory
1010
{
11-
private class AggregateLogger(ImmutableArray<ILogger> loggers) : ILogger
11+
private sealed class LazyLogger(Lazy<ILoggerProvider, LoggerProviderMetadata> lazyProvider, string categoryName)
1212
{
13-
private ImmutableArray<ILogger> _loggers = loggers;
13+
private readonly LoggerProviderMetadata _metadata = lazyProvider.Metadata;
14+
private readonly Lazy<ILogger> _lazyLogger = new(() => lazyProvider.Value.CreateLogger(categoryName));
15+
16+
public ILogger Instance => _lazyLogger.Value;
17+
18+
public bool IsEnabled(LogLevel logLevel)
19+
{
20+
// If the ILoggerProvider's metadata has a minimum log level, we can use that
21+
// rather than forcing the ILoggerProvider to be created.
22+
if (_metadata.MinimumLogLevel is LogLevel minimumLogLevel &&
23+
!logLevel.IsAtLeast(minimumLogLevel))
24+
{
25+
return false;
26+
}
27+
28+
return Instance.IsEnabled(logLevel);
29+
}
30+
}
31+
32+
private class AggregateLogger(ImmutableArray<LazyLogger> lazyLoggers) : ILogger
33+
{
34+
private ImmutableArray<LazyLogger> _lazyLoggers = lazyLoggers;
1435

1536
public bool IsEnabled(LogLevel logLevel)
1637
{
17-
foreach (var logger in _loggers)
38+
foreach (var lazyLogger in _lazyLoggers)
1839
{
19-
if (logger.IsEnabled(logLevel))
40+
if (lazyLogger.IsEnabled(logLevel))
2041
{
2142
return true;
2243
}
@@ -27,18 +48,18 @@ public bool IsEnabled(LogLevel logLevel)
2748

2849
public void Log(LogLevel logLevel, string message, Exception? exception)
2950
{
30-
foreach (var logger in _loggers)
51+
foreach (var lazyLogger in _lazyLoggers)
3152
{
32-
if (logger.IsEnabled(logLevel))
53+
if (lazyLogger.IsEnabled(logLevel))
3354
{
34-
logger.Log(logLevel, message, exception);
55+
lazyLogger.Instance.Log(logLevel, message, exception);
3556
}
3657
}
3758
}
3859

39-
internal void AddLogger(ILogger logger)
60+
internal void AddLogger(LazyLogger lazyLogger)
4061
{
41-
ImmutableInterlocked.Update(ref _loggers, (set, l) => set.Add(l), logger);
62+
ImmutableInterlocked.Update(ref _lazyLoggers, (set, l) => set.Add(l), lazyLogger);
4263
}
4364
}
4465
}

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Logging/AbstractLoggerFactory.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@ namespace Microsoft.CodeAnalysis.Razor.Logging;
1313

1414
internal abstract partial class AbstractLoggerFactory : ILoggerFactory
1515
{
16+
private ImmutableArray<Lazy<ILoggerProvider, LoggerProviderMetadata>> _providers;
1617
private ImmutableDictionary<string, AggregateLogger> _loggers;
17-
private ImmutableArray<ILoggerProvider> _providers;
1818

1919
protected AbstractLoggerFactory(ImmutableArray<ILoggerProvider> providers)
20+
: this(providers.SelectAsArray(p => new Lazy<ILoggerProvider, LoggerProviderMetadata>(() => p, LoggerProviderMetadata.Empty)))
21+
{
22+
}
23+
24+
protected AbstractLoggerFactory(ImmutableArray<Lazy<ILoggerProvider, LoggerProviderMetadata>> providers)
2025
{
2126
_providers = providers;
2227
_loggers = ImmutableDictionary.Create<string, AggregateLogger>(StringComparer.OrdinalIgnoreCase);
@@ -29,24 +34,26 @@ public ILogger GetOrCreateLogger(string categoryName)
2934
return logger;
3035
}
3136

32-
using var loggers = new PooledArrayBuilder<ILogger>(_providers.Length);
37+
using var lazyLoggers = new PooledArrayBuilder<LazyLogger>(_providers.Length);
3338

3439
foreach (var provider in _providers)
3540
{
36-
loggers.Add(provider.CreateLogger(categoryName));
41+
lazyLoggers.Add(new(provider, categoryName));
3742
}
3843

39-
var result = new AggregateLogger(loggers.DrainToImmutable());
44+
var result = new AggregateLogger(lazyLoggers.DrainToImmutable());
4045
return ImmutableInterlocked.AddOrUpdate(ref _loggers, categoryName, result, (k, v) => v);
4146
}
4247

4348
public void AddLoggerProvider(ILoggerProvider provider)
4449
{
45-
if (ImmutableInterlocked.Update(ref _providers, (set, p) => set.Add(p), provider))
50+
var lazyProvider = new Lazy<ILoggerProvider, LoggerProviderMetadata>(() => provider, LoggerProviderMetadata.Empty);
51+
52+
if (ImmutableInterlocked.Update(ref _providers, (set, p) => set.Add(p), lazyProvider))
4653
{
47-
foreach (var (category, logger) in _loggers)
54+
foreach (var (categoryName, logger) in _loggers)
4855
{
49-
logger.AddLogger(provider.CreateLogger(category));
56+
logger.AddLogger(new(lazyProvider, categoryName));
5057
}
5158
}
5259
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
6+
namespace Microsoft.CodeAnalysis.Razor.Logging;
7+
8+
internal sealed class LoggerProviderMetadata
9+
{
10+
public static LoggerProviderMetadata Empty { get; } = new();
11+
12+
public LogLevel? MinimumLogLevel { get; }
13+
14+
private LoggerProviderMetadata()
15+
{
16+
}
17+
18+
public LoggerProviderMetadata(IDictionary<string, object> data)
19+
: this()
20+
{
21+
MinimumLogLevel = data.TryGetValue(nameof(MinimumLogLevel), out var minimumLogLevel)
22+
? (LogLevel?)minimumLogLevel
23+
: null;
24+
}
25+
}

src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLogger.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,10 @@
99

1010
namespace Microsoft.VisualStudio.Razor.LanguageClient.Logging;
1111

12-
internal sealed class RazorLogHubLogger : ILogger
12+
internal sealed class RazorLogHubLogger(string categoryName, RazorLogHubTraceProvider traceProvider) : ILogger
1313
{
14-
private string _categoryName;
15-
private RazorLogHubTraceProvider _traceProvider;
16-
17-
public RazorLogHubLogger(string categoryName, RazorLogHubTraceProvider traceProvider)
18-
{
19-
_categoryName = categoryName;
20-
_traceProvider = traceProvider;
21-
}
14+
private readonly string _categoryName = categoryName;
15+
private readonly RazorLogHubTraceProvider _traceProvider = traceProvider;
2216

2317
public bool IsEnabled(LogLevel logLevel)
2418
{

src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/Logging/RazorLogHubLoggerProvider.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,11 @@
77

88
namespace Microsoft.VisualStudio.Razor.LanguageClient.Logging;
99

10-
[Export(typeof(ILoggerProvider))]
11-
internal sealed class RazorLogHubLoggerProvider : ILoggerProvider
10+
[ExportLoggerProvider]
11+
[method: ImportingConstructor]
12+
internal sealed class RazorLogHubLoggerProvider(RazorLogHubTraceProvider traceProvider) : ILoggerProvider
1213
{
13-
private readonly RazorLogHubTraceProvider _traceProvider;
14-
15-
[ImportingConstructor]
16-
public RazorLogHubLoggerProvider(RazorLogHubTraceProvider traceProvider)
17-
{
18-
_traceProvider = traceProvider;
19-
}
14+
private readonly RazorLogHubTraceProvider _traceProvider = traceProvider;
2015

2116
public ILogger CreateLogger(string categoryName)
2217
{

src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/ActivityLogLoggerProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Logging;
1010
/// <summary>
1111
/// An <see cref="ILoggerProvider"/> that logs any warnings or errors to the Visual Studio Activity Log.
1212
/// </summary>
13-
[Export(typeof(ILoggerProvider))]
13+
[ExportLoggerProvider(minimumLogLevel: LogLevel.Warning)]
1414
[method: ImportingConstructor]
1515
internal sealed partial class ActivityLogLoggerProvider(RazorActivityLog activityLog) : ILoggerProvider
1616
{
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.ComponentModel.Composition;
6+
using Microsoft.CodeAnalysis.Razor.Logging;
7+
8+
namespace Microsoft.VisualStudio.Razor.Logging;
9+
10+
[MetadataAttribute]
11+
[AttributeUsage(AttributeTargets.Class)]
12+
internal sealed class ExportLoggerProviderAttribute : ExportAttribute
13+
{
14+
public LogLevel? MinimumLogLevel { get; }
15+
16+
public ExportLoggerProviderAttribute()
17+
: base(typeof(ILoggerProvider))
18+
{
19+
MinimumLogLevel = null;
20+
}
21+
22+
public ExportLoggerProviderAttribute(LogLevel minimumLogLevel)
23+
: base(typeof(ILoggerProvider))
24+
{
25+
MinimumLogLevel = minimumLogLevel;
26+
}
27+
}

src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/Logging/MemoryLoggerProvider.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT license. See License.txt in the project root for license information.
33

4-
using System.ComponentModel.Composition;
54
using Microsoft.CodeAnalysis.Razor.Logging;
65

76
namespace Microsoft.VisualStudio.Razor.Logging;
87

9-
[Export(typeof(ILoggerProvider))]
10-
[method: ImportingConstructor]
11-
internal partial class MemoryLoggerProvider() : ILoggerProvider
8+
[ExportLoggerProvider]
9+
internal partial class MemoryLoggerProvider : ILoggerProvider
1210
{
1311
// How many messages will the buffer contain
1412
private const int BufferSize = 5000;

0 commit comments

Comments
 (0)