diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 04a6cac6c9..cc06123032 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -233,6 +233,7 @@ cdpx cdpxwin cend CENTERALIGN +cerr cfg Cfg changecursor diff --git a/PowerToys.sln b/PowerToys.sln index 3250682df6..83317057c7 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -271,6 +271,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest-ColorPickerUI", "s EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logging", "src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logger", "src\common\logger\logger.vcxproj", "{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -549,6 +551,10 @@ Global {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64 {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64 {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.ActiveCfg = Debug|x64 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.Build.0 = Debug|x64 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.ActiveCfg = Release|x64 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -628,6 +634,7 @@ Global {4FA206A5-F69F-4193-BF8F-F6EEB496734C} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {090CD7B7-3B0C-4D1D-BC98-83EB5D799BC1} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0} {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F} = {1AFB6476-670D-4E80-A464-657E01DFF482} + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD} = {1AFB6476-670D-4E80-A464-657E01DFF482} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/deps/spdlog.props b/deps/spdlog.props index 921cf9509a..85b16b962c 100644 --- a/deps/spdlog.props +++ b/deps/spdlog.props @@ -3,7 +3,7 @@ $(MSBuildThisFileDirectory)spdlog\include;%(AdditionalIncludeDirectories) - SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;%(PreprocessorDefinitions) + SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions) diff --git a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp index 24a2d54a86..a7ab40cb69 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp +++ b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp @@ -18,6 +18,7 @@ auto Strings = create_notifications_strings(); #define STR_HELPER(x) #x #define STR(x) STR_HELPER(x) + namespace // Strings in this namespace should not be localized { const wchar_t APPLICATION_ID[] = L"PowerToysInstaller"; @@ -61,7 +62,7 @@ void setup_log(fs::path directory, const spdlog::level::level_enum severity) std::shared_ptr logger; if (severity != spdlog::level::off) { - logger = spdlog::basic_logger_mt("file", (directory / LOG_FILENAME).string()); + logger = spdlog::basic_logger_mt("file", (directory / LOG_FILENAME).wstring()); std::error_code _; const DWORD msiSev = severity == spdlog::level::debug ? INSTALLLOGMODE_VERBOSE : INSTALLLOGMODE_ERROR; diff --git a/src/common/logger/framework.h b/src/common/logger/framework.h new file mode 100644 index 0000000000..ce94278880 --- /dev/null +++ b/src/common/logger/framework.h @@ -0,0 +1,3 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers diff --git a/src/common/logger/logger.cpp b/src/common/logger/logger.cpp new file mode 100644 index 0000000000..7fb3e474c3 --- /dev/null +++ b/src/common/logger/logger.cpp @@ -0,0 +1,64 @@ +// logger.cpp : Defines the functions for the static library. +// +#include "pch.h" +#include "framework.h" +#include "logger.h" +#include "logger_settings.h" +#include +#include +#include +#include + +using namespace std; +using namespace spdlog; + +map logLevelMapping = { + { L"trace", level::trace }, + { L"debug", level::debug }, + { L"info", level::info }, + { L"warn", level::warn }, + { L"err", level::err }, + { L"critical", level::critical }, + { L"off", level::off }, +}; + +level::level_enum getLogLevel(std::wstring_view logSettingsPath) +{ + auto logLevel = get_log_settings(logSettingsPath).logLevel; + level::level_enum result = logLevelMapping[LogSettings::defaultLogLevel]; + if (logLevelMapping.find(logLevel) != logLevelMapping.end()) + { + result = logLevelMapping[logLevel]; + } + + return result; +} + +Logger::Logger() +{ +} + +Logger::Logger(std::string loggerName, std::wstring logFilePath, std::wstring_view logSettingsPath) +{ + auto logLevel = getLogLevel(logSettingsPath); + try + { + auto sink = make_shared(logFilePath, 0, 0, false, 5); + this->logger = make_shared(loggerName, sink); + } + catch (...) + { + cerr << "Can not create file logger. Create stdout logger instead" << endl; + this->logger = spdlog::stdout_color_mt("some_unique_name"); + } + + this->logger->set_level(logLevel); + this->logger->set_pattern("[%Y-%m-%d %H:%M:%S.%f] [p-%P] [t-%t] [%l] %v"); + spdlog::register_logger(this->logger); + spdlog::flush_every(std::chrono::seconds(3)); +} + +Logger::~Logger() +{ + this->logger.reset(); +} diff --git a/src/common/logger/logger.h b/src/common/logger/logger.h new file mode 100644 index 0000000000..9a90d391c1 --- /dev/null +++ b/src/common/logger/logger.h @@ -0,0 +1,50 @@ +#pragma once +#include + +class Logger +{ +private: + std::shared_ptr logger; + +public: + Logger(); + Logger(std::string loggerName, std::wstring logFilePath, std::wstring_view logSettingsPath); + + template + void trace(const FormatString& fmt, const Args&... args) + { + this->logger->trace(fmt, args...); + } + + template + void debug(const FormatString& fmt, const Args&... args) + { + this->logger->debug(fmt, args...); + } + + template + void info(const FormatString& fmt, const Args&... args) + { + this->logger->info(fmt, args...); + } + + template + void warn(const FormatString& fmt, const Args&... args) + { + this->logger->warn(fmt, args...); + } + + template + void error(const FormatString& fmt, const Args&... args) + { + this->logger->error(fmt, args...); + } + + template + void critical(const FormatString& fmt, const Args&... args) + { + this->logger->critical(fmt, args...); + } + + ~Logger(); +}; diff --git a/src/common/logger/logger.vcxproj b/src/common/logger/logger.vcxproj new file mode 100644 index 0000000000..c69e5ea889 --- /dev/null +++ b/src/common/logger/logger.vcxproj @@ -0,0 +1,123 @@ + + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + logger + 10.0.17134.0 + + + + StaticLibrary + true + v142 + Unicode + + + StaticLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + true + + + false + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + MultiThreadedDebug + true + + + + + true + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + MultiThreaded + true + + + + + true + true + true + + + + + + + + + + + + + Create + Create + + + + + + + + {7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f} + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/common/logger/logger.vcxproj.filters b/src/common/logger/logger.vcxproj.filters new file mode 100644 index 0000000000..cc30326a76 --- /dev/null +++ b/src/common/logger/logger.vcxproj.filters @@ -0,0 +1,45 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + + \ No newline at end of file diff --git a/src/common/logger/logger_settings.cpp b/src/common/logger/logger_settings.cpp new file mode 100644 index 0000000000..34dfa87d72 --- /dev/null +++ b/src/common/logger/logger_settings.cpp @@ -0,0 +1,83 @@ +#include "pch.h" +#include "logger_settings.h" +#include +#include +#include +#include + +using namespace winrt::Windows::Data::Json; + +LogSettings::LogSettings() +{ + this->logLevel = LogSettings::defaultLogLevel; +} + +std::optional from_file(std::wstring_view file_name) +{ + try + { + std::ifstream file(file_name.data(), std::ios::binary); + if (file.is_open()) + { + using isbi = std::istreambuf_iterator; + std::string obj_str{ isbi{ file }, isbi{} }; + return JsonValue::Parse(winrt::to_hstring(obj_str)).GetObjectW(); + } + return std::nullopt; + } + catch (...) + { + return std::nullopt; + } +} + +void to_file(std::wstring_view file_name, const JsonObject& obj) +{ + std::wstring obj_str{ obj.Stringify().c_str() }; + try + { + std::ofstream{ file_name.data(), std::ios::binary } << winrt::to_string(obj_str); + } + catch (...) + { + std::cerr << "Can not create log config file" << std::endl; + } +} + +JsonObject to_json(LogSettings settings) +{ + JsonObject result; + result.SetNamedValue(LogSettings::logLevelOption, JsonValue::CreateStringValue(settings.logLevel)); + + return result; +} + +LogSettings to_settings(JsonObject jobject) +{ + LogSettings result; + try + { + result.logLevel = jobject.GetNamedString(LogSettings::logLevelOption); + } + catch (...) + { + std::cerr << "Can not read log level from config file" << std::endl; + result.logLevel = LogSettings::defaultLogLevel; + } + + return result; +} + +// Get log settings from file. File with default options is created if it does not exist +LogSettings get_log_settings(std::wstring_view file_name) +{ + auto jobject = from_file(file_name); + if (!jobject.has_value()) + { + auto json = to_json(LogSettings()); + to_file(file_name, json); + return to_settings(json); + } + + return to_settings(jobject.value()); +} diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h new file mode 100644 index 0000000000..c4e071b183 --- /dev/null +++ b/src/common/logger/logger_settings.h @@ -0,0 +1,15 @@ +#pragma once +#include + +struct LogSettings +{ + // The following strings are not localizable + inline const static std::wstring defaultLogLevel = L"warn"; + inline const static std::wstring logLevelOption = L"logLevel"; + + std::wstring logLevel; + LogSettings(); +}; + +// Get log settings from file. File with default options is created if it does not exist +LogSettings get_log_settings(std::wstring_view file_name); diff --git a/src/common/logger/packages.config b/src/common/logger/packages.config new file mode 100644 index 0000000000..81f107b8bc --- /dev/null +++ b/src/common/logger/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/common/logger/pch.cpp b/src/common/logger/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/common/logger/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/common/logger/pch.h b/src/common/logger/pch.h new file mode 100644 index 0000000000..885d5d62e4 --- /dev/null +++ b/src/common/logger/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#endif //PCH_H diff --git a/src/common/settings_helpers.cpp b/src/common/settings_helpers.cpp index 35fe5827de..8250fabc15 100644 --- a/src/common/settings_helpers.cpp +++ b/src/common/settings_helpers.cpp @@ -6,6 +6,7 @@ namespace PTSettingsHelper { constexpr inline const wchar_t* settings_filename = L"\\settings.json"; + constexpr inline const wchar_t* log_settings_filename = L"log_settings.json"; std::wstring get_root_save_folder_location() { @@ -71,4 +72,11 @@ namespace PTSettingsHelper auto saved_settings = json::from_file(save_file_location); return saved_settings.has_value() ? std::move(*saved_settings) : json::JsonObject{}; } + + std::wstring get_log_settings_file_location() + { + std::filesystem::path result(PTSettingsHelper::get_root_save_folder_location()); + result = result.append(log_settings_filename); + return result.wstring(); + } } diff --git a/src/common/settings_helpers.h b/src/common/settings_helpers.h index 754ab0237f..9e8c94e392 100644 --- a/src/common/settings_helpers.h +++ b/src/common/settings_helpers.h @@ -13,5 +13,5 @@ namespace PTSettingsHelper json::JsonObject load_module_settings(std::wstring_view powertoy_name); void save_general_settings(const json::JsonObject& settings); json::JsonObject load_general_settings(); - + std::wstring get_log_settings_file_location(); } diff --git a/src/logging/logging.vcxproj b/src/logging/logging.vcxproj index 6c283eab81..34550c82fc 100644 --- a/src/logging/logging.vcxproj +++ b/src/logging/logging.vcxproj @@ -66,7 +66,7 @@ true false Level4 - WIN32;_WINDOWS;SPDLOG_COMPILED_LIB;%(PreprocessorDefinitions) + WIN32;_WINDOWS;SPDLOG_COMPILED_LIB;SPDLOG_WCHAR_FILENAMES;%(PreprocessorDefinitions) $(IntDir) true true diff --git a/src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj b/src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj index 2b36ca613f..004757675f 100644 --- a/src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj +++ b/src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj @@ -120,6 +120,9 @@ {74485049-c722-400f-abe5-86ac52d929b3} + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + @@ -132,6 +135,7 @@ + diff --git a/src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj.filters b/src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj.filters index d64a5f3fdf..c6ceeb2f27 100644 --- a/src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj.filters +++ b/src/modules/launcher/Microsoft.Launcher/Microsoft.Launcher.vcxproj.filters @@ -11,6 +11,7 @@ Generated Files + diff --git a/src/modules/launcher/Microsoft.Launcher/dllmain.cpp b/src/modules/launcher/Microsoft.Launcher/dllmain.cpp index 8bb79a4c2a..2c5bfe1ad1 100644 --- a/src/modules/launcher/Microsoft.Launcher/dllmain.cpp +++ b/src/modules/launcher/Microsoft.Launcher/dllmain.cpp @@ -7,6 +7,9 @@ #include "Generated Files/resource.h" #include #include +#include +#include +#include extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -75,12 +78,18 @@ private: // Handle to event used to invoke the Runner HANDLE m_hEvent; + std::shared_ptr logger; + public: // Constructor Microsoft_Launcher() { app_name = GET_RESOURCE_STRING(IDS_LAUNCHER_NAME); app_key = LauncherConstants::ModuleKey; + std::filesystem::path logFilePath(PTSettingsHelper::get_module_save_folder_location(this->app_key)); + logFilePath.append("logging.txt"); + logger = std::make_shared("launcher", logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + logger->info("Launcher object is constructing"); init_settings(); SECURITY_ATTRIBUTES sa; @@ -92,6 +101,8 @@ public: ~Microsoft_Launcher() { + logger->info("Launcher object is destroying"); + logger.reset(); if (m_enabled) { terminateProcess(); @@ -172,6 +183,7 @@ public: // Enable the powertoy virtual void enable() { + this->logger->info("Launcher is enabling"); ResetEvent(m_hEvent); // Start PowerLauncher.exe only if the OS is 19H1 or higher if (UseNewSettings()) @@ -243,6 +255,7 @@ public: // Disable the powertoy virtual void disable() { + this->logger->info("Launcher is disabling"); if (m_enabled) { ResetEvent(m_hEvent); @@ -311,7 +324,12 @@ public: void terminateProcess() { DWORD processID = GetProcessId(m_hProcess); - TerminateProcess(m_hProcess, 1); + if (TerminateProcess(m_hProcess, 1) == 0) + { + auto err = get_last_error_message(GetLastError()); + this->logger->error(L"Launcher process was not terminated. {}", err.has_value() ? err.value() : L""); + } + // Temporarily disable sending a message to close /* EnumWindows(&requestMainWindowClose, processID); diff --git a/src/runner/general_settings.h b/src/runner/general_settings.h index c0f3ecfc1e..785e7271d1 100644 --- a/src/runner/general_settings.h +++ b/src/runner/general_settings.h @@ -22,4 +22,4 @@ struct GeneralSettings json::JsonObject load_general_settings(); GeneralSettings get_general_settings(); void apply_general_settings(const json::JsonObject& general_configs, bool save = true); -void start_initial_powertoys(); +void start_initial_powertoys(); \ No newline at end of file diff --git a/src/runner/main.cpp b/src/runner/main.cpp index d68a1fec1d..ebfe9347e4 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -33,6 +33,8 @@ #if _DEBUG && _WIN64 #include "unhandled_exception_handler.h" #endif +#include +#include extern "C" IMAGE_DOS_HEADER __ImageBase; extern updating::notifications::strings Strings; @@ -43,6 +45,7 @@ namespace const wchar_t POWER_TOYS_MODULE_LOAD_FAIL[] = L"Failed to load "; // Module name will be appended on this message and it is not localized. } +std::shared_ptr logger; void chdir_current_executable() { // Change current directory to the path of the executable. @@ -73,6 +76,7 @@ void open_menu_from_another_instance() int runner(bool isProcessElevated) { + logger->info("Runner is starting. Elevated={}", isProcessElevated); DPIAware::EnableDPIAwarenessForThisProcess(); #if _DEBUG && _WIN64 @@ -290,6 +294,11 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine { return 0; } + + std::filesystem::path logFilePath(PTSettingsHelper::get_root_save_folder_location()); + logFilePath = logFilePath.append(L"runner-logging.txt"); + logger = std::make_shared("runner", logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + int n_cmd_args = 0; LPWSTR* cmd_arg_list = CommandLineToArgvW(GetCommandLineW(), &n_cmd_args); switch (should_run_in_special_mode(n_cmd_args, cmd_arg_list)) diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj index 29d6aa0c72..a9f847d91e 100644 --- a/src/runner/runner.vcxproj +++ b/src/runner/runner.vcxproj @@ -238,6 +238,9 @@ {74485049-c722-400f-abe5-86ac52d929b3} + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + {17da04df-e393-4397-9cf0-84dabe11032e} @@ -249,6 +252,7 @@ + diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 439e2cdf08..670949071d 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -244,7 +244,6 @@ BOOL run_settings_non_elevated(LPCWSTR executable_path, LPWSTR executable_args, return process_created; } - DWORD g_settings_process_id = 0; void run_settings_window()