Add option to suppress writing startup messages

This commit is contained in:
John Luo 2017-09-27 19:20:33 -07:00
parent 6687f4eefc
commit a63932a492
18 changed files with 155 additions and 188 deletions

View File

@ -159,6 +159,17 @@ namespace Microsoft.AspNetCore.Hosting
return hostBuilder.UseSetting(WebHostDefaults.PreferHostingUrlsKey, preferHostingUrls ? "true" : "false");
}
/// <summary>
/// Specify if startup status messages should be suppressed.
/// </summary>
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <param name="suppressStatusMessages"><c>true</c> to suppress writing of hosting startup status messages; otherwise <c>false</c>.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder SuppressStatusMessages(this IWebHostBuilder hostBuilder, bool suppressStatusMessages)
{
return hostBuilder.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, suppressStatusMessages ? "true" : "false");
}
/// <summary>
/// Specify the amount of time to wait for the web host to shutdown.
/// </summary>

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Hosting
public static readonly string ContentRootKey = "contentRoot";
public static readonly string PreferHostingUrlsKey = "preferHostingUrls";
public static readonly string PreventHostingStartupKey = "preventHostingStartup";
public static readonly string SuppressStatusMessagesKey = "suppressStatusMessages";
public static readonly string ShutdownTimeoutKey = "shutdownTimeoutSeconds";
}

View File

@ -204,8 +204,11 @@ namespace Microsoft.AspNetCore.Hosting.Internal
_applicationServices = _applicationServiceCollection.BuildServiceProvider();
}
// Write errors to standard out so they can be retrieved when not in development mode.
Console.Out.WriteLine("Application startup exception: " + ex.ToString());
if (!_options.SuppressStatusMessages)
{
// Write errors to standard out so they can be retrieved when not in development mode.
Console.WriteLine("Application startup exception: " + ex.ToString());
}
var logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
logger.ApplicationError(ex);

View File

@ -30,6 +30,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
WebRoot = configuration[WebHostDefaults.WebRootKey];
ContentRootPath = configuration[WebHostDefaults.ContentRootKey];
PreventHostingStartup = WebHostUtilities.ParseBool(configuration, WebHostDefaults.PreventHostingStartupKey);
SuppressStatusMessages = WebHostUtilities.ParseBool(configuration, WebHostDefaults.SuppressStatusMessagesKey);
// Search the primary assembly and configured assemblies.
HostingStartupAssemblies = $"{ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]}"
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
@ -46,6 +48,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
public bool PreventHostingStartup { get; set; }
public bool SuppressStatusMessages { get; set; }
public IReadOnlyList<string> HostingStartupAssemblies { get; set; }
public bool DetailedErrors { get; set; }

View File

@ -151,26 +151,36 @@ namespace Microsoft.AspNetCore.Hosting
}
_webHostBuilt = true;
// Warn about deprecated environment variables
if (Environment.GetEnvironmentVariable("Hosting:Environment") != null)
{
Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
}
if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null)
{
Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
}
if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null)
{
Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'");
}
var hostingServices = BuildCommonServices(out var hostingStartupErrors);
var applicationServices = hostingServices.Clone();
var hostingServiceProvider = hostingServices.BuildServiceProvider();
if (!_options.SuppressStatusMessages)
{
// Warn about deprecated environment variables
if (Environment.GetEnvironmentVariable("Hosting:Environment") != null)
{
Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
}
if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null)
{
Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
}
if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null)
{
Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'");
}
}
var logger = hostingServiceProvider.GetRequiredService<ILogger<WebHost>>();
// Warn about duplicate HostingStartupAssemblies
foreach (var assemblyName in _options.HostingStartupAssemblies.GroupBy(a => a, StringComparer.OrdinalIgnoreCase).Where(g => g.Count() > 1))
{
logger.LogWarning($"The assembly {assemblyName} was specified multiple times. Hosting startup assemblies should only be specified once.");
}
AddApplicationServices(applicationServices, hostingServiceProvider);
var host = new WebHost(
@ -228,6 +238,7 @@ namespace Microsoft.AspNetCore.Hosting
_context.HostingEnvironment = _hostingEnvironment;
var services = new ServiceCollection();
services.AddSingleton(_options);
services.AddSingleton(_hostingEnvironment);
services.AddSingleton(_context);

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting.Internal;
namespace Microsoft.AspNetCore.Hosting
{
@ -76,7 +77,8 @@ namespace Microsoft.AspNetCore.Hosting
var done = new ManualResetEventSlim(false);
using (var cts = new CancellationTokenSource())
{
AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: "Application is shutting down...");
var shutdownMessage = host.Services.GetRequiredService<WebHostOptions>().SuppressStatusMessages ? string.Empty : "Application is shutting down...";
AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: shutdownMessage);
await host.RunAsync(cts.Token, "Application started. Press Ctrl+C to shut down.");
done.Set();
@ -91,22 +93,27 @@ namespace Microsoft.AspNetCore.Hosting
var hostingEnvironment = host.Services.GetService<IHostingEnvironment>();
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
var options = host.Services.GetRequiredService<WebHostOptions>();
Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}");
Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}");
var serverAddresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses;
if (serverAddresses != null)
if (!options.SuppressStatusMessages)
{
foreach (var address in serverAddresses)
Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}");
Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}");
var serverAddresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses;
if (serverAddresses != null)
{
Console.WriteLine($"Now listening on: {address}");
foreach (var address in serverAddresses)
{
Console.WriteLine($"Now listening on: {address}");
}
}
}
if (!string.IsNullOrEmpty(shutdownMessage))
{
Console.WriteLine(shutdownMessage);
if (!string.IsNullOrEmpty(shutdownMessage))
{
Console.WriteLine(shutdownMessage);
}
}
await host.WaitForTokenShutdownAsync(token);

View File

@ -95,6 +95,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
public bool PreservePublishedApplicationForDebugging { get; set; } = false;
public bool StatusMessagesEnabled { get; set; } = true;
public ApplicationType ApplicationType { get; set; }
public string PublishedApplicationRootPath { get; set; }

View File

@ -164,10 +164,14 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
Logger.LogInformation("Started {fileName}. Process Id : {processId}", startInfo.FileName, HostProcess.Id);
// The timeout here is large, because we don't know how long the test could need
// We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build
// just in case we missed one -anurse
await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10));
// Host may not write startup messages, in which case assume it started
if (DeploymentParameters.StatusMessagesEnabled)
{
// The timeout here is large, because we don't know how long the test could need
// We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build
// just in case we missed one -anurse
await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10));
}
return (url: actualUrl ?? hintUrl, hostExitToken: hostExitTokenSource.Token);
}

View File

@ -6,9 +6,9 @@ namespace Microsoft.Extensions.Hosting
public class ConsoleLifetimeOptions
{
/// <summary>
/// Indicates if host lifetime status messages should be written to the console such as on startup.
/// The default is true.
/// Indicates if host lifetime status messages should be supressed such as on startup.
/// The default is false.
/// </summary>
public bool WriteStatusMessages { get; set; } = true;
public bool SuppressStatusMessages { get; set; }
}
}

View File

@ -28,7 +28,7 @@ namespace Microsoft.Extensions.Hosting.Internal
public void RegisterDelayStartCallback(Action<object> callback, object state)
{
if (Options.WriteStatusMessages)
if (!Options.SuppressStatusMessages)
{
ApplicationLifetime.ApplicationStarted.Register(() =>
{

View File

@ -1,9 +1,10 @@
// 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.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing;
@ -16,78 +17,35 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
{
public class ShutdownTests : LoggedTest
{
private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: (?<url>.*)$");
private const string ApplicationStartedMessage = "Application started. Press Ctrl+C to shut down.";
private static readonly string StartedMessage = "Started";
private static readonly string CompletionMessage = "Stopping firing\n" +
"Stopping end\n" +
"Stopped firing\n" +
"Stopped end";
public ShutdownTests(ITestOutputHelper output) : base(output)
{
}
public ShutdownTests(ITestOutputHelper output) : base(output) { }
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows)]
[OSSkipCondition(OperatingSystems.MacOSX)]
public async Task ShutdownTestRun()
{
using (StartLog(out var loggerFactory))
{
var logger = loggerFactory.CreateLogger(nameof(ShutdownTestRun));
var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "TestAssets",
"Microsoft.AspNetCore.Hosting.TestSites");
var deploymentParameters = new DeploymentParameters(
applicationPath,
ServerType.Kestrel,
RuntimeFlavor.CoreClr,
RuntimeArchitecture.x64)
{
EnvironmentName = "Shutdown",
TargetFramework = "netcoreapp2.0",
ApplicationType = ApplicationType.Portable,
PublishApplicationBeforeDeployment = true
};
deploymentParameters.EnvironmentVariables["ASPNETCORE_STARTMECHANIC"] = "Run";
using (var deployer = new SelfHostDeployer(deploymentParameters, loggerFactory))
{
await deployer.DeployAsync();
string output = string.Empty;
deployer.HostProcess.OutputDataReceived += (sender, args) =>
{
if (!string.Equals(args.Data, ApplicationStartedMessage)
&& !string.IsNullOrEmpty(args.Data)
&& !NowListeningRegex.Match(args.Data).Success)
{
output += args.Data + '\n';
}
};
SendSIGINT(deployer.HostProcess.Id);
WaitForExitOrKill(deployer.HostProcess);
output = output.Trim('\n');
Assert.Equal("Application is shutting down...\n" +
"Stopping firing\n" +
"Stopping end\n" +
"Stopped firing\n" +
"Stopped end",
output);
}
}
await ExecuteShutdownTest(nameof(ShutdownTestRun), "Run");
}
[ConditionalFact]
[OSSkipCondition(OperatingSystems.Windows)]
[OSSkipCondition(OperatingSystems.MacOSX)]
public async Task ShutdownTestWaitForShutdown()
{
await ExecuteShutdownTest(nameof(ShutdownTestWaitForShutdown), "WaitForShutdown");
}
private async Task ExecuteShutdownTest(string testName, string shutdownMechanic)
{
using (StartLog(out var loggerFactory))
{
var logger = loggerFactory.CreateLogger(nameof(ShutdownTestWaitForShutdown));
var logger = loggerFactory.CreateLogger(testName);
var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "TestAssets",
"Microsoft.AspNetCore.Hosting.TestSites");
@ -101,29 +59,58 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
EnvironmentName = "Shutdown",
TargetFramework = "netcoreapp2.0",
ApplicationType = ApplicationType.Portable,
PublishApplicationBeforeDeployment = true
PublishApplicationBeforeDeployment = true,
StatusMessagesEnabled = false
};
deploymentParameters.EnvironmentVariables["ASPNETCORE_STARTMECHANIC"] = "WaitForShutdown";
deploymentParameters.EnvironmentVariables["ASPNETCORE_STARTMECHANIC"] = shutdownMechanic;
using (var deployer = new SelfHostDeployer(deploymentParameters, loggerFactory))
{
await deployer.DeployAsync();
string output = string.Empty;
deployer.HostProcess.OutputDataReceived += (sender, args) => output += args.Data + '\n';
var started = new ManualResetEventSlim();
var completed = new ManualResetEventSlim();
var output = string.Empty;
deployer.HostProcess.OutputDataReceived += (sender, args) =>
{
if (!string.IsNullOrEmpty(args.Data) && args.Data.StartsWith(StartedMessage))
{
started.Set();
output += args.Data.Substring(StartedMessage.Length) + '\n';
}
else
{
output += args.Data + '\n';
}
if (output.Contains(CompletionMessage))
{
completed.Set();
}
};
started.Wait(50000);
if (!started.IsSet)
{
throw new InvalidOperationException("Application did not start successfully");
}
SendSIGINT(deployer.HostProcess.Id);
WaitForExitOrKill(deployer.HostProcess);
completed.Wait(50000);
if (!started.IsSet)
{
throw new InvalidOperationException($"Application did not write the expected output. The received output is: {output}");
}
output = output.Trim('\n');
Assert.Equal("Stopping firing\n" +
"Stopping end\n" +
"Stopped firing\n" +
"Stopped end",
output);
Assert.Equal(CompletionMessage, output);
}
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IntegrationTesting;
@ -16,17 +15,12 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
{
public class WebHostBuilderTests : LoggedTest
{
private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: (?<url>.*)$");
private const string ApplicationStartedMessage = "Application started. Press Ctrl+C to shut down.";
public WebHostBuilderTests(ITestOutputHelper output) : base(output)
{
}
public WebHostBuilderTests(ITestOutputHelper output) : base(output) { }
[Fact]
public async Task InjectedStartup_DefaultApplicationNameIsEntryAssembly_CoreClr()
=> await InjectedStartup_DefaultApplicationNameIsEntryAssembly(RuntimeFlavor.CoreClr);
[ConditionalFact]
[OSSkipCondition(OperatingSystems.MacOSX)]
[OSSkipCondition(OperatingSystems.Linux)]
@ -48,7 +42,8 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
RuntimeArchitecture.x64)
{
TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0",
ApplicationType = ApplicationType.Portable
ApplicationType = ApplicationType.Portable,
StatusMessagesEnabled = false
};
using (var deployer = new SelfHostDeployer(deploymentParameters, loggerFactory))
@ -59,17 +54,15 @@ namespace Microsoft.AspNetCore.Hosting.FunctionalTests
var mre = new ManualResetEventSlim();
deployer.HostProcess.OutputDataReceived += (sender, args) =>
{
if (!string.Equals(args.Data, ApplicationStartedMessage)
&& !string.IsNullOrEmpty(args.Data)
&& !NowListeningRegex.Match(args.Data).Success)
if (!string.IsNullOrWhiteSpace(args.Data))
{
output += args.Data + '\n';
mre.Set();
}
};
mre.Wait(10000);
mre.Wait(50000);
output = output.Trim('\n');
Assert.Equal($"IStartupInjectionAssemblyName", output);

View File

@ -15,12 +15,13 @@ namespace Microsoft.AspNetCore.Hosting.Tests
{
var parameters = new Dictionary<string, string>()
{
{ "webroot", "wwwroot"},
{ "applicationName", "MyProjectReference"},
{ "startupAssembly", "MyProjectReference" },
{ "environment", EnvironmentName.Development},
{ "detailederrors", "true"},
{ "captureStartupErrors", "true" }
{ WebHostDefaults.WebRootKey, "wwwroot"},
{ WebHostDefaults.ApplicationKey, "MyProjectReference"},
{ WebHostDefaults.StartupAssemblyKey, "MyProjectReference" },
{ WebHostDefaults.EnvironmentKey, EnvironmentName.Development},
{ WebHostDefaults.DetailedErrorsKey, "true"},
{ WebHostDefaults.CaptureStartupErrorsKey, "true" },
{ WebHostDefaults.SuppressStatusMessagesKey, "true" }
};
var config = new WebHostOptions(new ConfigurationBuilder().AddInMemoryCollection(parameters).Build());
@ -31,6 +32,7 @@ namespace Microsoft.AspNetCore.Hosting.Tests
Assert.Equal(EnvironmentName.Development, config.Environment);
Assert.True(config.CaptureStartupErrors);
Assert.True(config.DetailedErrors);
Assert.True(config.SuppressStatusMessages);
}
[Fact]

View File

@ -15,15 +15,11 @@ namespace IStartupInjectionAssemblyName
{
var server = new TestServer(CreateWebHostBuilder(args));
// Mimic application startup messages so application deployer knows that the application has started
Console.WriteLine("Application started. Press Ctrl+C to shut down.");
Console.WriteLine("Now listening on: http://localhost:5000");
Task.Run(async () => Console.WriteLine(await server.CreateClient().GetStringAsync(""))).GetAwaiter().GetResult();
}
// Do not change the signature of this method. It's used for tests.
private static IWebHostBuilder CreateWebHostBuilder(string [] args) =>
new WebHostBuilder().ConfigureServices(services => services.AddSingleton<IStartup, Startup>());
new WebHostBuilder().SuppressStatusMessages(true).ConfigureServices(services => services.AddSingleton<IStartup, Startup>());
}
}

View File

@ -1,27 +0,0 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50801/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IStartupInjectionAssemblyName": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:50802/"
}
}
}

View File

@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
@ -26,6 +25,7 @@ namespace ServerComparison.TestSites
var builder = new WebHostBuilder()
.UseServer(new NoopServer())
.UseConfiguration(config)
.SuppressStatusMessages(true)
.ConfigureLogging((_, factory) =>
{
factory.AddConsole();
@ -45,10 +45,6 @@ namespace ServerComparison.TestSites
{
host.Start();
// Mimic application startup messages so application deployer knows that the application has started
Console.WriteLine("Application started. Press Ctrl+C to shut down.");
Console.WriteLine("Now listening on: http://localhost:5000");
host.WaitForShutdown();
}
}

View File

@ -1,27 +0,0 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50800/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Microsoft.AspNetCore.Hosting.TestSites": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:50803/"
}
}
}

View File

@ -12,6 +12,10 @@ namespace Microsoft.AspNetCore.Hosting.TestSites
{
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IApplicationLifetime lifetime)
{
lifetime.ApplicationStarted.Register(() =>
{
Console.WriteLine("Started");
});
lifetime.ApplicationStopping.Register(() =>
{
Console.WriteLine("Stopping firing");