diff --git a/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs index 95e98a584af6..1db978646def 100644 --- a/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs +++ b/src/BuiltInTools/dotnet-watch/CommandLine/CommandLineOptions.cs @@ -170,15 +170,20 @@ internal sealed class CommandLineOptions var targetFrameworkOption = (Option?)buildOptions.SingleOrDefault(option => option.Name == "--framework"); + var logLevel = parseResult.GetValue(verboseOption) + ? LogLevel.Debug + : parseResult.GetValue(quietOption) + ? LogLevel.Warning + : LogLevel.Information; + return new() { List = parseResult.GetValue(listOption), GlobalOptions = new() { - Quiet = parseResult.GetValue(quietOption), + LogLevel = logLevel, NoHotReload = parseResult.GetValue(noHotReloadOption), NonInteractive = parseResult.GetValue(NonInteractiveOption), - Verbose = parseResult.GetValue(verboseOption), BinaryLogPath = ParseBinaryLogFilePath(binLogPath), }, diff --git a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs index d0e84844de86..8ab19e3ca7d7 100644 --- a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs +++ b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Watch { @@ -12,16 +13,21 @@ internal enum TestFlags RunningAsTest = 1 << 0, MockBrowser = 1 << 1, + /// + /// Elevates the logging level + /// + TraceLogging = 1 << 2, + /// /// Instead of using to watch for Ctrl+C, Ctlr+R, and other keys, read from standard input. /// This allows tests to trigger key based events. /// - ReadKeyFromStdin = 1 << 2, + ReadKeyFromStdin = 1 << 3, /// /// Redirects the output of the launched browser process to watch output. /// - RedirectBrowserOutput = 1 << 3, + RedirectBrowserOutput = 1 << 4, } internal sealed record EnvironmentOptions( @@ -35,6 +41,7 @@ internal sealed record EnvironmentOptions( bool SuppressBrowserRefresh = false, bool SuppressEmojis = false, bool RestartOnRudeEdit = false, + LogLevel? CliLogLevel = null, string? AutoReloadWebSocketHostName = null, int? AutoReloadWebSocketPort = null, string? BrowserPath = null, @@ -53,6 +60,7 @@ internal sealed record EnvironmentOptions( SuppressBrowserRefresh: EnvironmentVariables.SuppressBrowserRefresh, SuppressEmojis: EnvironmentVariables.SuppressEmojis, RestartOnRudeEdit: EnvironmentVariables.RestartOnRudeEdit, + CliLogLevel: EnvironmentVariables.CliLogLevel, AutoReloadWebSocketHostName: EnvironmentVariables.AutoReloadWSHostName, AutoReloadWebSocketPort: EnvironmentVariables.AutoReloadWSPort, BrowserPath: EnvironmentVariables.BrowserPath, diff --git a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs index 28763d7f3223..41ac7d88edea 100644 --- a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs +++ b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Extensions.Logging; + namespace Microsoft.DotNet.Watch; internal static class EnvironmentVariables @@ -20,7 +22,19 @@ public static class Names public const string SuppressBrowserRefresh = "DOTNET_WATCH_SUPPRESS_BROWSER_REFRESH"; } - public static bool VerboseCliOutput => ReadBool("DOTNET_CLI_CONTEXT_VERBOSE"); + public static LogLevel? CliLogLevel + { + get + { + var value = Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE"); + return string.Equals(value, "trace", StringComparison.OrdinalIgnoreCase) + ? LogLevel.Trace + : ParseBool(value) + ? LogLevel.Debug + : null; + } + } + public static bool IsPollingEnabled => ReadBool("DOTNET_USE_POLLING_FILE_WATCHER"); public static bool SuppressEmojis => ReadBool("DOTNET_WATCH_SUPPRESS_EMOJIS"); public static bool RestartOnRudeEdit => ReadBool("DOTNET_WATCH_RESTART_ON_RUDE_EDIT"); @@ -46,11 +60,14 @@ public static class Names public static string? BrowserPath => Environment.GetEnvironmentVariable("DOTNET_WATCH_BROWSER_PATH"); private static bool ReadBool(string variableName) - => Environment.GetEnvironmentVariable(variableName) is var value && (value == "1" || bool.TryParse(value, out var boolValue) && boolValue); + => ParseBool(Environment.GetEnvironmentVariable(variableName)); private static TimeSpan? ReadTimeSpan(string variableName) => Environment.GetEnvironmentVariable(variableName) is var value && long.TryParse(value, out var intValue) && intValue >= 0 ? TimeSpan.FromMilliseconds(intValue) : null; private static int? ReadInt(string variableName) => Environment.GetEnvironmentVariable(variableName) is var value && int.TryParse(value, out var intValue) ? intValue : null; + + private static bool ParseBool(string? value) + => value == "1" || bool.TryParse(value, out var boolValue) && boolValue; } diff --git a/src/BuiltInTools/dotnet-watch/CommandLine/GlobalOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/GlobalOptions.cs index 0f24692c8522..2dc002f6070e 100644 --- a/src/BuiltInTools/dotnet-watch/CommandLine/GlobalOptions.cs +++ b/src/BuiltInTools/dotnet-watch/CommandLine/GlobalOptions.cs @@ -1,12 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Extensions.Logging; + namespace Microsoft.DotNet.Watch; internal sealed class GlobalOptions { - public bool Quiet { get; init; } - public bool Verbose { get; init; } + public LogLevel LogLevel { get; init; } public bool NoHotReload { get; init; } public bool NonInteractive { get; init; } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/AppModels/WebApplicationAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/WebApplicationAppModel.cs index 4ca515a862a6..6b9f2566bae7 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/AppModels/WebApplicationAppModel.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/WebApplicationAppModel.cs @@ -71,13 +71,13 @@ public bool IsServerSupported(ProjectGraphNode projectNode, ILogger logger) { if (context.EnvironmentOptions.SuppressBrowserRefresh) { - logger.Log(MessageDescriptor.SkippingConfiguringBrowserRefresh_SuppressedViaEnvironmentVariable.WithSeverityWhen(MessageSeverity.Error, RequiresBrowserRefresh), EnvironmentVariables.Names.SuppressBrowserRefresh); + logger.Log(MessageDescriptor.SkippingConfiguringBrowserRefresh_SuppressedViaEnvironmentVariable.WithLevelWhen(LogLevel.Error, RequiresBrowserRefresh), EnvironmentVariables.Names.SuppressBrowserRefresh); return false; } if (!projectNode.IsNetCoreApp(minVersion: s_minimumSupportedVersion)) { - logger.Log(MessageDescriptor.SkippingConfiguringBrowserRefresh_TargetFrameworkNotSupported.WithSeverityWhen(MessageSeverity.Error, RequiresBrowserRefresh)); + logger.Log(MessageDescriptor.SkippingConfiguringBrowserRefresh_TargetFrameworkNotSupported.WithLevelWhen(LogLevel.Error, RequiresBrowserRefresh)); return false; } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs b/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs index 55770d524293..3654be0d8752 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/CompilationHandler.cs @@ -521,7 +521,7 @@ void ReportDiagnostic(Diagnostic diagnostic, MessageDescriptor descriptor, strin { errorsToDisplayInApp.Add(MessageDescriptor.RestartingApplicationToApplyChanges.GetMessage()); } - else if (descriptor.Severity != MessageSeverity.None) + else if (descriptor.Level != LogLevel.None) { errorsToDisplayInApp.Add(descriptor.GetMessage(args)); } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs index 565bc7f9062b..287454da9faa 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/HotReloadDotNetWatcher.cs @@ -30,7 +30,7 @@ public HotReloadDotNetWatcher(DotNetWatchContext context, IConsole console, IRun _runtimeProcessLauncherFactory = runtimeProcessLauncherFactory; if (!context.Options.NonInteractive) { - var consoleInput = new ConsoleInputReader(_console, context.Options.Quiet, context.EnvironmentOptions.SuppressEmojis); + var consoleInput = new ConsoleInputReader(_console, context.Options.LogLevel, context.EnvironmentOptions.SuppressEmojis); var noPrompt = context.EnvironmentOptions.RestartOnRudeEdit; if (noPrompt) diff --git a/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs b/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs index 65ab6089466c..5d5b67ac10c2 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs @@ -230,16 +230,13 @@ private Task UpdateSolutionAsync(Solution newSolution, string operationDisplayNa private async Task ReportSolutionFilesAsync(Solution solution, int updateId, string operationDisplayName, CancellationToken cancellationToken) { -#if DEBUG - _logger.LogDebug("Solution: {Path}", solution.FilePath); + _logger.LogDebug("Solution after {Operation}: v{Version}", operationDisplayName, updateId); - if (!_logger.IsEnabled(LogLevel.Debug)) + if (!_logger.IsEnabled(LogLevel.Trace)) { return; } - _logger.LogDebug("Solution after {Operation}: v{Version}", operationDisplayName, updateId); - foreach (var project in solution.Projects) { _logger.LogDebug(" Project: {Path}", project.FilePath); @@ -265,8 +262,5 @@ async ValueTask InspectDocumentAsync(TextDocument document, string kind) var text = await document.GetTextAsync(cancellationToken); _logger.LogDebug(" {Kind}: {FilePath} [{Checksum}]", kind, document.FilePath, Convert.ToBase64String(text.GetChecksum().ToArray())); } -#else - await Task.CompletedTask; -#endif } } diff --git a/src/BuiltInTools/dotnet-watch/Program.cs b/src/BuiltInTools/dotnet-watch/Program.cs index f67a360a3a32..251a49f71bf2 100644 --- a/src/BuiltInTools/dotnet-watch/Program.cs +++ b/src/BuiltInTools/dotnet-watch/Program.cs @@ -46,7 +46,6 @@ public static async Task Main(string[] args) args, new PhysicalConsole(environmentOptions.TestFlags), environmentOptions, - EnvironmentVariables.VerboseCliOutput, out var exitCode); if (program == null) @@ -64,9 +63,9 @@ public static async Task Main(string[] args) } } - private static Program? TryCreate(IReadOnlyList args, IConsole console, EnvironmentOptions environmentOptions, bool verbose, out int errorCode) + private static Program? TryCreate(IReadOnlyList args, IConsole console, EnvironmentOptions environmentOptions, out int errorCode) { - var parsingLoggerFactory = new LoggerFactory(new ConsoleReporter(console, verbose, quiet: false, environmentOptions.SuppressEmojis)); + var parsingLoggerFactory = new LoggerFactory(new ConsoleReporter(console, environmentOptions.SuppressEmojis), environmentOptions.CliLogLevel ?? LogLevel.Information); var options = CommandLineOptions.Parse(args, parsingLoggerFactory.CreateLogger(LogComponentName), console.Out, out errorCode); if (options == null) { @@ -74,8 +73,9 @@ public static async Task Main(string[] args) return null; } - var reporter = new ConsoleReporter(console, verbose || options.GlobalOptions.Verbose, options.GlobalOptions.Quiet, environmentOptions.SuppressEmojis); - var loggerFactory = new LoggerFactory(reporter); + var logLevel = environmentOptions.CliLogLevel ?? options.GlobalOptions.LogLevel; + var reporter = new ConsoleReporter(console, environmentOptions.SuppressEmojis); + var loggerFactory = new LoggerFactory(reporter, logLevel); return TryCreate(options, console, environmentOptions, loggerFactory, reporter, out errorCode); } diff --git a/src/BuiltInTools/dotnet-watch/Properties/launchSettings.json b/src/BuiltInTools/dotnet-watch/Properties/launchSettings.json index 9e28729eb807..e4f4da2deaf1 100644 --- a/src/BuiltInTools/dotnet-watch/Properties/launchSettings.json +++ b/src/BuiltInTools/dotnet-watch/Properties/launchSettings.json @@ -2,8 +2,8 @@ "profiles": { "dotnet-watch": { "commandName": "Project", - "commandLineArgs": "--verbose -bl", - "workingDirectory": "C:\\bugs\\9756\\aspire-watch-start-issue\\Aspire.AppHost", + "commandLineArgs": "", + "workingDirectory": "C:\\temp\\Razor", "environmentVariables": { "DOTNET_WATCH_DEBUG_SDK_DIRECTORY": "$(RepoRoot)artifacts\\bin\\redist\\$(Configuration)\\dotnet\\sdk\\$(Version)", "DCP_IDE_REQUEST_TIMEOUT_SECONDS": "100000", diff --git a/src/BuiltInTools/dotnet-watch/UI/ConsoleInputReader.cs b/src/BuiltInTools/dotnet-watch/UI/ConsoleInputReader.cs index 4c9e930b245a..233d320765ba 100644 --- a/src/BuiltInTools/dotnet-watch/UI/ConsoleInputReader.cs +++ b/src/BuiltInTools/dotnet-watch/UI/ConsoleInputReader.cs @@ -1,15 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Extensions.Logging; + namespace Microsoft.DotNet.Watch { - internal sealed class ConsoleInputReader(IConsole console, bool quiet, bool suppressEmojis) + internal sealed class ConsoleInputReader(IConsole console, LogLevel logLevel, bool suppressEmojis) { private readonly object _writeLock = new(); public async Task GetKeyAsync(string prompt, Func validateInput, CancellationToken cancellationToken) { - if (quiet) + if (logLevel > LogLevel.Information) { return ConsoleKey.Escape; } diff --git a/src/BuiltInTools/dotnet-watch/UI/ConsoleReporter.cs b/src/BuiltInTools/dotnet-watch/UI/ConsoleReporter.cs index 198e518590bb..60b156142b08 100644 --- a/src/BuiltInTools/dotnet-watch/UI/ConsoleReporter.cs +++ b/src/BuiltInTools/dotnet-watch/UI/ConsoleReporter.cs @@ -9,10 +9,8 @@ namespace Microsoft.DotNet.Watch /// This API supports infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - internal sealed class ConsoleReporter(IConsole console, bool verbose, bool quiet, bool suppressEmojis) : IReporter, IProcessOutputReporter + internal sealed class ConsoleReporter(IConsole console, bool suppressEmojis) : IReporter, IProcessOutputReporter { - public bool IsVerbose { get; } = verbose; - public bool IsQuiet { get; } = quiet; public bool SuppressEmojis { get; } = suppressEmojis; private readonly Lock _writeLock = new(); @@ -50,33 +48,18 @@ private void WriteLine(TextWriter writer, string message, ConsoleColor? color, E } } - public void Report(EventId id, Emoji emoji, MessageSeverity severity, string message) + public void Report(EventId id, Emoji emoji, LogLevel level, string message) { - switch (severity) + var color = level switch { - case MessageSeverity.Error: - // Use stdout for error messages to preserve ordering with respect to other output. - WriteLine(console.Error, message, ConsoleColor.Red, emoji); - break; + LogLevel.Critical or LogLevel.Error => ConsoleColor.Red, + LogLevel.Warning => ConsoleColor.Yellow, + LogLevel.Information => (ConsoleColor?)null, + _ => ConsoleColor.DarkGray, + }; - case MessageSeverity.Warning: - WriteLine(console.Error, message, ConsoleColor.Yellow, emoji); - break; - - case MessageSeverity.Output: - if (!IsQuiet) - { - WriteLine(console.Error, message, color: null, emoji); - } - break; - - case MessageSeverity.Verbose: - if (IsVerbose) - { - WriteLine(console.Error, message, ConsoleColor.DarkGray, emoji); - } - break; - } + // Use stdout for error messages to preserve ordering with respect to other output. + WriteLine(console.Error, message, color, emoji); } } } diff --git a/src/BuiltInTools/dotnet-watch/UI/IReporter.cs b/src/BuiltInTools/dotnet-watch/UI/IReporter.cs index 4224aefb4855..c8251c85bd68 100644 --- a/src/BuiltInTools/dotnet-watch/UI/IReporter.cs +++ b/src/BuiltInTools/dotnet-watch/UI/IReporter.cs @@ -2,21 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; -using System.Diagnostics; using Microsoft.DotNet.HotReload; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Watch { - internal enum MessageSeverity - { - None, - Verbose, - Output, - Warning, - Error, - } - internal enum Emoji { Default = 0, @@ -66,61 +56,43 @@ public static string GetLogMessagePrefix(this Emoji emoji) public static void Log(this ILogger logger, MessageDescriptor descriptor, params object?[] args) { logger.Log( - descriptor.Severity.ToLogLevel(), + descriptor.Level, descriptor.Id, state: (descriptor, args), exception: null, formatter: static (state, _) => state.descriptor.GetMessage(state.args)); } - - public static LogLevel ToLogLevel(this MessageSeverity severity) - => severity switch - { - MessageSeverity.None => LogLevel.None, - MessageSeverity.Verbose => LogLevel.Debug, - MessageSeverity.Output => LogLevel.Information, - MessageSeverity.Warning => LogLevel.Warning, - MessageSeverity.Error => LogLevel.Error, - _ => throw new InvalidOperationException() - }; - - public static MessageSeverity ToSeverity(this LogLevel level) - => level switch - { - LogLevel.Debug => MessageSeverity.Verbose, - LogLevel.Information => MessageSeverity.Output, - LogLevel.Warning => MessageSeverity.Warning, - LogLevel.Error => MessageSeverity.Error, - LogLevel.None => MessageSeverity.None, - _ => throw new InvalidOperationException() - }; } - internal sealed class LoggerFactory(IReporter reporter) : ILoggerFactory + internal sealed class LoggerFactory(IReporter reporter, LogLevel level) : ILoggerFactory { - private sealed class Logger(IReporter reporter, string categoryName) : ILogger + private sealed class Logger(IReporter reporter, LogLevel level, string categoryName) : ILogger { public bool IsEnabled(LogLevel logLevel) - => reporter.IsVerbose || logLevel > LogLevel.Debug; + => logLevel >= level; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { + if (!IsEnabled(logLevel)) + { + return; + } + var (name, display) = LoggingUtilities.ParseCategoryName(categoryName); var prefix = display != null ? $"[{display}] " : ""; - var severity = logLevel.ToSeverity(); var descriptor = eventId.Id != 0 ? MessageDescriptor.GetDescriptor(eventId) : default; - var emoji = severity switch + var emoji = logLevel switch { _ when descriptor.Emoji != Emoji.Default => descriptor.Emoji, - MessageSeverity.Error => Emoji.Error, - MessageSeverity.Warning => Emoji.Warning, + LogLevel.Error => Emoji.Error, + LogLevel.Warning => Emoji.Warning, _ when MessageDescriptor.ComponentEmojis.TryGetValue(name, out var componentEmoji) => componentEmoji, _ => Emoji.Watch }; - reporter.Report(eventId, emoji, severity, prefix + formatter(state, exception)); + reporter.Report(eventId, emoji, logLevel, prefix + formatter(state, exception)); } public IDisposable? BeginScope(TState state) where TState : notnull @@ -132,27 +104,27 @@ public void Dispose() } public ILogger CreateLogger(string categoryName) - => new Logger(reporter, categoryName); + => new Logger(reporter, level, categoryName); public void AddProvider(ILoggerProvider provider) => throw new NotImplementedException(); } - internal readonly record struct MessageDescriptor(string Format, Emoji Emoji, MessageSeverity Severity, EventId Id) + internal readonly record struct MessageDescriptor(string Format, Emoji Emoji, LogLevel Level, EventId Id) { private static int s_id; private static ImmutableDictionary s_descriptors = []; - private static MessageDescriptor Create(string format, Emoji emoji, MessageSeverity severity) + private static MessageDescriptor Create(string format, Emoji emoji, LogLevel level) // reserve event id 0 for ad-hoc messages - => Create(new EventId(++s_id), format, emoji, severity); + => Create(new EventId(++s_id), format, emoji, level); private static MessageDescriptor Create(LogEvent logEvent, Emoji emoji) - => Create(logEvent.Id, logEvent.Message, emoji, logEvent.Level.ToSeverity()); + => Create(logEvent.Id, logEvent.Message, emoji, logEvent.Level); - private static MessageDescriptor Create(EventId id, string format, Emoji emoji, MessageSeverity severity) + private static MessageDescriptor Create(EventId id, string format, Emoji emoji, LogLevel level) { - var descriptor = new MessageDescriptor(format, emoji, severity, id.Id); + var descriptor = new MessageDescriptor(format, emoji, level, id.Id); s_descriptors = s_descriptors.Add(id, descriptor); return descriptor; } @@ -163,9 +135,18 @@ public static MessageDescriptor GetDescriptor(EventId id) public string GetMessage(params object?[] args) => Id.Id == 0 ? Format : string.Format(Format, args); - public MessageDescriptor WithSeverityWhen(MessageSeverity severity, bool condition) - => condition && Severity != severity - ? this with { Severity = severity, Emoji = severity switch { MessageSeverity.Error => Emoji.Error, MessageSeverity.Warning => Emoji.Warning, _ => Emoji } } + public MessageDescriptor WithLevelWhen(LogLevel level, bool condition) + => condition && Level != level + ? this with + { + Level = level, + Emoji = level switch + { + LogLevel.Error or LogLevel.Critical => Emoji.Error, + LogLevel.Warning => Emoji.Warning, + _ => Emoji + } + } : this; public static readonly ImmutableDictionary ComponentEmojis = ImmutableDictionary.Empty @@ -180,83 +161,82 @@ public MessageDescriptor WithSeverityWhen(MessageSeverity severity, bool conditi .Add(AspireServiceFactory.AspireLogComponentName, Emoji.Aspire); // predefined messages used for testing: - public static readonly MessageDescriptor HotReloadSessionStarting = Create("Hot reload session starting.", Emoji.HotReload, MessageSeverity.None); - public static readonly MessageDescriptor HotReloadSessionStarted = Create("Hot reload session started.", Emoji.HotReload, MessageSeverity.Verbose); - public static readonly MessageDescriptor ProjectsRebuilt = Create("Projects rebuilt ({0})", Emoji.HotReload, MessageSeverity.Verbose); - public static readonly MessageDescriptor ProjectsRestarted = Create("Projects restarted ({0})", Emoji.HotReload, MessageSeverity.Verbose); - public static readonly MessageDescriptor ProjectDependenciesDeployed = Create("Project dependencies deployed ({0})", Emoji.HotReload, MessageSeverity.Verbose); - public static readonly MessageDescriptor FixBuildError = Create("Fix the error to continue or press Ctrl+C to exit.", Emoji.Watch, MessageSeverity.Warning); - public static readonly MessageDescriptor WaitingForChanges = Create("Waiting for changes", Emoji.Watch, MessageSeverity.Output); - public static readonly MessageDescriptor LaunchedProcess = Create("Launched '{0}' with arguments '{1}': process id {2}", Emoji.Launch, MessageSeverity.Verbose); - public static readonly MessageDescriptor HotReloadChangeHandled = Create("Hot reload change handled in {0}ms.", Emoji.HotReload, MessageSeverity.Verbose); + public static readonly MessageDescriptor HotReloadSessionStarting = Create("Hot reload session starting.", Emoji.HotReload, LogLevel.None); + public static readonly MessageDescriptor HotReloadSessionStarted = Create("Hot reload session started.", Emoji.HotReload, LogLevel.Debug); + public static readonly MessageDescriptor ProjectsRebuilt = Create("Projects rebuilt ({0})", Emoji.HotReload, LogLevel.Debug); + public static readonly MessageDescriptor ProjectsRestarted = Create("Projects restarted ({0})", Emoji.HotReload, LogLevel.Debug); + public static readonly MessageDescriptor ProjectDependenciesDeployed = Create("Project dependencies deployed ({0})", Emoji.HotReload, LogLevel.Debug); + public static readonly MessageDescriptor FixBuildError = Create("Fix the error to continue or press Ctrl+C to exit.", Emoji.Watch, LogLevel.Warning); + public static readonly MessageDescriptor WaitingForChanges = Create("Waiting for changes", Emoji.Watch, LogLevel.Information); + public static readonly MessageDescriptor LaunchedProcess = Create("Launched '{0}' with arguments '{1}': process id {2}", Emoji.Launch, LogLevel.Debug); + public static readonly MessageDescriptor HotReloadChangeHandled = Create("Hot reload change handled in {0}ms.", Emoji.HotReload, LogLevel.Debug); public static readonly MessageDescriptor HotReloadSucceeded = Create(LogEvents.HotReloadSucceeded, Emoji.HotReload); public static readonly MessageDescriptor UpdatesApplied = Create(LogEvents.UpdatesApplied, Emoji.HotReload); public static readonly MessageDescriptor Capabilities = Create(LogEvents.Capabilities, Emoji.HotReload); - public static readonly MessageDescriptor WaitingForFileChangeBeforeRestarting = Create("Waiting for a file to change before restarting ...", Emoji.Wait, MessageSeverity.Warning); - public static readonly MessageDescriptor WatchingWithHotReload = Create("Watching with Hot Reload.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor RestartInProgress = Create("Restart in progress.", Emoji.Restart, MessageSeverity.Output); - public static readonly MessageDescriptor RestartRequested = Create("Restart requested.", Emoji.Restart, MessageSeverity.Output); - public static readonly MessageDescriptor ShutdownRequested = Create("Shutdown requested. Press Ctrl+C again to force exit.", Emoji.Stop, MessageSeverity.Output); - public static readonly MessageDescriptor ApplyUpdate_Error = Create("{0}{1}", Emoji.Error, MessageSeverity.Error); - public static readonly MessageDescriptor ApplyUpdate_Warning = Create("{0}{1}", Emoji.Warning, MessageSeverity.Warning); - public static readonly MessageDescriptor ApplyUpdate_Verbose = Create("{0}{1}", Emoji.Default, MessageSeverity.Verbose); - public static readonly MessageDescriptor ApplyUpdate_ChangingEntryPoint = Create("{0} Press \"Ctrl + R\" to restart.", Emoji.Warning, MessageSeverity.Warning); - public static readonly MessageDescriptor ApplyUpdate_FileContentDoesNotMatchBuiltSource = Create("{0} Expected if a source file is updated that is linked to project whose build is not up-to-date.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor ConfiguredToLaunchBrowser = Create("dotnet-watch is configured to launch a browser on ASP.NET Core application startup.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor ConfiguredToUseBrowserRefresh = Create("Using browser-refresh middleware", Emoji.Default, MessageSeverity.Verbose); - public static readonly MessageDescriptor SkippingConfiguringBrowserRefresh_SuppressedViaEnvironmentVariable = Create("Skipping configuring browser-refresh middleware since its refresh server suppressed via environment variable {0}.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor SkippingConfiguringBrowserRefresh_TargetFrameworkNotSupported = Create("Skipping configuring browser-refresh middleware since the target framework version is not supported. For more information see 'https://aka.ms/dotnet/watch/unsupported-tfm'.", Emoji.Watch, MessageSeverity.Warning); + public static readonly MessageDescriptor WaitingForFileChangeBeforeRestarting = Create("Waiting for a file to change before restarting ...", Emoji.Wait, LogLevel.Warning); + public static readonly MessageDescriptor WatchingWithHotReload = Create("Watching with Hot Reload.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor RestartInProgress = Create("Restart in progress.", Emoji.Restart, LogLevel.Information); + public static readonly MessageDescriptor RestartRequested = Create("Restart requested.", Emoji.Restart, LogLevel.Information); + public static readonly MessageDescriptor ShutdownRequested = Create("Shutdown requested. Press Ctrl+C again to force exit.", Emoji.Stop, LogLevel.Information); + public static readonly MessageDescriptor ApplyUpdate_Error = Create("{0}{1}", Emoji.Error, LogLevel.Error); + public static readonly MessageDescriptor ApplyUpdate_Warning = Create("{0}{1}", Emoji.Warning, LogLevel.Warning); + public static readonly MessageDescriptor ApplyUpdate_Verbose = Create("{0}{1}", Emoji.Default, LogLevel.Debug); + public static readonly MessageDescriptor ApplyUpdate_ChangingEntryPoint = Create("{0} Press \"Ctrl + R\" to restart.", Emoji.Warning, LogLevel.Warning); + public static readonly MessageDescriptor ApplyUpdate_FileContentDoesNotMatchBuiltSource = Create("{0} Expected if a source file is updated that is linked to project whose build is not up-to-date.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor ConfiguredToLaunchBrowser = Create("dotnet-watch is configured to launch a browser on ASP.NET Core application startup.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor ConfiguredToUseBrowserRefresh = Create("Using browser-refresh middleware", Emoji.Default, LogLevel.Debug); + public static readonly MessageDescriptor SkippingConfiguringBrowserRefresh_SuppressedViaEnvironmentVariable = Create("Skipping configuring browser-refresh middleware since its refresh server suppressed via environment variable {0}.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor SkippingConfiguringBrowserRefresh_TargetFrameworkNotSupported = Create("Skipping configuring browser-refresh middleware since the target framework version is not supported. For more information see 'https://aka.ms/dotnet/watch/unsupported-tfm'.", Emoji.Watch, LogLevel.Warning); public static readonly MessageDescriptor UpdatingDiagnostics = Create(LogEvents.UpdatingDiagnostics, Emoji.Default); public static readonly MessageDescriptor FailedToReceiveResponseFromConnectedBrowser = Create(LogEvents.FailedToReceiveResponseFromConnectedBrowser, Emoji.Default); public static readonly MessageDescriptor NoBrowserConnected = Create(LogEvents.NoBrowserConnected, Emoji.Default); - public static readonly MessageDescriptor LaunchingBrowser = Create("Launching browser: {0} {1}", Emoji.Default, MessageSeverity.Verbose); + public static readonly MessageDescriptor LaunchingBrowser = Create("Launching browser: {0} {1}", Emoji.Default, LogLevel.Debug); public static readonly MessageDescriptor RefreshingBrowser = Create(LogEvents.RefreshingBrowser, Emoji.Default); public static readonly MessageDescriptor ReloadingBrowser = Create(LogEvents.ReloadingBrowser, Emoji.Default); public static readonly MessageDescriptor RefreshServerRunningAt = Create(LogEvents.RefreshServerRunningAt, Emoji.Default); public static readonly MessageDescriptor ConnectedToRefreshServer = Create(LogEvents.ConnectedToRefreshServer, Emoji.Default); - public static readonly MessageDescriptor RestartingApplicationToApplyChanges = Create("Restarting application to apply changes ...", Emoji.Default, MessageSeverity.Output); - public static readonly MessageDescriptor RestartingApplication = Create("Restarting application ...", Emoji.Default, MessageSeverity.Output); - public static readonly MessageDescriptor IgnoringChangeInHiddenDirectory = Create("Ignoring change in hidden directory '{0}': {1} '{2}'", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor IgnoringChangeInOutputDirectory = Create("Ignoring change in output directory: {0} '{1}'", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor IgnoringChangeInExcludedFile = Create("Ignoring change in excluded file '{0}': {1}. Path matches {2} glob '{3}' set in '{4}'.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor FileAdditionTriggeredReEvaluation = Create("File addition triggered re-evaluation.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor ReEvaluationCompleted = Create("Re-evaluation completed.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor ProjectChangeTriggeredReEvaluation = Create("Project change triggered re-evaluation.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor NoCSharpChangesToApply = Create("No C# changes to apply.", Emoji.Watch, MessageSeverity.Output); - public static readonly MessageDescriptor Exited = Create("Exited", Emoji.Watch, MessageSeverity.Output); - public static readonly MessageDescriptor ExitedWithUnknownErrorCode = Create("Exited with unknown error code", Emoji.Error, MessageSeverity.Error); - public static readonly MessageDescriptor ExitedWithErrorCode = Create("Exited with error code {0}", Emoji.Error, MessageSeverity.Error); - public static readonly MessageDescriptor FailedToLaunchProcess = Create("Failed to launch '{0}' with arguments '{1}': {2}", Emoji.Error, MessageSeverity.Error); - public static readonly MessageDescriptor ApplicationFailed = Create("Application failed: {0}", Emoji.Error, MessageSeverity.Error); - public static readonly MessageDescriptor ProcessRunAndExited = Create("Process id {0} ran for {1}ms and exited with exit code {2}.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor WaitingForProcessToExitWithin = Create("Waiting for process {0} to exit within {1}s.", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor WaitingForProcessToExit = Create("Waiting for process {0} to exit ({1}).", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor FailedToKillProcess = Create("Failed to kill process {0}: {1}.", Emoji.Error, MessageSeverity.Error); - public static readonly MessageDescriptor TerminatingProcess = Create("Terminating process {0} ({1}).", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor FailedToSendSignalToProcess = Create("Failed to send {0} signal to process {1}: {2}", Emoji.Warning, MessageSeverity.Warning); - public static readonly MessageDescriptor ErrorReadingProcessOutput = Create("Error reading {0} of process {1}: {2}", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor HotReloadOfScopedCssSucceeded = Create("Hot reload of scoped css succeeded.", Emoji.HotReload, MessageSeverity.Output); - public static readonly MessageDescriptor HotReloadOfScopedCssPartiallySucceeded = Create("Hot reload of scoped css partially succeeded: {0} project(s) out of {1} were updated.", Emoji.HotReload, MessageSeverity.Output); - public static readonly MessageDescriptor HotReloadOfScopedCssFailed = Create("Hot reload of scoped css failed.", Emoji.Error, MessageSeverity.Error); - public static readonly MessageDescriptor HotReloadOfStaticAssetsSucceeded = Create("Hot reload of static assets succeeded.", Emoji.HotReload, MessageSeverity.Output); + public static readonly MessageDescriptor RestartingApplicationToApplyChanges = Create("Restarting application to apply changes ...", Emoji.Default, LogLevel.Information); + public static readonly MessageDescriptor RestartingApplication = Create("Restarting application ...", Emoji.Default, LogLevel.Information); + public static readonly MessageDescriptor IgnoringChangeInHiddenDirectory = Create("Ignoring change in hidden directory '{0}': {1} '{2}'", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor IgnoringChangeInOutputDirectory = Create("Ignoring change in output directory: {0} '{1}'", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor IgnoringChangeInExcludedFile = Create("Ignoring change in excluded file '{0}': {1}. Path matches {2} glob '{3}' set in '{4}'.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor FileAdditionTriggeredReEvaluation = Create("File addition triggered re-evaluation.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor ReEvaluationCompleted = Create("Re-evaluation completed.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor ProjectChangeTriggeredReEvaluation = Create("Project change triggered re-evaluation.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor NoCSharpChangesToApply = Create("No C# changes to apply.", Emoji.Watch, LogLevel.Information); + public static readonly MessageDescriptor Exited = Create("Exited", Emoji.Watch, LogLevel.Information); + public static readonly MessageDescriptor ExitedWithUnknownErrorCode = Create("Exited with unknown error code", Emoji.Error, LogLevel.Error); + public static readonly MessageDescriptor ExitedWithErrorCode = Create("Exited with error code {0}", Emoji.Error, LogLevel.Error); + public static readonly MessageDescriptor FailedToLaunchProcess = Create("Failed to launch '{0}' with arguments '{1}': {2}", Emoji.Error, LogLevel.Error); + public static readonly MessageDescriptor ApplicationFailed = Create("Application failed: {0}", Emoji.Error, LogLevel.Error); + public static readonly MessageDescriptor ProcessRunAndExited = Create("Process id {0} ran for {1}ms and exited with exit code {2}.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor WaitingForProcessToExitWithin = Create("Waiting for process {0} to exit within {1}s.", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor WaitingForProcessToExit = Create("Waiting for process {0} to exit ({1}).", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor FailedToKillProcess = Create("Failed to kill process {0}: {1}.", Emoji.Error, LogLevel.Error); + public static readonly MessageDescriptor TerminatingProcess = Create("Terminating process {0} ({1}).", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor FailedToSendSignalToProcess = Create("Failed to send {0} signal to process {1}: {2}", Emoji.Warning, LogLevel.Warning); + public static readonly MessageDescriptor ErrorReadingProcessOutput = Create("Error reading {0} of process {1}: {2}", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor HotReloadOfScopedCssSucceeded = Create("Hot reload of scoped css succeeded.", Emoji.HotReload, LogLevel.Information); + public static readonly MessageDescriptor HotReloadOfScopedCssPartiallySucceeded = Create("Hot reload of scoped css partially succeeded: {0} project(s) out of {1} were updated.", Emoji.HotReload, LogLevel.Information); + public static readonly MessageDescriptor HotReloadOfScopedCssFailed = Create("Hot reload of scoped css failed.", Emoji.Error, LogLevel.Error); + public static readonly MessageDescriptor HotReloadOfStaticAssetsSucceeded = Create("Hot reload of static assets succeeded.", Emoji.HotReload, LogLevel.Information); public static readonly MessageDescriptor SendingStaticAssetUpdateRequest = Create(LogEvents.SendingStaticAssetUpdateRequest, Emoji.Default); - public static readonly MessageDescriptor HotReloadCapabilities = Create("Hot reload capabilities: {0}.", Emoji.HotReload, MessageSeverity.Verbose); - public static readonly MessageDescriptor HotReloadSuspended = Create("Hot reload suspended. To continue hot reload, press \"Ctrl + R\".", Emoji.HotReload, MessageSeverity.Output); - public static readonly MessageDescriptor UnableToApplyChanges = Create("Unable to apply changes due to compilation errors.", Emoji.HotReload, MessageSeverity.Output); - public static readonly MessageDescriptor RestartNeededToApplyChanges = Create("Restart is needed to apply the changes.", Emoji.HotReload, MessageSeverity.Output); - public static readonly MessageDescriptor HotReloadEnabled = Create("Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload.", Emoji.HotReload, MessageSeverity.Output); - public static readonly MessageDescriptor PressCtrlRToRestart = Create("Press Ctrl+R to restart.", Emoji.LightBulb, MessageSeverity.Output); - public static readonly MessageDescriptor HotReloadCanceledProcessExited = Create("Hot reload canceled because the process exited.", Emoji.HotReload, MessageSeverity.Verbose); - public static readonly MessageDescriptor ApplicationKind_BlazorHosted = Create("Application kind: BlazorHosted. '{0}' references BlazorWebAssembly project '{1}'.", Emoji.Default, MessageSeverity.Verbose); - public static readonly MessageDescriptor ApplicationKind_BlazorWebAssembly = Create("Application kind: BlazorWebAssembly.", Emoji.Default, MessageSeverity.Verbose); - public static readonly MessageDescriptor ApplicationKind_WebApplication = Create("Application kind: WebApplication.", Emoji.Default, MessageSeverity.Verbose); - public static readonly MessageDescriptor ApplicationKind_Default = Create("Application kind: Default.", Emoji.Default, MessageSeverity.Verbose); - public static readonly MessageDescriptor WatchingFilesForChanges = Create("Watching {0} file(s) for changes", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor WatchingFilesForChanges_FilePath = Create("> {0}", Emoji.Watch, MessageSeverity.Verbose); - public static readonly MessageDescriptor Building = Create("Building {0} ...", Emoji.Default, MessageSeverity.Output); - public static readonly MessageDescriptor BuildSucceeded = Create("Build succeeded: {0}", Emoji.Default, MessageSeverity.Output); - public static readonly MessageDescriptor BuildFailed = Create("Build failed: {0}", Emoji.Default, MessageSeverity.Output); - + public static readonly MessageDescriptor HotReloadCapabilities = Create("Hot reload capabilities: {0}.", Emoji.HotReload, LogLevel.Debug); + public static readonly MessageDescriptor HotReloadSuspended = Create("Hot reload suspended. To continue hot reload, press \"Ctrl + R\".", Emoji.HotReload, LogLevel.Information); + public static readonly MessageDescriptor UnableToApplyChanges = Create("Unable to apply changes due to compilation errors.", Emoji.HotReload, LogLevel.Information); + public static readonly MessageDescriptor RestartNeededToApplyChanges = Create("Restart is needed to apply the changes.", Emoji.HotReload, LogLevel.Information); + public static readonly MessageDescriptor HotReloadEnabled = Create("Hot reload enabled. For a list of supported edits, see https://aka.ms/dotnet/hot-reload.", Emoji.HotReload, LogLevel.Information); + public static readonly MessageDescriptor PressCtrlRToRestart = Create("Press Ctrl+R to restart.", Emoji.LightBulb, LogLevel.Information); + public static readonly MessageDescriptor HotReloadCanceledProcessExited = Create("Hot reload canceled because the process exited.", Emoji.HotReload, LogLevel.Debug); + public static readonly MessageDescriptor ApplicationKind_BlazorHosted = Create("Application kind: BlazorHosted. '{0}' references BlazorWebAssembly project '{1}'.", Emoji.Default, LogLevel.Debug); + public static readonly MessageDescriptor ApplicationKind_BlazorWebAssembly = Create("Application kind: BlazorWebAssembly.", Emoji.Default, LogLevel.Debug); + public static readonly MessageDescriptor ApplicationKind_WebApplication = Create("Application kind: WebApplication.", Emoji.Default, LogLevel.Debug); + public static readonly MessageDescriptor ApplicationKind_Default = Create("Application kind: Default.", Emoji.Default, LogLevel.Debug); + public static readonly MessageDescriptor WatchingFilesForChanges = Create("Watching {0} file(s) for changes", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor WatchingFilesForChanges_FilePath = Create("> {0}", Emoji.Watch, LogLevel.Debug); + public static readonly MessageDescriptor Building = Create("Building {0} ...", Emoji.Default, LogLevel.Information); + public static readonly MessageDescriptor BuildSucceeded = Create("Build succeeded: {0}", Emoji.Default, LogLevel.Information); + public static readonly MessageDescriptor BuildFailed = Create("Build failed: {0}", Emoji.Default, LogLevel.Information); } internal interface IProcessOutputReporter @@ -278,9 +258,6 @@ internal interface IProcessOutputReporter internal interface IReporter { - void Report(EventId id, Emoji emoji, MessageSeverity severity, string message); - - public bool IsVerbose - => false; + void Report(EventId id, Emoji emoji, LogLevel level, string message); } } diff --git a/test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs b/test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs index 768eb47b262b..7495098d82ff 100644 --- a/test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs +++ b/test/dotnet-watch.Tests/CommandLine/CommandLineOptionsTests.cs @@ -3,6 +3,8 @@ #nullable disable +using Microsoft.Extensions.Logging; + namespace Microsoft.DotNet.Watch.UnitTests { public class CommandLineOptionsTests @@ -187,7 +189,7 @@ public void RemainingOptions() { var options = VerifyOptions(["-watchArg", "--verbose", "run", "-runArg"]); - Assert.True(options.GlobalOptions.Verbose); + Assert.Equal(LogLevel.Debug, options.GlobalOptions.LogLevel); Assert.Equal("run", options.Command); AssertEx.SequenceEqual(["-watchArg", "-runArg"], options.CommandArguments); } @@ -207,7 +209,7 @@ public void RemainingOptionsDashDash() { var options = VerifyOptions(["-watchArg", "--", "--verbose", "run", "-runArg"]); - Assert.False(options.GlobalOptions.Verbose); + Assert.Equal(LogLevel.Information, options.GlobalOptions.LogLevel); Assert.Equal("run", options.Command); AssertEx.SequenceEqual(["-watchArg", "--", "--verbose", "run", "-runArg",], options.CommandArguments); } @@ -217,7 +219,7 @@ public void RemainingOptionsDashDashRun() { var options = VerifyOptions(["--", "run"]); - Assert.False(options.GlobalOptions.Verbose); + Assert.Equal(LogLevel.Information, options.GlobalOptions.LogLevel); Assert.Equal("run", options.Command); AssertEx.SequenceEqual(["--", "run"], options.CommandArguments); } @@ -357,9 +359,9 @@ public void OptionDuplicates_Allowed_Bool( args = position switch { - ArgPosition.Before => args.Prepend("run").ToArray(), - ArgPosition.Both => args.Concat(new[] { "run" }).Concat(args).ToArray(), - ArgPosition.After => args.Append("run").ToArray(), + ArgPosition.Before => ["run", .. args], + ArgPosition.Both => [.. args, "run", .. args], + ArgPosition.After => [.. args, "run"], _ => args, }; @@ -367,8 +369,8 @@ public void OptionDuplicates_Allowed_Bool( Assert.True(arg switch { - "--verbose" => options.GlobalOptions.Verbose, - "--quiet" => options.GlobalOptions.Quiet, + "--verbose" => options.GlobalOptions.LogLevel == LogLevel.Debug, + "--quiet" => options.GlobalOptions.LogLevel == LogLevel.Warning, "--list" => options.List, "--no-hot-reload" => options.GlobalOptions.NoHotReload, "--non-interactive" => options.GlobalOptions.NonInteractive, diff --git a/test/dotnet-watch.Tests/CommandLine/ProgramTests.cs b/test/dotnet-watch.Tests/CommandLine/ProgramTests.cs index caee7fb19636..c8c57563a0ca 100644 --- a/test/dotnet-watch.Tests/CommandLine/ProgramTests.cs +++ b/test/dotnet-watch.Tests/CommandLine/ProgramTests.cs @@ -3,6 +3,8 @@ #nullable disable +using Microsoft.Extensions.Logging; + namespace Microsoft.DotNet.Watch.UnitTests { public class ProgramTests(ITestOutputHelper logger) : DotNetWatchTestBase(logger) @@ -15,7 +17,7 @@ public async Task ConsoleCancelKey() var console = new TestConsole(Logger); var reporter = new TestReporter(Logger); - var loggerFactory = new LoggerFactory(reporter); + var loggerFactory = new LoggerFactory(reporter, LogLevel.Debug); var watching = reporter.RegisterSemaphore(MessageDescriptor.WatchingWithHotReload); var shutdownRequested = reporter.RegisterSemaphore(MessageDescriptor.ShutdownRequested); diff --git a/test/dotnet-watch.Tests/ConsoleReporterTests.cs b/test/dotnet-watch.Tests/ConsoleReporterTests.cs index 8df1601dc0dc..d06075160675 100644 --- a/test/dotnet-watch.Tests/ConsoleReporterTests.cs +++ b/test/dotnet-watch.Tests/ConsoleReporterTests.cs @@ -3,6 +3,8 @@ #nullable disable +using Microsoft.Extensions.Logging; + namespace Microsoft.DotNet.Watch.UnitTests { public class ConsoleReporterTests @@ -15,23 +17,31 @@ public class ConsoleReporterTests public void WritesToStandardStreams(bool suppressEmojis) { var testConsole = new TestConsole(); - var reporter = new ConsoleReporter(testConsole, verbose: true, quiet: false, suppressEmojis: suppressEmojis); + var reporter = new ConsoleReporter(testConsole, suppressEmojis: suppressEmojis); + + reporter.Report(id: default, Emoji.Watch, LogLevel.Trace, "trace {0}"); + Assert.Empty(testConsole.GetError()); + testConsole.Clear(); - reporter.Report(id: default, Emoji.Watch, MessageSeverity.Verbose, "verbose {0}"); + reporter.Report(id: default, Emoji.Watch, LogLevel.Debug, "verbose {0}"); Assert.Equal($"dotnet watch {(suppressEmojis ? ":" : "⌚")} verbose {{0}}" + EOL, testConsole.GetError()); testConsole.Clear(); - reporter.Report(id: default, Emoji.Watch, MessageSeverity.Output, "out"); + reporter.Report(id: default, Emoji.Watch, LogLevel.Information, "out"); Assert.Equal($"dotnet watch {(suppressEmojis ? ":" : "⌚")} out" + EOL, testConsole.GetError()); testConsole.Clear(); - reporter.Report(id: default, Emoji.Warning, MessageSeverity.Warning, "warn"); + reporter.Report(id: default, Emoji.Warning, LogLevel.Warning, "warn"); Assert.Equal($"dotnet watch {(suppressEmojis ? ":" : "⚠")} warn" + EOL, testConsole.GetError()); testConsole.Clear(); - reporter.Report(id: default, Emoji.Error, MessageSeverity.Error, "error"); + reporter.Report(id: default, Emoji.Error, LogLevel.Error, "error"); Assert.Equal($"dotnet watch {(suppressEmojis ? ":" : "❌")} error" + EOL, testConsole.GetError()); testConsole.Clear(); + + reporter.Report(id: default, Emoji.Error, LogLevel.Critical, "critical"); + Assert.Equal($"dotnet watch {(suppressEmojis ? ":" : "❌")} critical" + EOL, testConsole.GetError()); + testConsole.Clear(); } private class TestConsole : IConsole diff --git a/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs b/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs index 9ac5030c4eba..91430900b71a 100644 --- a/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs +++ b/test/dotnet-watch.Tests/HotReload/CompilationHandlerTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.Extensions.Logging; + namespace Microsoft.DotNet.Watch.UnitTests; public class CompilationHandlerTests(ITestOutputHelper output) : DotNetWatchTestBase(output) @@ -22,7 +24,7 @@ public async Task ReferenceOutputAssembly_False() var processRunner = new ProcessRunner(processCleanupTimeout: TimeSpan.Zero); var reporter = new TestReporter(Logger); - var loggerFactory = new LoggerFactory(reporter); + var loggerFactory = new LoggerFactory(reporter, LogLevel.Debug); var logger = loggerFactory.CreateLogger("Test"); var projectGraph = ProjectGraphUtilities.TryLoadProjectGraph(options.ProjectPath, globalOptions: [], logger, projectGraphRequired: false, CancellationToken.None); var handler = new CompilationHandler(logger, processRunner); diff --git a/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs b/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs index ff2c8491194f..5d6aa7cbe349 100644 --- a/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs +++ b/test/dotnet-watch.Tests/HotReload/RuntimeProcessLauncherTests.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Watch.UnitTests; @@ -96,7 +97,7 @@ private RunningWatcher StartWatcher(TestAsset testAsset, string[] args, string? { var console = new TestConsole(Logger); var reporter = new TestReporter(Logger); - var loggerFactory = new LoggerFactory(reporter); + var loggerFactory = new LoggerFactory(reporter, LogLevel.Debug); var environmentOptions = TestOptions.GetEnvironmentOptions(workingDirectory ?? testAsset.Path, TestContext.Current.ToolsetUnderTest.DotNetHostPath, testAsset); var processRunner = new ProcessRunner(environmentOptions.GetProcessCleanupTimeout(isHotReloadEnabled: true)); diff --git a/test/dotnet-watch.Tests/TestUtilities/TestReporter.cs b/test/dotnet-watch.Tests/TestUtilities/TestReporter.cs index cd3da3e4476f..849ca9f6baf8 100644 --- a/test/dotnet-watch.Tests/TestUtilities/TestReporter.cs +++ b/test/dotnet-watch.Tests/TestUtilities/TestReporter.cs @@ -9,7 +9,7 @@ internal class TestReporter(ITestOutputHelper output) : IReporter, IProcessOutpu { private readonly Dictionary _actions = []; public readonly List ProcessOutput = []; - public readonly List<(MessageSeverity severity, string text)> Messages = []; + public readonly List<(LogLevel level, string text)> Messages = []; public bool IsVerbose => true; @@ -51,12 +51,12 @@ public void RegisterAction(EventId eventId, Action action) _actions[eventId] = existing; } - public void Report(EventId id, Emoji emoji, MessageSeverity severity, string message) + public void Report(EventId id, Emoji emoji, LogLevel level, string message) { - if (severity != MessageSeverity.None) + if (level != LogLevel.None) { - Messages.Add((severity, message)); - WriteTestOutput($"{ToString(severity)} {emoji.ToDisplay()} {message}"); + Messages.Add((level, message)); + WriteTestOutput($"{ToString(level)} {emoji.ToDisplay()} {message}"); } if (_actions.TryGetValue(id, out var action)) @@ -77,13 +77,13 @@ private void WriteTestOutput(string line) } } - private static string ToString(MessageSeverity severity) - => severity switch + private static string ToString(LogLevel level) + => level switch { - MessageSeverity.Verbose => "verbose", - MessageSeverity.Output => "output", - MessageSeverity.Warning => "warning", - MessageSeverity.Error => "error", + LogLevel.Trace or LogLevel.Debug => "verbose", + LogLevel.Information => "output", + LogLevel.Warning => "warning", + LogLevel.Critical or LogLevel.Error => "error", _ => throw new InvalidOperationException() }; }