diff options
author | montellese <montellese@kodi.tv> | 2020-03-14 10:59:34 +0100 |
---|---|---|
committer | montellese <montellese@kodi.tv> | 2020-04-16 20:05:39 +0200 |
commit | 91e7c78eb4cef1a60ce384c09403541fba0c74b0 (patch) | |
tree | 9ff14a84c2784bf5a0cff702020a473382093106 | |
parent | b97635702e3ddcaa8a27178d320cd2aadf147d8a (diff) |
[utils] CLog: replace custom logging implementation with spdlog
30 files changed, 599 insertions, 515 deletions
diff --git a/cmake/treedata/android/subdirs.txt b/cmake/treedata/android/subdirs.txt index 57c89ea843..0c4cf88360 100644 --- a/cmake/treedata/android/subdirs.txt +++ b/cmake/treedata/android/subdirs.txt @@ -13,6 +13,7 @@ xbmc/platform/android/network platform/android/network xbmc/platform/android/peripherals platform/android/peripherals xbmc/platform/android/powermanagement platform/android/powermanagement xbmc/platform/android/storage platform/android/storage +xbmc/platform/android/utils platform/android/utils xbmc/platform/linux/peripherals platform/linux/peripherals xbmc/platform/posix platform/posix xbmc/platform/posix/filesystem platform/posix/filesystem diff --git a/cmake/treedata/darwin_embedded/subdirs.txt b/cmake/treedata/darwin_embedded/subdirs.txt index d2f500ed15..be1dfc8d1b 100644 --- a/cmake/treedata/darwin_embedded/subdirs.txt +++ b/cmake/treedata/darwin_embedded/subdirs.txt @@ -7,6 +7,7 @@ xbmc/platform/darwin/ios-common platform/ios-common xbmc/platform/darwin/ios-common/network platform/ios-common/network xbmc/platform/darwin/ios-common/storage platform/ios-common/storage xbmc/platform/darwin/network platform/darwin/network +xbmc/platform/darwin/utils platform/darwin/utils xbmc/platform/posix posix xbmc/platform/posix/filesystem platform/posix/filesystem xbmc/platform/posix/utils platform/posix/utils diff --git a/cmake/treedata/osx/subdirs.txt b/cmake/treedata/osx/subdirs.txt index f4785abbf1..fa28aa0efb 100644 --- a/cmake/treedata/osx/subdirs.txt +++ b/cmake/treedata/osx/subdirs.txt @@ -7,6 +7,7 @@ xbmc/platform/darwin/osx/network platform/darwin/osx/network xbmc/platform/darwin/osx/peripherals platform/osx/peripherals xbmc/platform/darwin/osx/powermanagement platform/darwin/osx/powermanagement xbmc/platform/darwin/osx/storage platform/osx/storage +xbmc/platform/darwin/utils platform/darwin/utils xbmc/platform/posix posix xbmc/platform/posix/filesystem platform/posix/filesystem xbmc/platform/posix/network platform/posix/network diff --git a/xbmc/AppParamParser.cpp b/xbmc/AppParamParser.cpp index 6660b0a776..60ff9d821a 100644 --- a/xbmc/AppParamParser.cpp +++ b/xbmc/AppParamParser.cpp @@ -97,7 +97,7 @@ void CAppParamParser::SetAdvancedSettings(CAdvancedSettings& advancedSettings) c { advancedSettings.m_logLevel = LOG_LEVEL_DEBUG; advancedSettings.m_logLevelHint = LOG_LEVEL_DEBUG; - CLog::SetLogLevel(LOG_LEVEL_DEBUG); + CServiceBroker::GetLogging().SetLogLevel(LOG_LEVEL_DEBUG); } if (!m_settingsFile.empty()) diff --git a/xbmc/Application.cpp b/xbmc/Application.cpp index 5cb12e347c..5539c8e859 100644 --- a/xbmc/Application.cpp +++ b/xbmc/Application.cpp @@ -363,6 +363,8 @@ bool CApplication::Create(const CAppParamParser ¶ms) m_bTestMode = params.m_testmode; m_bStandalone = params.m_standAlone; + CServiceBroker::CreateLogging(); + CServiceBroker::RegisterCPUInfo(CCPUInfo::GetCPUInfo()); m_pSettingsComponent.reset(new CSettingsComponent()); @@ -420,11 +422,7 @@ bool CApplication::Create(const CAppParamParser ¶ms) CopyUserDataIfNeeded("special://masterprofile/", "iOS/sources.xml", "sources.xml"); #endif - if (!CLog::Init(CSpecialProtocol::TranslatePath("special://logpath").c_str())) - { - fprintf(stderr,"Could not init logging classes. Log folder error (%s)\n", CSpecialProtocol::TranslatePath("special://logpath").c_str()); - return false; - } + CServiceBroker::GetLogging().Initialize(CSpecialProtocol::TranslatePath("special://logpath")); #ifdef TARGET_POSIX //! @todo Win32 has no special://home/ mapping by default, so we //! must create these here. Ideally this should be using special://home/ and diff --git a/xbmc/ServiceBroker.cpp b/xbmc/ServiceBroker.cpp index 5d60cf8d23..a23061a45c 100644 --- a/xbmc/ServiceBroker.cpp +++ b/xbmc/ServiceBroker.cpp @@ -11,10 +11,22 @@ #include "Application.h" #include "profiles/ProfileManager.h" #include "settings/SettingsComponent.h" +#include "utils/log.h" #include "windowing/WinSystem.h" using namespace KODI; +std::unique_ptr<CLog> CServiceBroker::m_logging; +CLog& CServiceBroker::GetLogging() +{ + return *m_logging; +} + +void CServiceBroker::CreateLogging() +{ + m_logging = std::make_unique<CLog>(); +} + // announcement std::shared_ptr<ANNOUNCEMENT::CAnnouncementManager> CServiceBroker::m_pAnnouncementManager; std::shared_ptr<ANNOUNCEMENT::CAnnouncementManager> CServiceBroker::GetAnnouncementManager() diff --git a/xbmc/ServiceBroker.h b/xbmc/ServiceBroker.h index 8f5ca91c81..8a7844da95 100644 --- a/xbmc/ServiceBroker.h +++ b/xbmc/ServiceBroker.h @@ -55,6 +55,7 @@ class CSettingsComponent; class CDecoderFilterManager; class CMediaManager; class CCPUInfo; +class CLog; namespace KODI { @@ -78,6 +79,9 @@ namespace PERIPHERALS class CServiceBroker { public: + static CLog& GetLogging(); + static void CreateLogging(); + static std::shared_ptr<ANNOUNCEMENT::CAnnouncementManager> GetAnnouncementManager(); static void RegisterAnnouncementManager(std::shared_ptr<ANNOUNCEMENT::CAnnouncementManager> announcementManager); static void UnregisterAnnouncementManager(); @@ -139,6 +143,7 @@ public: static void UnregisterCPUInfo(); private: + static std::unique_ptr<CLog> m_logging; static std::shared_ptr<ANNOUNCEMENT::CAnnouncementManager> m_pAnnouncementManager; static CGUIComponent* m_pGUI; static CWinSystemBase* m_pWinSystem; diff --git a/xbmc/peripherals/devices/PeripheralCecAdapter.cpp b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp index 039acb10af..1505aaf73f 100644 --- a/xbmc/peripherals/devices/PeripheralCecAdapter.cpp +++ b/xbmc/peripherals/devices/PeripheralCecAdapter.cpp @@ -1206,7 +1206,7 @@ void CPeripheralCecAdapter::CecLogMessage(void *cbParam, const cec_log_message* break; } - if (iLevel >= CEC_LOG_NOTICE || (iLevel >= 0 && CLog::IsLogLevelLogged(LOGDEBUG))) + if (iLevel >= CEC_LOG_NOTICE || (iLevel >= 0 && CServiceBroker::GetLogging().IsLogLevelLogged(LOGDEBUG))) CLog::Log(iLevel, LOGCEC, "%s - %s", __FUNCTION__, message->message); } diff --git a/xbmc/platform/android/utils/AndroidInterfaceForCLog.cpp b/xbmc/platform/android/utils/AndroidInterfaceForCLog.cpp new file mode 100644 index 0000000000..62644a5cff --- /dev/null +++ b/xbmc/platform/android/utils/AndroidInterfaceForCLog.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "platform/android/utils/AndroidInterfaceForCLog.h" + +#include "CompileInfo.h" + +#include <spdlog/sinks/android_sink.h> +#include <spdlog/sinks/dist_sink.h> + +std::unique_ptr<IPlatformLog> IPlatformLog::CreatePlatformLog() +{ + return std::make_unique<CAndroidInterfaceForCLog>(); +} + +void CAndroidInterfaceForCLog::AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const +{ + distributionSink->add_sink( + std::make_shared<spdlog::sinks::android_sink_mt>(CCompileInfo::GetAppName())); +} diff --git a/xbmc/platform/android/utils/AndroidInterfaceForCLog.h b/xbmc/platform/android/utils/AndroidInterfaceForCLog.h new file mode 100644 index 0000000000..eabc204e79 --- /dev/null +++ b/xbmc/platform/android/utils/AndroidInterfaceForCLog.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/posix/utils/PosixInterfaceForCLog.h" + +class CAndroidInterfaceForCLog : public CPosixInterfaceForCLog +{ +public: + CAndroidInterfaceForCLog() = default; + ~CAndroidInterfaceForCLog() = default; + + // specialization of CPosixInterfaceForCLog + void AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const override; +}; diff --git a/xbmc/platform/android/utils/CMakeLists.txt b/xbmc/platform/android/utils/CMakeLists.txt new file mode 100644 index 0000000000..c5f53082c3 --- /dev/null +++ b/xbmc/platform/android/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES AndroidInterfaceForCLog.cpp) + +set(HEADERS AndroidInterfaceForCLog.h) + +core_add_library(platform_android_utils) diff --git a/xbmc/platform/darwin/DarwinUtils.h b/xbmc/platform/darwin/DarwinUtils.h index b13e596b24..4f3db3557d 100644 --- a/xbmc/platform/darwin/DarwinUtils.h +++ b/xbmc/platform/darwin/DarwinUtils.h @@ -27,7 +27,6 @@ public: static const char *GetAppRootFolder(void); static bool IsIosSandboxed(void); static void SetScheduling(bool realtime); - static void PrintDebugString(std::string debugString); static bool CFStringRefToString(CFStringRef source, std::string& destination); static bool CFStringRefToUTF8String(CFStringRef source, std::string& destination); static const std::string& GetManufacturer(void); diff --git a/xbmc/platform/darwin/DarwinUtils.mm b/xbmc/platform/darwin/DarwinUtils.mm index 08e6603ca2..8f9cd57e19 100644 --- a/xbmc/platform/darwin/DarwinUtils.mm +++ b/xbmc/platform/darwin/DarwinUtils.mm @@ -245,11 +245,6 @@ bool CFStringRefToStringWithEncoding(CFStringRef source, std::string &destinatio return true; } -void CDarwinUtils::PrintDebugString(std::string debugString) -{ - NSLog(@"Debug Print: %s", debugString.c_str()); -} - bool CDarwinUtils::CFStringRefToString(CFStringRef source, std::string &destination) { diff --git a/xbmc/platform/darwin/tvos/XBMCController.mm b/xbmc/platform/darwin/tvos/XBMCController.mm index d6381513f7..588db3ee9c 100644 --- a/xbmc/platform/darwin/tvos/XBMCController.mm +++ b/xbmc/platform/darwin/tvos/XBMCController.mm @@ -421,7 +421,7 @@ int KODI_Run(bool renderGUI) CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel = LOG_LEVEL_NORMAL; CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevelHint = LOG_LEVEL_NORMAL; #endif - CLog::SetLogLevel(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel); + CServiceBroker::GetLogging().SetLogLevel(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel); // not a failure if returns false, just means someone // did the init before us. diff --git a/xbmc/platform/darwin/utils/CMakeLists.txt b/xbmc/platform/darwin/utils/CMakeLists.txt new file mode 100644 index 0000000000..c2717d595c --- /dev/null +++ b/xbmc/platform/darwin/utils/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES DarwinInterfaceForCLog.mm) + +set(HEADERS DarwinInterfaceForCLog.h) + +core_add_library(platform_darwin_utils) diff --git a/xbmc/platform/darwin/utils/DarwinInterfaceForCLog.h b/xbmc/platform/darwin/utils/DarwinInterfaceForCLog.h new file mode 100644 index 0000000000..b6bf541e32 --- /dev/null +++ b/xbmc/platform/darwin/utils/DarwinInterfaceForCLog.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/posix/utils/PosixInterfaceForCLog.h" + +#include <memory> +#include <mutex> + +#include <spdlog/formatter.h> +#include <spdlog/sinks/sink.h> + +class CDarwinInterfaceForCLog : public CPosixInterfaceForCLog, public spdlog::sinks::sink +{ +public: + CDarwinInterfaceForCLog(); + ~CDarwinInterfaceForCLog() = default; + + // specialization of CPosixInterfaceForCLog + void AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const override; + + // implementations of spdlog::sink + void log(const spdlog::details::log_msg& msg) override; + void flush() override; + void set_pattern(const std::string& pattern) override; + void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; + +private: + std::unique_ptr<spdlog::formatter> m_formatter; + std::mutex m_mutex; +}; diff --git a/xbmc/platform/darwin/utils/DarwinInterfaceForCLog.mm b/xbmc/platform/darwin/utils/DarwinInterfaceForCLog.mm new file mode 100644 index 0000000000..0d126af932 --- /dev/null +++ b/xbmc/platform/darwin/utils/DarwinInterfaceForCLog.mm @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DarwinInterfaceForCLog.h" + +#include <string> + +#import <Foundation/Foundation.h> +#include <spdlog/common.h> +#include <spdlog/details/pattern_formatter.h> +#include <spdlog/sinks/dist_sink.h> + +std::unique_ptr<IPlatformLog> IPlatformLog::CreatePlatformLog() +{ + return std::make_unique<CDarwinInterfaceForCLog>(); +} + +CDarwinInterfaceForCLog::CDarwinInterfaceForCLog() + : m_formatter(std::make_unique<spdlog::pattern_formatter>()) +{ +} + +void CDarwinInterfaceForCLog::AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const +{ + distributionSink->add_sink(std::make_shared<CDarwinInterfaceForCLog>()); +} + +void CDarwinInterfaceForCLog::log(const spdlog::details::log_msg& msg) +{ + std::lock_guard<std::mutex> lock(m_mutex); + spdlog::memory_buf_t formatted; + m_formatter->format(msg, formatted); + formatted.push_back('\0'); + NSLog(@"%s", formatted.data()); +} + +void CDarwinInterfaceForCLog::flush() +{ + std::lock_guard<std::mutex> lock(m_mutex); + fflush(stderr); +} + +void CDarwinInterfaceForCLog::set_pattern(const std::string& pattern) +{ + set_formatter(std::make_unique<spdlog::pattern_formatter>(pattern)); +} + +void CDarwinInterfaceForCLog::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) +{ + std::lock_guard<std::mutex> lock(m_mutex); + m_formatter = std::move(sink_formatter); +} diff --git a/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp b/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp index ce4c5aa5b5..68c1ddb5ef 100644 --- a/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp +++ b/xbmc/platform/posix/utils/PosixInterfaceForCLog.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2018 Team Kodi + * Copyright (C) 2020 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later @@ -7,100 +7,10 @@ */ #include "PosixInterfaceForCLog.h" -#include <stdio.h> -#include <time.h> -#include <sys/time.h> -#if defined(TARGET_DARWIN) -#include "platform/darwin/DarwinUtils.h" -#elif defined(TARGET_ANDROID) -#include "platform/android/activity/XBMCApp.h" -#endif // TARGET_ANDROID - -struct FILEWRAP : public FILE -{}; - - -CPosixInterfaceForCLog::CPosixInterfaceForCLog() : - m_file(NULL) -{ } - -CPosixInterfaceForCLog::~CPosixInterfaceForCLog() -{ - if (m_file) - fclose(m_file); - m_file = NULL; -} - -bool CPosixInterfaceForCLog::OpenLogFile(const std::string &logFilename, const std::string &backupOldLogToFilename) -{ - if (m_file) - return false; // file was already opened - - (void)remove(backupOldLogToFilename.c_str()); // if it's failed, try to continue - (void)rename(logFilename.c_str(), backupOldLogToFilename.c_str()); // if it's failed, try to continue - - m_file = (FILEWRAP*)fopen(logFilename.c_str(), "wb"); - if (!m_file) - return false; // error, can't open log file - - static const unsigned char BOM[3] = { 0xEF, 0xBB, 0xBF }; - (void)fwrite(BOM, sizeof(BOM), 1, m_file); // write BOM, ignore possible errors - - return true; -} - -void CPosixInterfaceForCLog::CloseLogFile() -{ - if (m_file) - { - fclose(m_file); - m_file = NULL; - } -} - -bool CPosixInterfaceForCLog::WriteStringToLog(const std::string &logString) +#if !defined(TARGET_ANDROID) && !defined(TARGET_DARWIN) +std::unique_ptr<IPlatformLog> IPlatformLog::CreatePlatformLog() { - if (!m_file) - return false; - - const bool ret = (fwrite(logString.data(), logString.size(), 1, m_file) == 1) && - (fwrite("\n", 1, 1, m_file) == 1); - (void)fflush(m_file); - - return ret; -} - -void CPosixInterfaceForCLog::PrintDebugString(const std::string &debugString) -{ -#ifdef _DEBUG -#if defined(TARGET_DARWIN) - CDarwinUtils::PrintDebugString(debugString); -#elif defined(TARGET_ANDROID) - //print to adb - CXBMCApp::android_printf("Debug Print: %s", debugString.c_str()); -#endif // TARGET_ANDROID -#endif // _DEBUG -} - -void CPosixInterfaceForCLog::GetCurrentLocalTime(int& year, int& month, int& day, int &hour, int& minute, int& second, double& milliseconds) -{ - struct tm localTime; - struct timeval tv; - - if (gettimeofday(&tv, nullptr) != -1 && localtime_r(&tv.tv_sec, &localTime) != NULL) - { - year = localTime.tm_year + 1900; - month = localTime.tm_mon + 1; - day = localTime.tm_mday; - hour = localTime.tm_hour; - minute = localTime.tm_min; - second = localTime.tm_sec; - milliseconds = static_cast<double>(tv.tv_usec) / 1000; - } - else - { - year = month = day = hour = minute = second = 0; - milliseconds = 0.0; - } + return std::make_unique<CPosixInterfaceForCLog>(); } +#endif diff --git a/xbmc/platform/posix/utils/PosixInterfaceForCLog.h b/xbmc/platform/posix/utils/PosixInterfaceForCLog.h index 4e0f6c6646..48a016efb2 100644 --- a/xbmc/platform/posix/utils/PosixInterfaceForCLog.h +++ b/xbmc/platform/posix/utils/PosixInterfaceForCLog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2018 Team Kodi + * Copyright (C) 2020 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later @@ -8,20 +8,17 @@ #pragma once -#include <string> +#include "utils/IPlatformLog.h" -struct FILEWRAP; // forward declaration, wrapper for FILE - -class CPosixInterfaceForCLog +class CPosixInterfaceForCLog : public IPlatformLog { public: - CPosixInterfaceForCLog(); - ~CPosixInterfaceForCLog(); - bool OpenLogFile(const std::string& logFilename, const std::string& backupOldLogToFilename); - void CloseLogFile(void); - bool WriteStringToLog(const std::string& logString); - void PrintDebugString(const std::string& debugString); - static void GetCurrentLocalTime(int& year, int& month, int& day, int& hour, int& minute, int& second, double& millisecond); -private: - FILEWRAP* m_file; + CPosixInterfaceForCLog() = default; + virtual ~CPosixInterfaceForCLog() = default; + + spdlog_filename_t GetLogFilename(const std::string& filename) const override { return filename; } + void AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const override + { + } }; diff --git a/xbmc/platform/win32/utils/Win32InterfaceForCLog.cpp b/xbmc/platform/win32/utils/Win32InterfaceForCLog.cpp index 9eea2d0f49..3c055dafa0 100644 --- a/xbmc/platform/win32/utils/Win32InterfaceForCLog.cpp +++ b/xbmc/platform/win32/utils/Win32InterfaceForCLog.cpp @@ -1,119 +1,34 @@ /* - * Copyright (C) 2014-2018 Team Kodi + * Copyright (C) 2020 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ -#ifndef TARGET_WINDOWS +#if !defined(TARGET_WINDOWS) && !defined(TARGET_WIN10) #error This file is for win32 platforms only -#endif //!TARGET_WINDOWS +#endif // !defined(TARGET_WINDOWS) && !defined(TARGET_WIN10) #include "Win32InterfaceForCLog.h" -#include "platform/win32/WIN32Util.h" -#include "utils/StringUtils.h" -#include "utils/auto_buffer.h" - -#include <Windows.h> - -CWin32InterfaceForCLog::CWin32InterfaceForCLog() : - m_hFile(INVALID_HANDLE_VALUE) -{ } - -CWin32InterfaceForCLog::~CWin32InterfaceForCLog() -{ - if (m_hFile != INVALID_HANDLE_VALUE) - CloseHandle(m_hFile); -} - -bool CWin32InterfaceForCLog::OpenLogFile(const std::string& logFilename, const std::string& backupOldLogToFilename) -{ - if (m_hFile != INVALID_HANDLE_VALUE) - return false; // file was already opened - - std::wstring strLogFileW(CWIN32Util::ConvertPathToWin32Form(CWIN32Util::SmbToUnc(logFilename))); - std::wstring strLogFileOldW(CWIN32Util::ConvertPathToWin32Form(CWIN32Util::SmbToUnc(backupOldLogToFilename))); - - if (strLogFileW.empty()) - return false; - - if (!strLogFileOldW.empty()) - { - (void)DeleteFileW(strLogFileOldW.c_str()); // if it's failed, try to continue -#ifdef TARGET_WINDOWS_STORE - (void)MoveFileEx(strLogFileW.c_str(), strLogFileOldW.c_str(), MOVEFILE_REPLACE_EXISTING); // if it's failed, try to continue -#else - (void)MoveFileW(strLogFileW.c_str(), strLogFileOldW.c_str()); // if it's failed, try to continue -#endif - } -#ifdef TARGET_WINDOWS_STORE - m_hFile = CreateFile2(strLogFileW.c_str(), GENERIC_WRITE, FILE_SHARE_READ, - CREATE_ALWAYS, NULL); -#else - m_hFile = CreateFileW(strLogFileW.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); -#endif - - if (m_hFile == INVALID_HANDLE_VALUE) - return false; - - static const unsigned char BOM[3] = { 0xEF, 0xBB, 0xBF }; - DWORD written; - (void)WriteFile(m_hFile, BOM, sizeof(BOM), &written, NULL); // write BOM, ignore possible errors - (void)FlushFileBuffers(m_hFile); - - return true; -} +#include "platform/win32/WIN32Util.h" -void CWin32InterfaceForCLog::CloseLogFile(void) -{ - if (m_hFile != INVALID_HANDLE_VALUE) - { - CloseHandle(m_hFile); - m_hFile = INVALID_HANDLE_VALUE; - } -} +#include <spdlog/sinks/dist_sink.h> +#include <spdlog/sinks/msvc_sink.h> -bool CWin32InterfaceForCLog::WriteStringToLog(const std::string& logString) +std::unique_ptr<IPlatformLog> IPlatformLog::CreatePlatformLog() { - if (m_hFile == INVALID_HANDLE_VALUE) - return false; - - std::string strData(logString); - StringUtils::Replace(strData, "\n", "\r\n"); - strData += "\r\n"; - - DWORD written; - const bool ret = (WriteFile(m_hFile, strData.c_str(), strData.length(), &written, NULL) != 0) && written == strData.length(); - - return ret; + return std::make_unique<CWin32InterfaceForCLog>(); } -void CWin32InterfaceForCLog::PrintDebugString(const std::string& debugString) +spdlog_filename_t CWin32InterfaceForCLog::GetLogFilename(const std::string& filename) const { -#ifdef _DEBUG - ::OutputDebugStringW(L"Debug Print: "); - int bufSize = MultiByteToWideChar(CP_UTF8, 0, debugString.c_str(), debugString.length(), NULL, 0); - XUTILS::auto_buffer buf(sizeof(wchar_t) * (bufSize + 1)); // '+1' for extra safety - if (MultiByteToWideChar(CP_UTF8, 0, debugString.c_str(), debugString.length(), (wchar_t*)buf.get(), buf.size() / sizeof(wchar_t)) == bufSize) - ::OutputDebugStringW(std::wstring((wchar_t*)buf.get(), bufSize).c_str()); - else - ::OutputDebugStringA(debugString.c_str()); - ::OutputDebugStringW(L"\n"); -#endif // _DEBUG + return CWIN32Util::ConvertPathToWin32Form(CWIN32Util::SmbToUnc(filename)); } -void CWin32InterfaceForCLog::GetCurrentLocalTime(int& year, int& month, int& day, int& hour, int& minute, int& second, double& millisecond) +void CWin32InterfaceForCLog::AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const { - KODI::TIME::SystemTime time; - GetLocalTime(&time); - year = time.year; - month = time.month; - day = time.day; - hour = time.hour; - minute = time.minute; - second = time.second; - millisecond = static_cast<double>(time.milliseconds); + distributionSink->add_sink(std::make_shared<spdlog::sinks::msvc_sink_mt>()); } diff --git a/xbmc/platform/win32/utils/Win32InterfaceForCLog.h b/xbmc/platform/win32/utils/Win32InterfaceForCLog.h index 6baf61a316..1c3b3d8ea5 100644 --- a/xbmc/platform/win32/utils/Win32InterfaceForCLog.h +++ b/xbmc/platform/win32/utils/Win32InterfaceForCLog.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2018 Team Kodi + * Copyright (C) 2020 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later @@ -8,20 +8,19 @@ #pragma once -#include <string> +#if !defined(TARGET_WINDOWS) && !defined(TARGET_WIN10) +#error This file is for win32 platforms only +#endif // !defined(TARGET_WINDOWS) && !defined(TARGET_WIN10) -typedef void* HANDLE; // forward declaration, to avoid inclusion of whole Windows.h +#include "utils/IPlatformLog.h" -class CWin32InterfaceForCLog +class CWin32InterfaceForCLog : public IPlatformLog { public: - CWin32InterfaceForCLog(); - ~CWin32InterfaceForCLog(); - bool OpenLogFile(const std::string& logFilename, const std::string& backupOldLogToFilename); - void CloseLogFile(void); - bool WriteStringToLog(const std::string& logString); - void PrintDebugString(const std::string& debugString); - static void GetCurrentLocalTime(int& year, int& month, int& day, int& hour, int& minute, int& second, double& millisecond); -private: - HANDLE m_hFile; + CWin32InterfaceForCLog() = default; + ~CWin32InterfaceForCLog() = default; + + spdlog_filename_t GetLogFilename(const std::string& filename) const override; + void AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const override; }; diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp index bf7c76a517..54f8261135 100644 --- a/xbmc/settings/AdvancedSettings.cpp +++ b/xbmc/settings/AdvancedSettings.cpp @@ -68,7 +68,7 @@ void CAdvancedSettings::OnSettingsLoaded() m_logLevel = std::min(m_logLevelHint, LOG_LEVEL_DEBUG/*LOG_LEVEL_NORMAL*/); CLog::Log(LOGNOTICE, "Disabled debug logging due to GUI setting. Level %d.", m_logLevel); } - CLog::SetLogLevel(m_logLevel); + CServiceBroker::GetLogging().SetLogLevel(m_logLevel); m_extraLogEnabled = settings->GetBool(CSettings::SETTING_DEBUG_EXTRALOGGING); SetExtraLogLevel(settings->GetList(CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL)); @@ -867,7 +867,7 @@ void CAdvancedSettings::ParseSettingsFile(const std::string &file) setting->SetVisible(false); } m_logLevel = std::max(m_logLevel, m_logLevelHint); - CLog::SetLogLevel(m_logLevel); + CServiceBroker::GetLogging().SetLogLevel(m_logLevel); } XMLUtils::GetString(pRootElement, "cddbaddress", m_cddbAddress); @@ -1351,7 +1351,7 @@ void CAdvancedSettings::SetDebugMode(bool debug) { int level = std::max(m_logLevelHint, LOG_LEVEL_DEBUG_FREEMEM); m_logLevel = level; - CLog::SetLogLevel(level); + CServiceBroker::GetLogging().SetLogLevel(level); CLog::Log(LOGNOTICE, "Enabled debug logging due to GUI setting. Level %d.", level); } else @@ -1359,7 +1359,7 @@ void CAdvancedSettings::SetDebugMode(bool debug) int level = std::min(m_logLevelHint, LOG_LEVEL_DEBUG/*LOG_LEVEL_NORMAL*/); CLog::Log(LOGNOTICE, "Disabled debug logging due to GUI setting. Level %d.", level); m_logLevel = level; - CLog::SetLogLevel(level); + CServiceBroker::GetLogging().SetLogLevel(level); } } diff --git a/xbmc/test/TestBasicEnvironment.cpp b/xbmc/test/TestBasicEnvironment.cpp index 9714018924..cbb9605c80 100644 --- a/xbmc/test/TestBasicEnvironment.cpp +++ b/xbmc/test/TestBasicEnvironment.cpp @@ -38,6 +38,9 @@ void TestBasicEnvironment::SetUp() { CAppParamParser params; params.m_platformDirectories = false; + + CServiceBroker::CreateLogging(); + m_pSettingsComponent.reset(new CSettingsComponent()); m_pSettingsComponent->Init(params); diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index 648d15ffee..9705e31d09 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -111,6 +111,7 @@ set(HEADERS ActorProtocol.h IBufferObject.h ILocalizer.h InfoLoader.h + IPlatformLog.h IRssObserver.h IScreenshotSurface.h ISerializable.h @@ -125,6 +126,7 @@ set(HEADERS ActorProtocol.h LegacyPathTranslation.h Locale.h log.h + logtypes.h MathUtils.h MemUtils.h Mime.h diff --git a/xbmc/utils/IPlatformLog.h b/xbmc/utils/IPlatformLog.h new file mode 100644 index 0000000000..6ccf98d334 --- /dev/null +++ b/xbmc/utils/IPlatformLog.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <mutex> +#include <string> + +#ifdef TARGET_WINDOWS +using spdlog_filename_t = std::wstring; +#else +using spdlog_filename_t = std::string; +#endif + +namespace spdlog +{ +namespace sinks +{ +template<typename Mutex> +class dist_sink; +} +} // namespace spdlog + +class IPlatformLog +{ +public: + virtual ~IPlatformLog() = default; + + static std::unique_ptr<IPlatformLog> CreatePlatformLog(); + + virtual spdlog_filename_t GetLogFilename(const std::string& filename) const = 0; + virtual void AddSinks( + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> distributionSink) const = 0; +}; diff --git a/xbmc/utils/log.cpp b/xbmc/utils/log.cpp index e51a5f3d6f..79b423ff18 100644 --- a/xbmc/utils/log.cpp +++ b/xbmc/utils/log.cpp @@ -10,207 +10,185 @@ #include "CompileInfo.h" #include "ServiceBroker.h" -#include "settings/AdvancedSettings.h" -#include "settings/SettingsComponent.h" -#include "threads/CriticalSection.h" -#include "threads/SingleLock.h" -#include "threads/Thread.h" -#include "utils/StringUtils.h" - -#include <inttypes.h> - -#if defined(TARGET_POSIX) -#include "platform/posix/utils/PosixInterfaceForCLog.h" -typedef class CPosixInterfaceForCLog PlatformInterfaceForCLog; -#elif defined(TARGET_WINDOWS) +#include "filesystem/File.h" +#include "utils/URIUtils.h" + +#if defined(TARGET_ANDROID) +#include "platform/android/utils/AndroidInterfaceForCLog.h" +#elif defined(TARGET_DARWIN) +#include "platform/darwin/utils/DarwinInterfaceForCLog.h" +#elif defined(TARGET_WINDOWS) || defined(TARGET_WIN10) #include "platform/win32/utils/Win32InterfaceForCLog.h" -typedef class CWin32InterfaceForCLog PlatformInterfaceForCLog; +#else +#include "platform/posix/utils/PosixInterfaceForCLog.h" #endif +#include <cstring> -static const char* const levelNames[] = -{"DEBUG", "INFO", "NOTICE", "WARNING", "ERROR", "SEVERE", "FATAL", "NONE"}; +#include <spdlog/sinks/basic_file_sink.h> +#include <spdlog/sinks/dist_sink.h> -// add 1 to level number to get index of name -static const char* const logLevelNames[] = -{ "LOG_LEVEL_NONE" /*-1*/, "LOG_LEVEL_NORMAL" /*0*/, "LOG_LEVEL_DEBUG" /*1*/, "LOG_LEVEL_DEBUG_FREEMEM" /*2*/ }; +static constexpr unsigned char Utf8Bom[3] = {0xEF, 0xBB, 0xBF}; +static const std::string LogFileExtension = ".log"; +static const std::string LogPattern = "%Y-%m-%d %T.%e T:%-5t %7l <%n>: %v"; -namespace -{ -class CLogGlobals +CLog::CLog() + : m_platform(IPlatformLog::CreatePlatformLog()), + m_sinks(std::make_shared<spdlog::sinks::dist_sink_mt>()), + m_defaultLogger(CreateLogger("general")), + m_logLevel(LOG_LEVEL_DEBUG) { -public: - ~CLogGlobals() = default; - PlatformInterfaceForCLog m_platform; - int m_repeatCount = 0; - int m_repeatLogLevel = -1; - std::string m_repeatLine; - int m_logLevel = LOG_LEVEL_DEBUG; - int m_extraLogLevels = 0; - CCriticalSection critSec; -}; - -static CLogGlobals g_logState; -} + // add platform-specific debug sinks + m_platform->AddSinks(m_sinks); -CLog::CLog() = default; + // register the default logger with spdlog + spdlog::set_default_logger(m_defaultLogger); -CLog::~CLog() = default; + // set the formatting pattern globally + spdlog::set_pattern(LogPattern); -void CLog::Close() -{ - CSingleLock waitLock(g_logState.critSec); - g_logState.m_platform.CloseLogFile(); - g_logState.m_repeatLine.clear(); + // flush on debug logs + spdlog::flush_on(spdlog::level::debug); + + // set the log level + SetLogLevel(m_logLevel); } -void CLog::LogString(int logLevel, std::string&& logString) +void CLog::Initialize(const std::string& path) { - CSingleLock waitLock(g_logState.critSec); - std::string strData(logString); - StringUtils::TrimRight(strData); - if (!strData.empty()) + if (m_fileSink != nullptr) + return; + + if (path.empty()) + return; + + // put together the path to the log file(s) + std::string appName = CCompileInfo::GetAppName(); + StringUtils::ToLower(appName); + const std::string filePathBase = URIUtils::AddFileToFolder(path, appName); + const std::string filePath = filePathBase + LogFileExtension; + const std::string oldFilePath = filePathBase + ".old" + LogFileExtension; + + // handle old.log by deleting an existing old.log and renaming the last log to old.log + XFILE::CFile::Delete(oldFilePath); + XFILE::CFile::Rename(filePath, oldFilePath); + + // write UTF-8 BOM { - if (g_logState.m_repeatLogLevel == logLevel && g_logState.m_repeatLine == strData) - { - g_logState.m_repeatCount++; - return; - } - else if (g_logState.m_repeatCount) - { - std::string strData2 = StringUtils::Format("Previous line repeats %d times.", - g_logState.m_repeatCount); - PrintDebugString(strData2); - WriteLogString(g_logState.m_repeatLogLevel, strData2); - g_logState.m_repeatCount = 0; - } - - g_logState.m_repeatLine = strData; - g_logState.m_repeatLogLevel = logLevel; - - PrintDebugString(strData); - - WriteLogString(logLevel, strData); + XFILE::CFile file; + if (file.OpenForWrite(filePath, true)) + file.Write(Utf8Bom, sizeof(Utf8Bom)); } -} -void CLog::LogString(int logLevel, int component, std::string&& logString) -{ - if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->CanLogComponent(component) && IsLogLevelLogged(logLevel)) - LogString(logLevel, std::move(logString)); + // create the file sink + m_fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>( + m_platform->GetLogFilename(filePath), false); + m_fileSink->set_pattern(LogPattern); + + // add it to the existing sinks + m_sinks->add_sink(m_fileSink); } -bool CLog::Init(const std::string& path) +void CLog::Uninitialize() { - CSingleLock waitLock(g_logState.critSec); + if (m_fileSink == nullptr) + return; - // the log folder location is initialized in the CAdvancedSettings - // constructor and changed in CApplication::Create() + // flush all loggers + spdlog::apply_all([](std::shared_ptr<spdlog::logger> logger) { logger->flush(); }); - std::string appName = CCompileInfo::GetAppName(); - StringUtils::ToLower(appName); - return g_logState.m_platform.OpenLogFile(path + appName + ".log", path + appName + ".old.log"); -} + // flush the file sink + m_fileSink->flush(); -void CLog::MemDump(char *pData, int length) -{ - Log(LOGDEBUG, "MEM_DUMP: Dumping from %p", pData); - for (int i = 0; i < length; i+=16) - { - std::string strLine = StringUtils::Format("MEM_DUMP: %04x ", i); - char *alpha = pData; - for (int k=0; k < 4 && i + 4*k < length; k++) - { - for (int j=0; j < 4 && i + 4*k + j < length; j++) - { - std::string strFormat = StringUtils::Format(" %02x", (unsigned char)*pData++); - strLine += strFormat; - } - strLine += " "; - } - // pad with spaces - while (strLine.size() < 13*4 + 16) - strLine += " "; - for (int j=0; j < 16 && i + j < length; j++) - { - if (*alpha > 31) - strLine += *alpha; - else - strLine += '.'; - alpha++; - } - Log(LOGDEBUG, "%s", strLine.c_str()); - } + // remove and destroy the file sink + m_sinks->remove_sink(m_fileSink); + m_fileSink.reset(); } void CLog::SetLogLevel(int level) { - CSingleLock waitLock(g_logState.critSec); - if (level >= LOG_LEVEL_NONE && level <= LOG_LEVEL_MAX) - { - g_logState.m_logLevel = level; - CLog::Log(LOGNOTICE, "Log level changed to \"%s\"", logLevelNames[g_logState.m_logLevel + 1]); - } + if (level < LOG_LEVEL_NONE || level > LOG_LEVEL_MAX) + return; + + m_logLevel = level; + + auto spdLevel = spdlog::level::info; +#if defined(_DEBUG) || defined(PROFILE) + spdLevel = spdlog::level::trace; +#else + if (level <= LOG_LEVEL_NONE) + spdLevel = spdlog::level::off; + else if (level >= LOG_LEVEL_DEBUG) + spdLevel = spdlog::level::trace; else - CLog::Log(LOGERROR, "%s: Invalid log level requested: %d", __FUNCTION__, level); -} + spdLevel = spdlog::level::info; +#endif -int CLog::GetLogLevel() -{ - return g_logState.m_logLevel; -} + if (m_defaultLogger != nullptr && m_defaultLogger->level() == spdLevel) + return; -void CLog::SetExtraLogLevels(int level) -{ - CSingleLock waitLock(g_logState.critSec); - g_logState.m_extraLogLevels = level; + spdlog::set_level(spdLevel); + FormatAndLogInternal(spdlog::level::info, "Log level changed to \"%s\"", + spdlog::level::to_string_view(spdLevel)); } bool CLog::IsLogLevelLogged(int loglevel) { - const int extras = (loglevel & ~LOGMASK); - if (extras != 0 && (g_logState.m_extraLogLevels & extras) == 0) - return false; - - if (g_logState.m_logLevel >= LOG_LEVEL_DEBUG) + if (m_logLevel >= LOG_LEVEL_DEBUG) return true; - if (g_logState.m_logLevel <= LOG_LEVEL_NONE) + if (m_logLevel <= LOG_LEVEL_NONE) return false; - // "m_logLevel" is "LOG_LEVEL_NORMAL" return (loglevel & LOGMASK) >= LOGNOTICE; } +Logger CLog::GetLogger(const std::string& loggerName) +{ + auto logger = spdlog::get(loggerName); + if (logger == nullptr) + logger = CreateLogger(loggerName); -void CLog::PrintDebugString(const std::string& line) + return logger; +} + +CLog& CLog::GetInstance() { -#if defined(_DEBUG) || defined(PROFILE) - g_logState.m_platform.PrintDebugString(line); -#endif // defined(_DEBUG) || defined(PROFILE) + return CServiceBroker::GetLogging(); } -bool CLog::WriteLogString(int logLevel, const std::string& logString) +spdlog::level::level_enum CLog::MapLogLevel(int level) { - static const char* prefixFormat = "%02d-%02d-%02d %02d:%02d:%02d.%03d T:%" PRIu64" %7s: "; - - std::string strData(logString); - /* fixup newline alignment, number of spaces should equal prefix length */ - StringUtils::Replace(strData, "\n", "\n "); - - int year, month, day, hour, minute, second; - double millisecond; - g_logState.m_platform.GetCurrentLocalTime(year, month, day, hour, minute, second, millisecond); - - strData = StringUtils::Format(prefixFormat, - year, - month, - day, - hour, - minute, - second, - static_cast<int>(millisecond), - static_cast<uint64_t>(CThread::GetCurrentThreadNativeId()), - levelNames[logLevel]) + strData; - - return g_logState.m_platform.WriteStringToLog(strData); + switch (level) + { + case LOGDEBUG: + return spdlog::level::debug; + case LOGINFO: + case LOGNOTICE: + return spdlog::level::info; + case LOGWARNING: + return spdlog::level::warn; + case LOGERROR: + return spdlog::level::err; + case LOGSEVERE: + case LOGFATAL: + return spdlog::level::critical; + case LOGNONE: + return spdlog::level::off; + + default: + break; + } + + return spdlog::level::info; +} + +Logger CLog::CreateLogger(const std::string& loggerName) +{ + // create the logger + auto logger = std::make_shared<spdlog::logger>(loggerName, m_sinks); + + // initialize the logger + spdlog::initialize_logger(logger); + + return logger; } diff --git a/xbmc/utils/log.h b/xbmc/utils/log.h index c45ed06c52..bf37ba3799 100644 --- a/xbmc/utils/log.h +++ b/xbmc/utils/log.h @@ -8,98 +8,191 @@ #pragma once +// spdlog specific defines +#define SPDLOG_LEVEL_NAMES {"TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "FATAL", "OFF"}; + #include "commons/ilog.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/IPlatformLog.h" #include "utils/StringUtils.h" +#include "utils/logtypes.h" #include <string> -#include <utility> +#include <spdlog/spdlog.h> + +namespace spdlog +{ +namespace sinks +{ +template<typename Mutex> +class basic_file_sink; + +template<typename Mutex> +class dist_sink; +} // namespace sinks +} // namespace spdlog class CLog { public: CLog(); - ~CLog(); - static void Close(); + ~CLog() = default; + + void Initialize(const std::string& path); + void Uninitialize(); - static void Log(int loglevel, const char* format) + void SetLogLevel(int level); + int GetLogLevel() { return m_logLevel; } + bool IsLogLevelLogged(int loglevel); + + Logger GetLogger(const std::string& loggerName); + + template<typename Char, typename... Args> + static inline void Log(int level, const Char* format, Args&&... args) { - if (IsLogLevelLogged(loglevel)) - LogString(loglevel, format); + Log(MapLogLevel(level), format, std::forward<Args>(args)...); } - template<typename... Args> - static void Log(int loglevel, const char* format, Args&&... args) + template<typename Char, typename... Args> + static inline void Log(int level, int component, const Char* format, Args&&... args) { - if (IsLogLevelLogged(loglevel)) - LogString(loglevel, StringUtils::Format(format, std::forward<Args>(args)...)); + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->CanLogComponent(component)) + return; + + Log(level, format, std::forward<Args>(args)...); } - static void Log(int loglevel, int component, const char* format) + template<typename Char, typename... Args> + static inline void Log(spdlog::level::level_enum level, const Char* format, Args&&... args) { - if (IsLogLevelLogged(loglevel)) - LogString(loglevel, component, format); + GetInstance().FormatAndLogInternal(level, format, std::forward<Args>(args)...); } - template<typename... Args> - static void Log(int loglevel, int component, const char* format, Args&&... args) + template<typename Char, typename... Args> + static inline void Log(spdlog::level::level_enum level, + int component, + const Char* format, + Args&&... args) { - // We defer component checking to LogString to avoid having to drag in advancedsettings - // everywhere we want to log anything - if (IsLogLevelLogged(loglevel)) - LogString(loglevel, component, StringUtils::Format(format, std::forward<Args>(args)...)); + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->CanLogComponent(component)) + return; + + Log(level, format, std::forward<Args>(args)...); } - static void LogFunction(int loglevel, std::string functionName, const char* format) + template<typename Char, typename... Args> + static inline void LogFunction(int level, + const char* functionName, + const Char* format, + Args&&... args) { - if (IsLogLevelLogged(loglevel)) - LogString(loglevel, functionName + ": " + format); + LogFunction(MapLogLevel(level), functionName, format, std::forward<Args>(args)...); } - template<typename... Args> - static void LogFunction(int loglevel, - std::string functionName, - const char* format, - Args&&... args) + template<typename Char, typename... Args> + static inline void LogFunction( + int level, const char* functionName, int component, const Char* format, Args&&... args) + { + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->CanLogComponent(component)) + return; + + LogFunction(level, functionName, format, std::forward<Args>(args)...); + } + + template<typename Char, typename... Args> + static inline void LogFunction(spdlog::level::level_enum level, + const char* functionName, + const Char* format, + Args&&... args) { - if (IsLogLevelLogged(loglevel)) - { - functionName.append(": "); - LogString(loglevel, functionName + StringUtils::Format(format, std::forward<Args>(args)...)); - } + if (functionName == nullptr || strlen(functionName) == 0) + GetInstance().FormatAndLogInternal(level, format, std::forward<Args>(args)...); + else + GetInstance().FormatAndLogFunctionInternal(level, functionName, format, + std::forward<Args>(args)...); } - static void LogFunction(int loglevel, std::string functionName, int component, const char* format) + template<typename Char, typename... Args> + static inline void LogFunction(spdlog::level::level_enum level, + const char* functionName, + int component, + const Char* format, + Args&&... args) { - if (IsLogLevelLogged(loglevel)) - LogString(loglevel, component, functionName + ": " + format); + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->CanLogComponent(component)) + return; + + LogFunction(level, functionName, format, std::forward<Args>(args)...); } +#define LogF(level, format, ...) LogFunction((level), __FUNCTION__, (format), ##__VA_ARGS__) +#define LogFC(level, component, format, ...) \ + LogFunction((level), __FUNCTION__, (component), (format), ##__VA_ARGS__) + +private: + static CLog& GetInstance(); + + static spdlog::level::level_enum MapLogLevel(int level); + template<typename... Args> - static void LogFunction( - int loglevel, std::string functionName, int component, const char* format, Args&&... args) + static inline void FormatAndLogFunctionInternal(spdlog::level::level_enum level, + const char* functionName, + const char* format, + Args&&... args) + { + GetInstance().FormatAndLogInternal( + level, StringUtils::Format("{0:s}: {1:s}", functionName, format).c_str(), + std::forward<Args>(args)...); + } + + template<typename... Args> + static inline void FormatAndLogFunctionInternal(spdlog::level::level_enum level, + const char* functionName, + const wchar_t* format, + Args&&... args) + { + GetInstance().FormatAndLogInternal( + level, StringUtils::Format(L"{0:s}: {1:s}", functionName, format).c_str(), + std::forward<Args>(args)...); + } + + template<typename Char, typename... Args> + inline void FormatAndLogInternal(spdlog::level::level_enum level, + const Char* format, + Args&&... args) + { + // TODO: for now we manually format the messages to support both python- and printf-style formatting. + // this can be removed once all log messages have been adjusted to python-style formatting + auto logString = StringUtils::Format(format, std::forward<Args>(args)...); + + // fixup newline alignment, number of spaces should equal prefix length + StringUtils::Replace(logString, "\n", "\n "); + + m_defaultLogger->log(level, std::move(logString)); + } + + Logger CreateLogger(const std::string& loggerName); + + std::unique_ptr<IPlatformLog> m_platform; + std::shared_ptr<spdlog::sinks::dist_sink<std::mutex>> m_sinks; + Logger m_defaultLogger; + + std::shared_ptr<spdlog::sinks::basic_file_sink<std::mutex>> m_fileSink; + + int m_logLevel; +}; + +namespace XbmcUtils +{ +class LogImplementation : public XbmcCommons::ILogger +{ +public: + ~LogImplementation() override = default; + inline void log(int logLevel, IN_STRING const char* message) override { - // We defer component checking to LogString to avoid having to drag in advancedsettings - // everywhere we want to log anything - if (IsLogLevelLogged(loglevel)) - { - functionName.append(": "); - LogString(loglevel, component, - functionName + StringUtils::Format(format, std::forward<Args>(args)...)); - } + CLog::Log(logLevel, "{0:s}", message); } -#define LogF(loglevel, ...) LogFunction((loglevel), __FUNCTION__, ##__VA_ARGS__) -#define LogFC(loglevel, component, ...) LogFunction((loglevel), __FUNCTION__, (component), ##__VA_ARGS__) - static void MemDump(char *pData, int length); - static bool Init(const std::string& path); - static void PrintDebugString(const std::string& line); // universal interface for printing debug strings - static void SetLogLevel(int level); - static int GetLogLevel(); - static void SetExtraLogLevels(int level); - static bool IsLogLevelLogged(int loglevel); - -protected: - static void LogString(int logLevel, std::string&& logString); - static void LogString(int logLevel, int component, std::string&& logString); - static bool WriteLogString(int logLevel, const std::string& logString); }; +} // namespace XbmcUtils diff --git a/xbmc/utils/logtypes.h b/xbmc/utils/logtypes.h new file mode 100644 index 0000000000..f41aa7eb07 --- /dev/null +++ b/xbmc/utils/logtypes.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> + +namespace spdlog +{ +class logger; +} + +using Logger = std::shared_ptr<spdlog::logger>; diff --git a/xbmc/utils/test/TestRegExp.cpp b/xbmc/utils/test/TestRegExp.cpp index 1019c5bfcb..dd04792365 100644 --- a/xbmc/utils/test/TestRegExp.cpp +++ b/xbmc/utils/test/TestRegExp.cpp @@ -10,6 +10,7 @@ * Investigate why. */ #include "CompileInfo.h" +#include "ServiceBroker.h" #include "filesystem/File.h" #include "filesystem/SpecialProtocol.h" #include "utils/RegExp.h" @@ -128,7 +129,7 @@ protected: TestRegExpLog() = default; ~TestRegExpLog() override { - CLog::Close(); + CServiceBroker::GetLogging().Uninitialize(); } }; @@ -143,13 +144,13 @@ TEST_F(TestRegExpLog, DumpOvector) std::string appName = CCompileInfo::GetAppName(); StringUtils::ToLower(appName); logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; - EXPECT_TRUE(CLog::Init(CSpecialProtocol::TranslatePath("special://temp/").c_str())); + CServiceBroker::GetLogging().Initialize(CSpecialProtocol::TranslatePath("special://temp/").c_str()); EXPECT_TRUE(XFILE::CFile::Exists(logfile)); EXPECT_TRUE(regex.RegComp("^(?<first>Test)\\s*(?<second>.*)\\.")); EXPECT_EQ(0, regex.RegFind("Test string.")); regex.DumpOvector(LOGDEBUG); - CLog::Close(); + CServiceBroker::GetLogging().Uninitialize(); EXPECT_TRUE(file.Open(logfile)); while ((bytesread = file.Read(buf, sizeof(buf) - 1)) > 0) @@ -162,7 +163,7 @@ TEST_F(TestRegExpLog, DumpOvector) EXPECT_STREQ("\xEF\xBB\xBF", logstring.substr(0, 3).c_str()); - EXPECT_TRUE(regex.RegComp(".*DEBUG: regexp ovector=\\{\\[0,12\\],\\[0,4\\]," + EXPECT_TRUE(regex.RegComp(".*DEBUG <general>: regexp ovector=\\{\\[0,12\\],\\[0,4\\]," "\\[5,11\\]\\}.*")); EXPECT_GE(regex.RegFind(logstring), 0); diff --git a/xbmc/utils/test/Testlog.cpp b/xbmc/utils/test/Testlog.cpp index 71bf9d6a5f..15f37a8f9e 100644 --- a/xbmc/utils/test/Testlog.cpp +++ b/xbmc/utils/test/Testlog.cpp @@ -7,6 +7,7 @@ */ #include "CompileInfo.h" +#include "ServiceBroker.h" #include "filesystem/File.h" #include "filesystem/SpecialProtocol.h" #include "test/TestUtils.h" @@ -24,7 +25,7 @@ protected: Testlog() = default; ~Testlog() override { - CLog::Close(); + CServiceBroker::GetLogging().Uninitialize(); } }; @@ -39,7 +40,7 @@ TEST_F(Testlog, Log) std::string appName = CCompileInfo::GetAppName(); StringUtils::ToLower(appName); logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; - EXPECT_TRUE(CLog::Init(CSpecialProtocol::TranslatePath("special://temp/").c_str())); + CServiceBroker::GetLogging().Initialize(CSpecialProtocol::TranslatePath("special://temp/").c_str()); EXPECT_TRUE(XFILE::CFile::Exists(logfile)); CLog::Log(LOGDEBUG, "debug log message"); @@ -50,7 +51,7 @@ TEST_F(Testlog, Log) CLog::Log(LOGSEVERE, "severe log message"); CLog::Log(LOGFATAL, "fatal log message"); CLog::Log(LOGNONE, "none type log message"); - CLog::Close(); + CServiceBroker::GetLogging().Uninitialize(); EXPECT_TRUE(file.Open(logfile)); while ((bytesread = file.Read(buf, sizeof(buf) - 1)) > 0) @@ -63,60 +64,21 @@ TEST_F(Testlog, Log) EXPECT_STREQ("\xEF\xBB\xBF", logstring.substr(0, 3).c_str()); - EXPECT_TRUE(regex.RegComp(".*DEBUG: debug log message.*")); + EXPECT_TRUE(regex.RegComp(".*DEBUG <general>: debug log message.*")); EXPECT_GE(regex.RegFind(logstring), 0); - EXPECT_TRUE(regex.RegComp(".*INFO: info log message.*")); + EXPECT_TRUE(regex.RegComp(".*INFO <general>: info log message.*")); EXPECT_GE(regex.RegFind(logstring), 0); - EXPECT_TRUE(regex.RegComp(".*NOTICE: notice log message.*")); + EXPECT_TRUE(regex.RegComp(".*INFO <general>: notice log message.*")); EXPECT_GE(regex.RegFind(logstring), 0); - EXPECT_TRUE(regex.RegComp(".*WARNING: warning log message.*")); + EXPECT_TRUE(regex.RegComp(".*WARNING <general>: warning log message.*")); EXPECT_GE(regex.RegFind(logstring), 0); - EXPECT_TRUE(regex.RegComp(".*ERROR: error log message.*")); + EXPECT_TRUE(regex.RegComp(".*ERROR <general>: error log message.*")); EXPECT_GE(regex.RegFind(logstring), 0); - EXPECT_TRUE(regex.RegComp(".*SEVERE: severe log message.*")); + EXPECT_TRUE(regex.RegComp(".*FATAL <general>: severe log message.*")); EXPECT_GE(regex.RegFind(logstring), 0); - EXPECT_TRUE(regex.RegComp(".*FATAL: fatal log message.*")); + EXPECT_TRUE(regex.RegComp(".*FATAL <general>: fatal log message.*")); EXPECT_GE(regex.RegFind(logstring), 0); - EXPECT_TRUE(regex.RegComp(".*NONE: none type log message.*")); - EXPECT_GE(regex.RegFind(logstring), 0); - - EXPECT_TRUE(XFILE::CFile::Delete(logfile)); -} - -TEST_F(Testlog, MemDump) -{ - std::string logfile, logstring; - char buf[100]; - unsigned int bytesread; - XFILE::CFile file; - CRegExp regex; - char refdata[] = "0123456789abcdefghijklmnopqrstuvwxyz"; - - std::string appName = CCompileInfo::GetAppName(); - StringUtils::ToLower(appName); - logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; - EXPECT_TRUE(CLog::Init(CSpecialProtocol::TranslatePath("special://temp/").c_str())); - EXPECT_TRUE(XFILE::CFile::Exists(logfile)); - - CLog::MemDump(refdata, sizeof(refdata)); - CLog::Close(); - - EXPECT_TRUE(file.Open(logfile)); - while ((bytesread = file.Read(buf, sizeof(buf) - 1)) > 0) - { - buf[bytesread] = '\0'; - logstring.append(buf); - } - file.Close(); - EXPECT_FALSE(logstring.empty()); - - EXPECT_STREQ("\xEF\xBB\xBF", logstring.substr(0, 3).c_str()); - - EXPECT_TRUE(regex.RegComp(".*DEBUG: MEM_DUMP: Dumping from.*")); - EXPECT_GE(regex.RegFind(logstring), 0); - EXPECT_TRUE(regex.RegComp(".*DEBUG: MEM_DUMP: 0000 30 31 32 33.*")); - EXPECT_GE(regex.RegFind(logstring), 0); - EXPECT_TRUE(regex.RegComp(".*73 74 75 76 ghijklmnopqrstuv.*")); + EXPECT_TRUE(regex.RegComp(".*OFF <general>: none type log message.*")); EXPECT_GE(regex.RegFind(logstring), 0); EXPECT_TRUE(XFILE::CFile::Delete(logfile)); @@ -129,13 +91,13 @@ TEST_F(Testlog, SetLogLevel) std::string appName = CCompileInfo::GetAppName(); StringUtils::ToLower(appName); logfile = CSpecialProtocol::TranslatePath("special://temp/") + appName + ".log"; - EXPECT_TRUE(CLog::Init(CSpecialProtocol::TranslatePath("special://temp/").c_str())); + CServiceBroker::GetLogging().Initialize(CSpecialProtocol::TranslatePath("special://temp/").c_str()); EXPECT_TRUE(XFILE::CFile::Exists(logfile)); - EXPECT_EQ(LOG_LEVEL_DEBUG, CLog::GetLogLevel()); - CLog::SetLogLevel(LOG_LEVEL_MAX); - EXPECT_EQ(LOG_LEVEL_MAX, CLog::GetLogLevel()); + EXPECT_EQ(LOG_LEVEL_DEBUG, CServiceBroker::GetLogging().GetLogLevel()); + CServiceBroker::GetLogging().SetLogLevel(LOG_LEVEL_MAX); + EXPECT_EQ(LOG_LEVEL_MAX, CServiceBroker::GetLogging().GetLogLevel()); - CLog::Close(); + CServiceBroker::GetLogging().Uninitialize(); EXPECT_TRUE(XFILE::CFile::Delete(logfile)); } |