Allow debug log file to be specified in handler settings; move tests from IISExpress to shared. (#1033)

This commit is contained in:
Justin Kotalik 2018-07-11 18:29:36 -07:00 committed by GitHub
parent 9635d4fd8d
commit 385f647215
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 267 additions and 44 deletions

View File

@ -111,7 +111,8 @@ private:
// we currently limit the size of m_pstrErrorInfo to 5000, be careful if you want to change its payload
//
APPLICATION_MANAGER() : m_pApplicationInfoHash(NULL),
m_hostingModel(HOSTING_UNKNOWN)
m_hostingModel(HOSTING_UNKNOWN),
m_fDebugInitialize(FALSE)
{
InitializeSRWLock(&m_srwLock);
}
@ -120,4 +121,5 @@ private:
static APPLICATION_MANAGER *sm_pApplicationManager;
SRWLOCK m_srwLock;
APP_HOSTING_MODEL m_hostingModel;
BOOL m_fDebugInitialize;
};

View File

@ -38,6 +38,15 @@ APPLICATION_MANAGER::GetOrCreateApplicationInfo(
*ppApplicationInfo = NULL;
if (!m_fDebugInitialize)
{
SRWExclusiveLock lock(m_srwLock);
if (!m_fDebugInitialize)
{
DebugInitializeFromConfig(*pServer, *pHttpContext->GetApplication());
m_fDebugInitialize = TRUE;
}
}
// The configuration path is unique for each application and is used for the
// key in the applicationInfoHash.
pszApplicationId = pHttpContext->GetApplication()->GetApplicationId();

View File

@ -58,7 +58,7 @@ ASPNETCORE_SHIM_CONFIG::Populate(
if (m_hostingModel == HOSTING_OUT_PROCESS)
{
RETURN_IF_FAILED(ConfigUtility::FindHandlerVersion(pAspNetCoreElement, &m_struHandlerVersion));
RETURN_IF_FAILED(ConfigUtility::FindHandlerVersion(pAspNetCoreElement, m_struHandlerVersion));
}
return S_OK;

View File

@ -25,6 +25,7 @@ BOOL g_fAspnetcoreRHLoadedError = FALSE;
BOOL g_fInShutdown = FALSE;
DWORD g_dwActiveServerProcesses = 0;
SRWLOCK g_srwLock;
PFN_ASPNETCORE_CREATE_APPLICATION g_pfnAspNetCoreCreateApplication;
VOID

View File

@ -7,24 +7,49 @@
#include "ahutil.h"
#include "stringu.h"
#include "exceptions.h"
#include "atlbase.h"
class ConfigUtility
{
#define CS_ASPNETCORE_HANDLER_SETTINGS L"handlerSettings"
#define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion"
#define CS_ASPNETCORE_DEBUG_FILE L"debugFile"
#define CS_ASPNETCORE_DEBUG_LEVEL L"debugLevel"
#define CS_ASPNETCORE_HANDLER_SETTINGS_NAME L"name"
#define CS_ASPNETCORE_HANDLER_SETTINGS_VALUE L"value"
public:
static
HRESULT
FindHandlerVersion(IAppHostElement* pElement, STRU* strHandlerVersionValue)
FindHandlerVersion(IAppHostElement* pElement, STRU& strHandlerVersionValue)
{
return FindKeyValuePair(pElement, CS_ASPNETCORE_HANDLER_VERSION, strHandlerVersionValue);
}
static
HRESULT
FindDebugFile(IAppHostElement* pElement, STRU& strDebugFile)
{
return FindKeyValuePair(pElement, CS_ASPNETCORE_DEBUG_FILE, strDebugFile);
}
static
HRESULT
FindDebugLevel(IAppHostElement* pElement, STRU& strDebugFile)
{
return FindKeyValuePair(pElement, CS_ASPNETCORE_DEBUG_LEVEL, strDebugFile);
}
private:
static
HRESULT
FindKeyValuePair(IAppHostElement* pElement, PCWSTR key, STRU& strHandlerVersionValue)
{
HRESULT hr;
CComPtr<IAppHostElement> pHandlerSettings = nullptr;
CComPtr<IAppHostElementCollection> pHandlerSettingsCollection = nullptr;
CComPtr<IAppHostElement> pHandlerVar = nullptr;
ENUM_INDEX index {};
ENUM_INDEX index{};
STRU strHandlerName;
STRU strHandlerValue;
@ -43,9 +68,9 @@ public:
RETURN_IF_FAILED(GetElementStringProperty(pHandlerVar, CS_ASPNETCORE_HANDLER_SETTINGS_NAME, &strHandlerName));
RETURN_IF_FAILED(GetElementStringProperty(pHandlerVar, CS_ASPNETCORE_HANDLER_SETTINGS_VALUE, &strHandlerValue));
if (strHandlerName.Equals(CS_ASPNETCORE_HANDLER_VERSION, TRUE))
if (strHandlerName.Equals(key, TRUE))
{
RETURN_IF_FAILED(strHandlerVersionValue->Copy(strHandlerValue));
RETURN_IF_FAILED(strHandlerVersionValue.Copy(strHandlerValue));
break;
}

View File

@ -10,9 +10,12 @@
#include "dbgutil.h"
#include "Environment.h"
#include "SRWExclusiveLock.h"
#include "exceptions.h"
#include "atlbase.h"
#include "config_utility.h"
inline HANDLE g_hStandardOutput;
inline HANDLE g_logFile;
inline HANDLE g_hStandardOutput = INVALID_HANDLE_VALUE;
inline HANDLE g_logFile = INVALID_HANDLE_VALUE;
inline SRWLOCK g_logFileLock;
VOID
@ -21,6 +24,8 @@ DebugInitialize()
g_hStandardOutput = GetStdHandle(STD_OUTPUT_HANDLE);
HKEY hKey;
InitializeSRWLock(&g_logFileLock);
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters",
@ -47,18 +52,9 @@ DebugInitialize()
RegCloseKey(hKey);
}
// We expect single digit value and a null char
const size_t environmentVariableValueSize = 2;
std::wstring environmentVariableValue(environmentVariableValueSize, '\0');
try
{
const auto value = std::stoi(Environment::GetEnvironmentVariableValue(L"ASPNETCORE_MODULE_DEBUG").value_or(L"0"));
if (value >= 1) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_ERROR;
if (value >= 2) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_WARNING;
if (value >= 3) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_INFO;
if (value >= 4) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_CONSOLE;
SetDebugFlags(Environment::GetEnvironmentVariableValue(L"ASPNETCORE_MODULE_DEBUG").value_or(L"0"));
}
catch (...)
{
@ -69,9 +65,73 @@ DebugInitialize()
{
const auto debugOutputFile = Environment::GetEnvironmentVariableValue(L"ASPNETCORE_MODULE_DEBUG_FILE");
if (debugOutputFile.has_value())
CreateDebugLogFile(debugOutputFile.value_or(L""));
}
catch (...)
{
// ignore
}
}
HRESULT
DebugInitializeFromConfig(IHttpServer& pHttpServer, IHttpApplication& pHttpApplication)
{
CComPtr<IAppHostElement> pAspNetCoreElement;
const CComBSTR bstrAspNetCoreSection = L"system.webServer/aspNetCore";
CComBSTR bstrConfigPath = pHttpApplication.GetAppConfigPath();
RETURN_IF_FAILED(pHttpServer.GetAdminManager()->GetAdminSection(bstrAspNetCoreSection,
bstrConfigPath,
&pAspNetCoreElement));
STRU debugFile;
RETURN_IF_FAILED(ConfigUtility::FindDebugFile(pAspNetCoreElement, debugFile));
STRU debugValue;
RETURN_IF_FAILED(ConfigUtility::FindDebugLevel(pAspNetCoreElement, debugValue));
SetDebugFlags(debugFile.QueryStr());
CreateDebugLogFile(debugFile.QueryStr());
return S_OK;
}
void SetDebugFlags(const std::wstring &debugValue)
{
try
{
if (!debugValue.empty())
{
g_logFile = CreateFileW(debugOutputFile.value().c_str(),
const auto value = std::stoi(debugValue.c_str());
if (value >= 1) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_ERROR;
if (value >= 2) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_WARNING;
if (value >= 3) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_INFO;
if (value >= 4) DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_CONSOLE;
}
}
catch (...)
{
// ignore
}
}
void CreateDebugLogFile(const std::wstring &debugOutputFile)
{
try
{
if (!debugOutputFile.empty())
{
if (g_logFile != INVALID_HANDLE_VALUE)
{
WLOG_INFOF(L"Switching debug log files to %s", debugOutputFile.c_str());
CloseHandle(g_logFile);
DEBUG_FLAGS_VAR &= ~ASPNETCORE_DEBUG_FLAG_FILE;
}
g_logFile = CreateFileW(debugOutputFile.c_str(),
(GENERIC_READ | GENERIC_WRITE),
(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
nullptr,
@ -81,11 +141,9 @@ DebugInitialize()
);
if (g_logFile != INVALID_HANDLE_VALUE)
{
InitializeSRWLock(&g_logFileLock);
DEBUG_FLAGS_VAR |= ASPNETCORE_DEBUG_FLAG_FILE;
}
}
}
catch (...)
{

View File

@ -3,6 +3,8 @@
#pragma once
#include "stdafx.h"
#include "stringu.h"
#include <Windows.h>
#include "dbgutil.h"
@ -27,6 +29,14 @@
VOID
DebugInitialize();
VOID
CreateDebugLogFile(const std::wstring &debugOutputFile);
HRESULT
DebugInitializeFromConfig(IHttpServer& pHttpServer, IHttpApplication& pHttpApplication);
void SetDebugFlags(const std::wstring &value);
VOID
DebugStop();

View File

@ -25,7 +25,8 @@ HANDLE g_hEventLog = NULL;
HRESULT
InitializeGlobalConfiguration(
IHttpServer * pServer
IHttpServer * pServer,
IHttpApplication* pHttpApplication
)
{
if (!g_fGlobalInitialize)
@ -48,6 +49,8 @@ InitializeGlobalConfiguration(
}
DebugInitialize();
DebugInitializeFromConfig(*pServer, *pHttpApplication);
g_fGlobalInitialize = TRUE;
}
}
@ -87,10 +90,9 @@ CreateApplication(
_Out_ IAPPLICATION **ppApplication
)
{
try
{
RETURN_IF_FAILED(InitializeGlobalConfiguration(pServer));
RETURN_IF_FAILED(InitializeGlobalConfiguration(pServer, pHttpApplication));
REQUESTHANDLER_CONFIG *pConfig = nullptr;
RETURN_IF_FAILED(REQUESTHANDLER_CONFIG::CreateRequestHandlerConfig(pServer, pHttpApplication, &pConfig));

View File

@ -5,6 +5,9 @@
#include "requesthandler_config.h"
#include "debugutil.h"
#include "environmentvariablehash.h"
#include "exceptions.h"
#include "config_utility.h"
REQUESTHANDLER_CONFIG::~REQUESTHANDLER_CONFIG()
{

View File

@ -91,5 +91,36 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
}
}
[ConditionalFact]
[SkipIIS]
public async Task StartupMessagesLogFileSwitchedWhenLogFilePresentInWebConfig()
{
var firstTempFile = Path.GetTempFileName();
var secondTempFile = Path.GetTempFileName();
try
{
var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true);
deploymentParameters.EnvironmentVariables["ASPNETCORE_MODULE_DEBUG_FILE"] = firstTempFile;
var deploymentResult = await DeployAsync(deploymentParameters);
Helpers.AddDebugLogToWebConfig(deploymentParameters.PublishedApplicationRootPath, secondTempFile);
var response = await deploymentResult.RetryingHttpClient.GetAsync("/");
StopServer();
var logContents = File.ReadAllText(firstTempFile);
Assert.Contains("Switching debug log files to", logContents);
var secondLogContents = File.ReadAllText(secondTempFile);
Assert.Contains("[aspnetcorev2.dll]", logContents);
Assert.Contains("[aspnetcorev2_inprocess.dll]", logContents);
}
finally
{
File.Delete(firstTempFile);
File.Delete(secondTempFile);
}
}
}
}

View File

@ -10,16 +10,18 @@ using Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
public class StartupExceptionTests : IISFunctionalTestBase
{
// TODO FileNotFound here.
[ConditionalTheory]
[InlineData("CheckLogFile")]
[InlineData("CheckErrLogFile")]
public async Task CheckStdoutWithRandomNumber(string path)
{
var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite");
// Forcing publish for now to have parity between IIS and IISExpress
// Reason is because by default for IISExpress, we expect there to not be a web.config file.
// However, for IIS, we need a web.config file because the default on generated on publish
// doesn't include V2. We can remove the publish flag once IIS supports non-publish running
var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite", publish: true);
deploymentParameters.EnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path;
var randomNumberString = new Random(Guid.NewGuid().GetHashCode()).Next(10000000).ToString();
deploymentParameters.EnvironmentVariables["ASPNETCORE_INPROCESS_RANDOM_VALUE"] = randomNumberString;
@ -42,9 +44,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[InlineData("CheckOversizedStdOutWrites")]
public async Task CheckStdoutWithLargeWrites(string path)
{
// Need a web.config
// Also publish issues.
var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite");
var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite", publish: true);
deploymentParameters.EnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path;
var deploymentResult = await DeployAsync(deploymentParameters);
@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
[ConditionalFact]
public async Task Gets500_30_ErrorPage()
{
var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite");
var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite", publish: true);
var deploymentResult = await DeployAsync(deploymentParameters);

View File

@ -52,6 +52,41 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
config.Save(webConfigFile);
}
public static void AddDebugLogToWebConfig(string contentRoot, string filename)
{
var path = Path.Combine(contentRoot, "web.config");
var webconfig = XDocument.Load(path);
var xElement = webconfig.Descendants("aspNetCore").Single();
var element = xElement.Descendants("handlerSettings").SingleOrDefault();
if (element == null)
{
element = new XElement("handlerSettings");
xElement.Add(element);
}
CreateOrSetElement(element, "debugLevel", "4");
CreateOrSetElement(element, "debugFile", Path.Combine(contentRoot, filename));
webconfig.Save(path);
}
private static void CreateOrSetElement(XElement rootElement, string name, string value)
{
if (rootElement.Descendants()
.Attributes()
.Where(attribute => attribute.Value == name)
.Any())
{
return;
}
var element = new XElement("handlerSetting");
element.SetAttributeValue("name", name);
element.SetAttributeValue("value", value);
rootElement.Add(element);
}
// Defaults to inprocess specific deployment parameters
public static DeploymentParameters GetBaseDeploymentParameters(string site = null, HostingModel hostingModel = HostingModel.InProcess, bool publish = false)
{
@ -72,5 +107,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
private static string GetWebConfigFile(IISDeploymentResult deploymentResult)
=> Path.Combine(deploymentResult.DeploymentResult.ContentRoot, "web.config");
}
}

View File

@ -221,6 +221,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
var pool = _serverManager.ApplicationPools.Add(AppPoolName);
pool.ProcessModel.IdentityType = ProcessModelIdentityType.LocalSystem;
pool.ManagedRuntimeVersion = string.Empty;
pool.StartMode = StartMode.AlwaysRunning;
AddEnvironmentVariables(contentRoot, pool);
@ -244,7 +245,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
AddEnvironmentVariableToAppPool(envCollection, tuple.Key, tuple.Value);
}
AddEnvironmentVariableToAppPool(envCollection, "ASPNETCORE_MODULE_DEBUG_FILE", $"{WebSiteName}.txt");
}
catch (COMException comException)
{

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.IntegrationTesting
@ -58,13 +59,14 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
// For now, only support using published output
DeploymentParameters.PublishApplicationBeforeDeployment = true;
if (DeploymentParameters.PublishApplicationBeforeDeployment)
{
DotnetPublish();
contentRoot = DeploymentParameters.PublishedApplicationRootPath;
}
Helpers.AddDebugLogToWebConfig(contentRoot, Path.Combine(contentRoot, $"{_application.WebSiteName}.txt"));
var uri = TestIISUriHelper.BuildTestUri(ServerType.IIS, DeploymentParameters.ApplicationBaseUriHint);
// To prevent modifying the IIS setup concurrently.
await _application.StartIIS(uri, contentRoot);

View File

@ -0,0 +1,17 @@
// 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.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Testing.xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
public sealed class SkipIISAttribute : Attribute, ITestCondition
{
public bool IsMet => DeployerSelector.ServerType == ServerType.IIS;
public string SkipReason => "Cannot run test on full IIS.";
}
}

View File

@ -13,7 +13,7 @@ namespace ConfigUtilityTests
class ConfigUtilityTest : public Test
{
protected:
void Test(std::wstring key, std::wstring value, std::wstring expected)
void TestHandlerVersion(std::wstring key, std::wstring value, std::wstring expected, HRESULT(*func)(IAppHostElement*, STRU&))
{
IAppHostElement* retElement = NULL;
@ -40,7 +40,7 @@ namespace ConfigUtilityTests
.WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(key.c_str())), testing::Return(S_OK)))
.WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(value.c_str())), testing::Return(S_OK)));
HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), &handlerVersion);
HRESULT hr = func(element.get(), handlerVersion);
EXPECT_EQ(hr, S_OK);
EXPECT_STREQ(handlerVersion.QueryStr(), expected.c_str());
@ -49,12 +49,29 @@ namespace ConfigUtilityTests
TEST_F(ConfigUtilityTest, CheckHandlerVersionKeysAndValues)
{
Test(L"handlerVersion", L"value", L"value");
Test(L"handlerversion", L"value", L"value");
Test(L"HandlerversioN", L"value", L"value");
Test(L"randomvalue", L"value", L"");
Test(L"", L"value", L"");
Test(L"", L"", L"");
auto func = ConfigUtility::FindHandlerVersion;
TestHandlerVersion(L"handlerVersion", L"value", L"value", func);
TestHandlerVersion(L"handlerversion", L"value", L"value", func);
TestHandlerVersion(L"HandlerversioN", L"value", L"value", func);
TestHandlerVersion(L"randomvalue", L"value", L"", func);
TestHandlerVersion(L"", L"value", L"", func);
TestHandlerVersion(L"", L"", L"", func);
}
TEST_F(ConfigUtilityTest, CheckDebugLogFile)
{
auto func = ConfigUtility::FindDebugFile;
TestHandlerVersion(L"debugFile", L"value", L"value", func);
TestHandlerVersion(L"debugFILE", L"value", L"value", func);
}
TEST_F(ConfigUtilityTest, CheckDebugLevel)
{
auto func = ConfigUtility::FindDebugLevel;
TestHandlerVersion(L"debugLevel", L"value", L"value", func);
TestHandlerVersion(L"debugLEVEL", L"value", L"value", func);
}
TEST(ConfigUtilityTestSingle, MultipleElements)
@ -84,7 +101,7 @@ namespace ConfigUtilityTests
.WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"handlerVersion")), testing::Return(S_OK)))
.WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"value2")), testing::Return(S_OK)));
HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), &handlerVersion);
HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), handlerVersion);
EXPECT_EQ(hr, S_OK);
EXPECT_STREQ(handlerVersion.QueryStr(), L"value2");
@ -98,7 +115,7 @@ namespace ConfigUtilityTests
ON_CALL(*element, GetElementByName(_, _))
.WillByDefault(DoAll(testing::SetArgPointee<1>(nullptr), testing::Return(HRESULT_FROM_WIN32( ERROR_INVALID_INDEX ))));
HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), &handlerVersion);
HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), handlerVersion);
EXPECT_EQ(hr, S_OK);
EXPECT_STREQ(handlerVersion.QueryStr(), L"");

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false" hostingModel="inprocess">
</aspNetCore>
</system.webServer>
</configuration>