Remove WebAssemblyLoggerFactory and refactor logging setup (#20432)
**Changes in this PR** - Replaces `WebAssemblyLoggerFactory` with `LoggerFactory` from logging extensions package - Moves WebAssemblyConsoleLogger and PrependMessageLogger to provider model Now that we are using the standard `LoggerFactory` support for config options like `SetMinimumLevel` and `AddProvider` is available. Compared to what is currently in the `blazor-wasm` branch, the changes in this PR add an additional 12 kb to the total compressed size. Addresses #19737
This commit is contained in:
parent
41a9588353
commit
e67e7a08ca
|
|
@ -56,7 +56,9 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions.dll",
|
"Microsoft.Extensions.FileProviders.Abstractions.dll",
|
||||||
"Microsoft.Extensions.FileProviders.Physical.dll",
|
"Microsoft.Extensions.FileProviders.Physical.dll",
|
||||||
"Microsoft.Extensions.FileSystemGlobbing.dll",
|
"Microsoft.Extensions.FileSystemGlobbing.dll",
|
||||||
|
"Microsoft.Extensions.Logging.dll",
|
||||||
"Microsoft.Extensions.Logging.Abstractions.dll",
|
"Microsoft.Extensions.Logging.Abstractions.dll",
|
||||||
|
"Microsoft.Extensions.Options.dll",
|
||||||
"Microsoft.Extensions.Primitives.dll",
|
"Microsoft.Extensions.Primitives.dll",
|
||||||
"Microsoft.JSInterop.dll",
|
"Microsoft.JSInterop.dll",
|
||||||
"Microsoft.JSInterop.WebAssembly.dll",
|
"Microsoft.JSInterop.WebAssembly.dll",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
|
{
|
||||||
|
internal class LoggingBuilder : ILoggingBuilder
|
||||||
|
{
|
||||||
|
public LoggingBuilder(IServiceCollection services)
|
||||||
|
{
|
||||||
|
Services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IServiceCollection Services { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,6 +55,9 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
Configuration = new ConfigurationBuilder();
|
Configuration = new ConfigurationBuilder();
|
||||||
RootComponents = new RootComponentMappingCollection();
|
RootComponents = new RootComponentMappingCollection();
|
||||||
Services = new ServiceCollection();
|
Services = new ServiceCollection();
|
||||||
|
Logging = new LoggingBuilder(Services);
|
||||||
|
|
||||||
|
Logging.SetMinimumLevel(LogLevel.Warning);
|
||||||
|
|
||||||
// Retrieve required attributes from JSRuntimeInvoker
|
// Retrieve required attributes from JSRuntimeInvoker
|
||||||
InitializeNavigationManager(jsRuntimeInvoker);
|
InitializeNavigationManager(jsRuntimeInvoker);
|
||||||
|
|
@ -128,6 +131,11 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IWebAssemblyHostEnvironment HostEnvironment { get; }
|
public IWebAssemblyHostEnvironment HostEnvironment { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the logging builder for configuring logging services.
|
||||||
|
/// </summary>
|
||||||
|
public ILoggingBuilder Logging { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a <see cref="IServiceProviderFactory{TBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />.
|
/// Registers a <see cref="IServiceProviderFactory{TBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -186,8 +194,9 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
Services.AddSingleton<IJSRuntime>(DefaultWebAssemblyJSRuntime.Instance);
|
Services.AddSingleton<IJSRuntime>(DefaultWebAssemblyJSRuntime.Instance);
|
||||||
Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
|
Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance);
|
||||||
Services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
Services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance);
|
||||||
Services.AddSingleton<ILoggerFactory, WebAssemblyLoggerFactory>();
|
Services.AddLogging(builder => {
|
||||||
Services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(WebAssemblyConsoleLogger<>)));
|
builder.AddProvider(new WebAssemblyConsoleLoggerProvider(DefaultWebAssemblyJSRuntime.Instance));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.Web" />
|
<Reference Include="Microsoft.AspNetCore.Components.Web" />
|
||||||
<Reference Include="Microsoft.Extensions.Configuration.Json" />
|
<Reference Include="Microsoft.Extensions.Configuration.Json" />
|
||||||
|
<Reference Include="Microsoft.Extensions.Logging" />
|
||||||
<Reference Include="Microsoft.JSInterop.WebAssembly" />
|
<Reference Include="Microsoft.JSInterop.WebAssembly" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
@ -43,7 +44,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Services
|
||||||
|
|
||||||
public bool IsEnabled(LogLevel logLevel)
|
public bool IsEnabled(LogLevel logLevel)
|
||||||
{
|
{
|
||||||
return logLevel >= LogLevel.Warning && logLevel != LogLevel.None;
|
return logLevel != LogLevel.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.WebAssembly.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A provider of <see cref="WebAssemblyConsoleLogger{T}"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
internal class WebAssemblyConsoleLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, WebAssemblyConsoleLogger<object>> _loggers;
|
||||||
|
private readonly IJSInProcessRuntime _jsRuntime;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an instance of <see cref="WebAssemblyConsoleLoggerProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The options to create <see cref="WebAssemblyConsoleLogger"/> instances with.</param>
|
||||||
|
public WebAssemblyConsoleLoggerProvider(IJSInProcessRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
_loggers = new ConcurrentDictionary<string, WebAssemblyConsoleLogger<object>>();
|
||||||
|
_jsRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ILogger CreateLogger(string name)
|
||||||
|
{
|
||||||
|
return _loggers.GetOrAdd(name, loggerName => new WebAssemblyConsoleLogger<object>(name, _jsRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.WebAssembly.Services
|
|
||||||
{
|
|
||||||
internal class WebAssemblyLoggerFactory : ILoggerFactory
|
|
||||||
{
|
|
||||||
private readonly IJSInProcessRuntime _jsRuntime;
|
|
||||||
|
|
||||||
public WebAssemblyLoggerFactory(IServiceProvider services)
|
|
||||||
{
|
|
||||||
_jsRuntime = (IJSInProcessRuntime)services.GetRequiredService<IJSRuntime>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We might implement this in the future, but it's not required currently
|
|
||||||
public void AddProvider(ILoggerProvider provider)
|
|
||||||
=> throw new NotSupportedException();
|
|
||||||
|
|
||||||
public ILogger CreateLogger(string categoryName)
|
|
||||||
=> new WebAssemblyConsoleLogger<object>(categoryName, _jsRuntime);
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -230,12 +230,28 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
// Arrange & Act
|
// Arrange & Act
|
||||||
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
|
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(DefaultServiceTypes.Count, builder.Services.Count);
|
|
||||||
foreach (var type in DefaultServiceTypes)
|
foreach (var type in DefaultServiceTypes)
|
||||||
{
|
{
|
||||||
Assert.Single(builder.Services, d => d.ServiceType == type);
|
Assert.Single(builder.Services, d => d.ServiceType == type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Builder_SupportsConfiguringLogging()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker());
|
||||||
|
var provider = new Mock<ILoggerProvider>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.Logging.AddProvider(provider.Object);
|
||||||
|
var host = builder.Build();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var loggerProvider = host.Services.GetRequiredService<ILoggerProvider>();
|
||||||
|
Assert.NotNull(loggerProvider);
|
||||||
|
Assert.Equal<ILoggerProvider>(provider.Object, loggerProvider);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,10 +85,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
Browser.FindElement(By.Id("log-none")).Click();
|
Browser.FindElement(By.Id("log-none")).Click();
|
||||||
Browser.FindElement(By.Id("log-trace")).Click();
|
Browser.FindElement(By.Id("log-trace")).Click();
|
||||||
Browser.FindElement(By.Id("log-debug")).Click();
|
Browser.FindElement(By.Id("log-debug")).Click();
|
||||||
Browser.FindElement(By.Id("log-information")).Click();
|
|
||||||
AssertLastLogMessage(LogLevel.Info, "Test log message");
|
AssertLastLogMessage(LogLevel.Info, "Test log message");
|
||||||
|
|
||||||
// These severity levels are displayed
|
// These severity levels are displayed
|
||||||
|
Browser.FindElement(By.Id("log-information")).Click();
|
||||||
|
AssertLastLogMessage(LogLevel.Info, "[Custom logger] This is a Information message with count=4");
|
||||||
Browser.FindElement(By.Id("log-warning")).Click();
|
Browser.FindElement(By.Id("log-warning")).Click();
|
||||||
AssertLastLogMessage(LogLevel.Warning, "[Custom logger] This is a Warning message with count=5");
|
AssertLastLogMessage(LogLevel.Warning, "[Custom logger] This is a Warning message with count=5");
|
||||||
Browser.FindElement(By.Id("log-error")).Click();
|
Browser.FindElement(By.Id("log-error")).Click();
|
||||||
|
|
|
||||||
|
|
@ -3,33 +3,42 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Services;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace BasicTestApp
|
namespace BasicTestApp
|
||||||
{
|
{
|
||||||
// The goal for this class is to make it possible for E2E tests to observe that a custom
|
internal class PrependMessageLoggerProvider : ILoggerProvider
|
||||||
// logger factory can be plugged in and gets used when logging unhandled exceptions.
|
|
||||||
// However, it's valuable to pass through all calls to the default implementation too
|
|
||||||
// so that if any defect in the underlying implementation would break tests, we still see it.
|
|
||||||
|
|
||||||
public class PrependMessageLoggerFactory : ILoggerFactory
|
|
||||||
{
|
{
|
||||||
private readonly string _message;
|
ILogger _logger;
|
||||||
private readonly ILoggerFactory _underlyingFactory;
|
string _message;
|
||||||
|
ILogger _defaultLogger;
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
public PrependMessageLoggerFactory(string message, ILoggerFactory underlyingFactory)
|
public PrependMessageLoggerProvider(string message, IJSRuntime runtime)
|
||||||
{
|
{
|
||||||
_message = message;
|
_message = message;
|
||||||
_underlyingFactory = underlyingFactory;
|
_defaultLogger = new WebAssemblyConsoleLogger<object>(runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddProvider(ILoggerProvider provider)
|
|
||||||
=> _underlyingFactory.AddProvider(provider);
|
|
||||||
|
|
||||||
public ILogger CreateLogger(string categoryName)
|
public ILogger CreateLogger(string categoryName)
|
||||||
=> new PrependMessageLogger(_message, _underlyingFactory.CreateLogger(categoryName));
|
{
|
||||||
|
if (_logger == null)
|
||||||
|
{
|
||||||
|
_logger = new PrependMessageLogger(_message, _defaultLogger);
|
||||||
|
}
|
||||||
|
return _logger;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
=> _underlyingFactory.Dispose();
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
_logger = null;
|
||||||
|
}
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
private class PrependMessageLogger : ILogger
|
private class PrependMessageLogger : ILogger
|
||||||
{
|
{
|
||||||
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Components.WebAssembly.Http;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Services;
|
using Microsoft.AspNetCore.Components.WebAssembly.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace BasicTestApp
|
namespace BasicTestApp
|
||||||
{
|
{
|
||||||
|
|
@ -44,15 +45,8 @@ namespace BasicTestApp
|
||||||
policy.RequireAssertion(ctx => ctx.User.Identity.Name?.StartsWith("B") ?? false));
|
policy.RequireAssertion(ctx => ctx.User.Identity.Name?.StartsWith("B") ?? false));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Replace the default logger with a custom one that wraps it
|
builder.Logging.Services.AddSingleton<ILoggerProvider, PrependMessageLoggerProvider>(s => new PrependMessageLoggerProvider("Custom logger", s.GetService<IJSRuntime>()));
|
||||||
var originalLoggerDescriptor = builder.Services.Single(d => d.ServiceType == typeof(ILoggerFactory));
|
builder.Logging.SetMinimumLevel(LogLevel.Information);
|
||||||
builder.Services.AddSingleton<ILoggerFactory>(services =>
|
|
||||||
{
|
|
||||||
var originalLogger = (ILoggerFactory)Activator.CreateInstance(
|
|
||||||
originalLoggerDescriptor.ImplementationType,
|
|
||||||
new object[] { services });
|
|
||||||
return new PrependMessageLoggerFactory("Custom logger", originalLogger);
|
|
||||||
});
|
|
||||||
|
|
||||||
var host = builder.Build();
|
var host = builder.Build();
|
||||||
ConfigureCulture(host);
|
ConfigureCulture(host);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue