|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
using Microsoft.Extensions.Logging;
using Xunit;
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
public class EventLogHelpers
{
public static async Task VerifyEventLogEventAsync(IISDeploymentResult deploymentResult, string expectedRegexMatchString, ILogger logger, bool allowMultiple = false)
{
Assert.True(deploymentResult.HostProcess.HasExited);
try
{
await AssertEntryAsync(expectedRegexMatchString, deploymentResult, allowMultiple);
}
catch (Exception)
{
var entries = GetEntries(deploymentResult);
foreach (var entry in entries)
{
logger.LogInformation("'{Message}', generated {Generated}, written {Written}", entry.Message, entry.TimeGenerated, entry.TimeWritten);
}
throw;
}
}
public static async Task VerifyEventLogEvents(IISDeploymentResult deploymentResult, params string[] expectedRegexMatchString)
{
Assert.True(deploymentResult.HostProcess.HasExited);
var entries = GetEntries(deploymentResult).ToList();
for (var i = 0; i < expectedRegexMatchString.Length; i++)
{
var matchedEntries = await AssertEntryAsync(expectedRegexMatchString[i], deploymentResult);
Assert.True(matchedEntries is not null, $"Regex {expectedRegexMatchString[i]} was not found.");
}
}
public static string OnlyOneAppPerAppPool()
{
if (DeployerSelector.HasNewShim)
{
return "Only one in-process application is allowed per IIS application pool. Please assign the application '(.*)' to a different IIS application pool.";
}
else
{
return "Only one inprocess application is allowed per IIS application pool";
}
}
private static async Task<EventLogEntry[]> AssertEntryAsync(string regexString, IISDeploymentResult deploymentResult, bool allowMultiple = false)
{
EventLogEntry[] matchedEntries = [];
var expectedRegex = new Regex(regexString, RegexOptions.Singleline);
// EventLogs don't seem to be instant, add retries to attempt to reduce test failures when looking for logs.
for (var i = 10; i > 0; i--)
{
var entries = GetEntries(deploymentResult);
matchedEntries = entries.Where(entry => expectedRegex.IsMatch(entry.Message)).ToArray();
if (matchedEntries.Length == 0)
{
await Task.Delay(100);
continue;
}
AssertEntries();
return matchedEntries;
}
AssertEntries();
return matchedEntries;
void AssertEntries()
{
Assert.True(matchedEntries.Length > 0, $"No entries matched by '{regexString}'");
Assert.True(allowMultiple || matchedEntries.Length < 2, $"Multiple entries matched by '{regexString}': {FormatEntries(matchedEntries)}");
}
}
private static string FormatEntries(IEnumerable<EventLogEntry> entries)
{
return string.Join(",", entries.Select(e => e.Message));
}
internal static IEnumerable<EventLogEntry> GetEntries(IISDeploymentResult deploymentResult)
{
var eventLog = new EventLog("Application");
// Eventlog is already sorted based on time of event in ascending time.
// Check results in reverse order.
var processIdString = $"Process Id: {deploymentResult.HostProcess.Id}.";
// Event log messages round down to the nearest second, so subtract 5 seconds to make sure we get event logs
var processStartTime = deploymentResult.HostProcess.StartTime.AddSeconds(-10);
for (var i = eventLog.Entries.Count - 1; i >= 0; i--)
{
var eventLogEntry = eventLog.Entries[i];
if (eventLogEntry.TimeGenerated < processStartTime)
{
// If event logs is older than the process start time, we didn't find a match.
break;
}
if (eventLogEntry.ReplacementStrings == null ||
eventLogEntry.ReplacementStrings.Length < 3)
{
continue;
}
// ReplacementStings == EventData collection in EventLog
// This is unaffected if event providers are not registered correctly
if (eventLogEntry.Source == AncmVersionToMatch(deploymentResult) &&
processIdString == eventLogEntry.ReplacementStrings[1])
{
yield return eventLogEntry;
}
}
}
private static string AncmVersionToMatch(IISDeploymentResult deploymentResult)
{
return "IIS " +
(deploymentResult.DeploymentParameters.ServerType == ServerType.IISExpress ? "Express " : "") +
"AspNetCore Module V2";
}
public static string Started(IISDeploymentResult deploymentResult)
{
if (deploymentResult.DeploymentParameters.HostingModel == HostingModel.InProcess)
{
return InProcessStarted(deploymentResult);
}
else
{
return OutOfProcessStarted(deploymentResult);
}
}
public static string InProcessStarted(IISDeploymentResult deploymentResult)
{
if (DeployerSelector.HasNewHandler)
{
return $"Application '{EscapedContentRoot(deploymentResult)}' started successfully.";
}
else
{
return $"Application '{EscapedContentRoot(deploymentResult)}' started the coreclr in-process successfully";
}
}
public static string OutOfProcessStarted(IISDeploymentResult deploymentResult)
{
return $"Application '/LM/W3SVC/1/ROOT' started process '\\d+' successfully and process '\\d+' is listening on port '\\d+'.";
}
public static string InProcessFailedToStart(IISDeploymentResult deploymentResult, string reason)
{
if (DeployerSelector.HasNewHandler)
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' failed to load coreclr. Exception message:\r\n{reason}";
}
else
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' failed to load clr and managed application. {reason}";
}
}
public static string ShutdownMessage(IISDeploymentResult deploymentResult)
{
if (deploymentResult.DeploymentParameters.HostingModel == HostingModel.InProcess)
{
return "Application 'MACHINE/WEBROOT/APPHOST/.*?' has shutdown.";
}
else
{
return "Application '/LM/W3SVC/1/ROOT' with physical root '.*?' shut down process with Id '.*?' listening on port '.*?'";
}
}
public static string ShutdownFileChange(IISDeploymentResult deploymentResult)
{
return $"Application '{EscapedContentRoot(deploymentResult)}' was recycled after detecting file change in application directory.";
}
public static string InProcessFailedToStop(IISDeploymentResult deploymentResult, string reason)
{
return "Failed to gracefully shutdown application 'MACHINE/WEBROOT/APPHOST/.*?'.";
}
public static string InProcessThreadException(IISDeploymentResult deploymentResult, string reason)
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed exception{reason}";
}
public static string InProcessThreadExit(IISDeploymentResult deploymentResult, string code)
{
if (DeployerSelector.HasNewHandler)
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' has exited from Program.Main with exit code = '{code}'. Please check the stderr logs for more information.";
}
else
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed background thread exit, exit code = '{code}'.";
}
}
public static string InProcessThreadExitStdOut(IISDeploymentResult deploymentResult, string code, string output)
{
if (DeployerSelector.HasNewHandler)
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' has exited from Program.Main with exit code = '{code}'. First 30KB characters of captured stdout and stderr logs:\r\n{output}";
}
else
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' hit unexpected managed background thread exit, exit code = '{code}'. Last 4KB characters of captured stdout and stderr logs:\r\n{output}";
}
}
public static string FailedToStartApplication(IISDeploymentResult deploymentResult, string code)
{
return $"Failed to start application '/LM/W3SVC/1/ROOT', ErrorCode '{code}'.";
}
public static string ConfigurationLoadError(IISDeploymentResult deploymentResult, string reason)
{
if (DeployerSelector.HasNewShim)
{
return $"Could not load configuration. Exception message:\r\n{reason}";
}
else
{
return $"Could not load configuration. Exception message: {reason}";
}
}
public static string OutOfProcessFailedToStart(IISDeploymentResult deploymentResult, string output)
{
if (DeployerSelector.HasNewShim)
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' failed to start process with " +
$"commandline '(.*)' with multiple retries. " +
$"Failed to bind to port '(.*)'. First 30KB characters of captured stdout and stderr logs from multiple retries:\r\n{output}";
}
else
{
return $"Application '/LM/W3SVC/1/ROOT' with physical root '{EscapedContentRoot(deploymentResult)}' failed to start process with " +
$"commandline '(.*)' with multiple retries. " +
$"The last try of listening port is '(.*)'. See previous warnings for details.";
}
}
public static string InProcessHostfxrInvalid(IISDeploymentResult deploymentResult)
{
return $"Hostfxr version used does not support 'hostfxr_get_native_search_directories', update the version of hostfxr to a higher version. Path to hostfxr: '(.*)'.";
}
public static string InProcessHostfxrUnableToLoad(IISDeploymentResult deploymentResult)
{
return $"Unable to load '(.*)'. This might be caused by a bitness mismatch between IIS application pool and published application.";
}
public static string InProcessFailedToFindNativeDependencies(IISDeploymentResult deploymentResult)
{
if (DeployerSelector.HasNewShim)
{
return "Unable to locate application dependencies. Ensure that the versions of Microsoft.NetCore.App and Microsoft.AspNetCore.App targeted by the application are installed.";
}
else
{
return "Invoking hostfxr to find the inprocess request handler failed without finding any native dependencies. " +
"This most likely means the app is misconfigured, please check the versions of Microsoft.NetCore.App and Microsoft.AspNetCore.App that " +
"are targeted by the application and are installed on the machine.";
}
}
public static string InProcessFailedToFindApplication()
{
return "Provided application path does not exist, or isn't a .dll or .exe.";
}
public static string InProcessFailedToFindRequestHandler(IISDeploymentResult deploymentResult)
{
if (DeployerSelector.HasNewShim)
{
return "Could not find the assembly '(.*)' referenced for the in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS or Microsoft.AspNetCore.App is referenced in your application.";
}
else
{
return "Could not find the assembly '(.*)' referenced for the in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application.";
}
}
public static string CouldNotStartStdoutFileRedirection(string file, IISDeploymentResult deploymentResult)
{
return
$"Could not start stdout file redirection to '{Regex.Escape(file)}' with application base '{EscapedContentRoot(deploymentResult)}'.";
}
public static string CouldNotFindHandler()
{
if (DeployerSelector.HasNewShim)
{
return "Could not find 'aspnetcorev2_inprocess.dll'";
}
else
{
return "Could not find the assembly 'aspnetcorev2_inprocess.dll'";
}
}
public static string UnableToStart(IISDeploymentResult deploymentResult, string subError)
{
if (DeployerSelector.HasNewShim)
{
return $@"Application '{Regex.Escape(deploymentResult.ContentRoot)}\\' failed to start. Exception message:\r\n{subError}";
}
else
{
return $@"Application '{Regex.Escape(deploymentResult.ContentRoot)}\\' wasn't able to start. {subError}";
}
}
public static string FrameworkNotFound()
{
if (DeployerSelector.HasNewShim)
{
return "Unable to locate application dependencies. Ensure that the versions of Microsoft.NetCore.App and Microsoft.AspNetCore.App targeted by the application are installed.";
}
else
{
return "The framework 'Microsoft.NETCore.App', version '2.9.9' was not found.";
}
}
private static string EscapedContentRoot(IISDeploymentResult deploymentResult)
{
var contentRoot = deploymentResult.ContentRoot;
if (!contentRoot.EndsWith('\\'))
{
contentRoot += '\\';
}
return Regex.Escape(contentRoot);
}
}
|