mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 08:00:05 +00:00 
			
		
		
		
	Add file logging to local application data directory
This commit is contained in:
		
							parent
							
								
									41d47878e3
								
							
						
					
					
						commit
						32cd672a40
					
				
					 6 changed files with 255 additions and 47 deletions
				
			
		|  | @ -91,6 +91,7 @@ set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp | ||||||
|                 source/scwx/qt/manager/download_manager.hpp |                 source/scwx/qt/manager/download_manager.hpp | ||||||
|                 source/scwx/qt/manager/font_manager.hpp |                 source/scwx/qt/manager/font_manager.hpp | ||||||
|                 source/scwx/qt/manager/hotkey_manager.hpp |                 source/scwx/qt/manager/hotkey_manager.hpp | ||||||
|  |                 source/scwx/qt/manager/log_manager.hpp | ||||||
|                 source/scwx/qt/manager/media_manager.hpp |                 source/scwx/qt/manager/media_manager.hpp | ||||||
|                 source/scwx/qt/manager/placefile_manager.hpp |                 source/scwx/qt/manager/placefile_manager.hpp | ||||||
|                 source/scwx/qt/manager/position_manager.hpp |                 source/scwx/qt/manager/position_manager.hpp | ||||||
|  | @ -106,6 +107,7 @@ set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp | ||||||
|                 source/scwx/qt/manager/download_manager.cpp |                 source/scwx/qt/manager/download_manager.cpp | ||||||
|                 source/scwx/qt/manager/font_manager.cpp |                 source/scwx/qt/manager/font_manager.cpp | ||||||
|                 source/scwx/qt/manager/hotkey_manager.cpp |                 source/scwx/qt/manager/hotkey_manager.cpp | ||||||
|  |                 source/scwx/qt/manager/log_manager.cpp | ||||||
|                 source/scwx/qt/manager/media_manager.cpp |                 source/scwx/qt/manager/media_manager.cpp | ||||||
|                 source/scwx/qt/manager/placefile_manager.cpp |                 source/scwx/qt/manager/placefile_manager.cpp | ||||||
|                 source/scwx/qt/manager/position_manager.cpp |                 source/scwx/qt/manager/position_manager.cpp | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| #include <scwx/qt/config/radar_site.hpp> | #include <scwx/qt/config/radar_site.hpp> | ||||||
| #include <scwx/qt/main/main_window.hpp> | #include <scwx/qt/main/main_window.hpp> | ||||||
| #include <scwx/qt/main/versions.hpp> | #include <scwx/qt/main/versions.hpp> | ||||||
|  | #include <scwx/qt/manager/log_manager.hpp> | ||||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | #include <scwx/qt/manager/radar_product_manager.hpp> | ||||||
| #include <scwx/qt/manager/resource_manager.hpp> | #include <scwx/qt/manager/resource_manager.hpp> | ||||||
| #include <scwx/qt/manager/settings_manager.hpp> | #include <scwx/qt/manager/settings_manager.hpp> | ||||||
|  | @ -22,7 +23,6 @@ | ||||||
| #include <aws/core/Aws.h> | #include <aws/core/Aws.h> | ||||||
| #include <boost/asio.hpp> | #include <boost/asio.hpp> | ||||||
| #include <fmt/format.h> | #include <fmt/format.h> | ||||||
| #include <spdlog/spdlog.h> |  | ||||||
| #include <QApplication> | #include <QApplication> | ||||||
| #include <QStandardPaths> | #include <QStandardPaths> | ||||||
| #include <QTranslator> | #include <QTranslator> | ||||||
|  | @ -31,9 +31,6 @@ static const std::string logPrefix_ = "scwx::main"; | ||||||
| static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
| 
 | 
 | ||||||
| static void OverrideDefaultStyle(const std::vector<std::string>& args); | static void OverrideDefaultStyle(const std::vector<std::string>& args); | ||||||
| static void QtLogMessageHandler(QtMsgType                 messageType, |  | ||||||
|                                 const QMessageLogContext& context, |  | ||||||
|                                 const QString&            message); |  | ||||||
| 
 | 
 | ||||||
| int main(int argc, char* argv[]) | int main(int argc, char* argv[]) | ||||||
| { | { | ||||||
|  | @ -45,11 +42,8 @@ int main(int argc, char* argv[]) | ||||||
|    } |    } | ||||||
| 
 | 
 | ||||||
|    // Initialize logger
 |    // Initialize logger
 | ||||||
|    scwx::util::Logger::Initialize(); |    auto& logManager = scwx::qt::manager::LogManager::Instance(); | ||||||
|    spdlog::set_level(spdlog::level::debug); |    logManager.Initialize(); | ||||||
| 
 |  | ||||||
|    // Install Qt Message Handler
 |  | ||||||
|    qInstallMessageHandler(&QtLogMessageHandler); |  | ||||||
| 
 | 
 | ||||||
|    QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); |    QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); | ||||||
| 
 | 
 | ||||||
|  | @ -101,6 +95,7 @@ int main(int argc, char* argv[]) | ||||||
|    scwx::qt::config::RadarSite::Initialize(); |    scwx::qt::config::RadarSite::Initialize(); | ||||||
|    scwx::qt::config::CountyDatabase::Initialize(); |    scwx::qt::config::CountyDatabase::Initialize(); | ||||||
|    scwx::qt::manager::SettingsManager::Instance().Initialize(); |    scwx::qt::manager::SettingsManager::Instance().Initialize(); | ||||||
|  |    logManager.InitializeLogFile(); | ||||||
|    scwx::qt::manager::ResourceManager::Initialize(); |    scwx::qt::manager::ResourceManager::Initialize(); | ||||||
| 
 | 
 | ||||||
|    // Theme
 |    // Theme
 | ||||||
|  | @ -176,40 +171,3 @@ OverrideDefaultStyle([[maybe_unused]] const std::vector<std::string>& args) | ||||||
|    } |    } | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| 
 |  | ||||||
| void QtLogMessageHandler(QtMsgType                 messageType, |  | ||||||
|                          const QMessageLogContext& context, |  | ||||||
|                          const QString&            message) |  | ||||||
| { |  | ||||||
|    static const auto qtLogger_ = scwx::util::Logger::Create("qt"); |  | ||||||
| 
 |  | ||||||
|    static const std::unordered_map<QtMsgType, spdlog::level::level_enum> |  | ||||||
|       levelMap_ {{QtMsgType::QtDebugMsg, spdlog::level::level_enum::debug}, |  | ||||||
|                  {QtMsgType::QtInfoMsg, spdlog::level::level_enum::info}, |  | ||||||
|                  {QtMsgType::QtWarningMsg, spdlog::level::level_enum::warn}, |  | ||||||
|                  {QtMsgType::QtCriticalMsg, spdlog::level::level_enum::err}, |  | ||||||
|                  {QtMsgType::QtFatalMsg, spdlog::level::level_enum::critical}}; |  | ||||||
| 
 |  | ||||||
|    spdlog::level::level_enum level = spdlog::level::level_enum::info; |  | ||||||
|    auto                      it    = levelMap_.find(messageType); |  | ||||||
|    if (it != levelMap_.cend()) |  | ||||||
|    { |  | ||||||
|       level = it->second; |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    spdlog::source_loc location {}; |  | ||||||
|    if (context.file != nullptr && context.function != nullptr) |  | ||||||
|    { |  | ||||||
|       location = {context.file, context.line, context.function}; |  | ||||||
|    } |  | ||||||
| 
 |  | ||||||
|    if (context.category != nullptr) |  | ||||||
|    { |  | ||||||
|       qtLogger_->log( |  | ||||||
|          location, level, "[{}] {}", context.category, message.toStdString()); |  | ||||||
|    } |  | ||||||
|    else |  | ||||||
|    { |  | ||||||
|       qtLogger_->log(location, level, message.toStdString()); |  | ||||||
|    } |  | ||||||
| } |  | ||||||
|  |  | ||||||
							
								
								
									
										178
									
								
								scwx-qt/source/scwx/qt/manager/log_manager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								scwx-qt/source/scwx/qt/manager/log_manager.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,178 @@ | ||||||
|  | #include <scwx/qt/manager/log_manager.hpp> | ||||||
|  | #include <scwx/util/logger.hpp> | ||||||
|  | 
 | ||||||
|  | #include <filesystem> | ||||||
|  | #include <map> | ||||||
|  | #include <ranges> | ||||||
|  | #include <unordered_map> | ||||||
|  | 
 | ||||||
|  | #include <boost/process/environment.hpp> | ||||||
|  | #include <fmt/format.h> | ||||||
|  | #include <spdlog/spdlog.h> | ||||||
|  | #include <QStandardPaths> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "scwx::qt::manager::log_manager"; | ||||||
|  | static const auto        logger_    = scwx::util::Logger::Create(logPrefix_); | ||||||
|  | 
 | ||||||
|  | static void QtLogMessageHandler(QtMsgType                 messageType, | ||||||
|  |                                 const QMessageLogContext& context, | ||||||
|  |                                 const QString&            message); | ||||||
|  | 
 | ||||||
|  | class LogManager::Impl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit Impl() {} | ||||||
|  |    ~Impl() {} | ||||||
|  | 
 | ||||||
|  |    void PruneLogFiles(); | ||||||
|  | 
 | ||||||
|  |    std::string logPath_ {}; | ||||||
|  |    std::string logFile_ {}; | ||||||
|  |    int         pid_ {}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | LogManager::LogManager() : p(std::make_unique<Impl>()) {} | ||||||
|  | LogManager::~LogManager() = default; | ||||||
|  | 
 | ||||||
|  | void LogManager::Initialize() | ||||||
|  | { | ||||||
|  |    // Initialize logger
 | ||||||
|  |    scwx::util::Logger::Initialize(); | ||||||
|  |    spdlog::set_level(spdlog::level::debug); | ||||||
|  | 
 | ||||||
|  |    // Install Qt Message Handler
 | ||||||
|  |    qInstallMessageHandler(&QtLogMessageHandler); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void LogManager::InitializeLogFile() | ||||||
|  | { | ||||||
|  |    p->logPath_ = | ||||||
|  |       QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) | ||||||
|  |          .toStdString(); | ||||||
|  |    p->pid_     = boost::this_process::get_id(); | ||||||
|  |    p->logFile_ = fmt::format("{}/supercell-wx.{}.log", p->logPath_, p->pid_); | ||||||
|  | 
 | ||||||
|  |    // Create log directory if it doesn't exist
 | ||||||
|  |    if (!std::filesystem::exists(p->logPath_)) | ||||||
|  |    { | ||||||
|  |       if (!std::filesystem::create_directories(p->logPath_)) | ||||||
|  |       { | ||||||
|  |          logger_->error("Unable to create log directory: \"{}\"", p->logPath_); | ||||||
|  |          return; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    scwx::util::Logger::AddFileSink(p->logFile_); | ||||||
|  | 
 | ||||||
|  |    p->PruneLogFiles(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void LogManager::Impl::PruneLogFiles() | ||||||
|  | { | ||||||
|  |    using namespace std::chrono_literals; | ||||||
|  | 
 | ||||||
|  |    static constexpr std::size_t kMaxLogFiles_        = 5; | ||||||
|  |    static constexpr auto        kMinModificationAge_ = 30min; | ||||||
|  | 
 | ||||||
|  |    std::multimap<std::filesystem::file_time_type, std::filesystem::path> | ||||||
|  |       logFiles {}; | ||||||
|  | 
 | ||||||
|  |    // Find existing log files in log directory
 | ||||||
|  |    for (auto& file : std::filesystem::directory_iterator(logPath_)) | ||||||
|  |    { | ||||||
|  |       const std::string filename = file.path().filename().string(); | ||||||
|  | 
 | ||||||
|  |       if (file.is_regular_file() && filename.starts_with("supercell-wx.") && | ||||||
|  |           filename.ends_with(".log")) | ||||||
|  |       { | ||||||
|  |          logger_->trace("Found log file: {}", filename); | ||||||
|  | 
 | ||||||
|  |          try | ||||||
|  |          { | ||||||
|  |             auto lastWriteTime = std::filesystem::last_write_time(file); | ||||||
|  |             logFiles.insert({lastWriteTime, file}); | ||||||
|  |          } | ||||||
|  |          catch (const std::exception&) | ||||||
|  |          { | ||||||
|  |             logger_->error("Error getting last write time of file: {}", | ||||||
|  |                            file.path().string()); | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    // Clean up old log files
 | ||||||
|  |    auto now                   = std::filesystem::file_time_type::clock::now(); | ||||||
|  |    auto modificationThreshold = now - kMinModificationAge_; | ||||||
|  | 
 | ||||||
|  |    for (auto& logFile : | ||||||
|  |         logFiles | std::views::reverse | std::views::drop(kMaxLogFiles_)) | ||||||
|  |    { | ||||||
|  |       if (logFile.first < modificationThreshold) | ||||||
|  |       { | ||||||
|  |          const std::string filename = logFile.second.filename().string(); | ||||||
|  | 
 | ||||||
|  |          logger_->info("Removing old log file: {}", filename); | ||||||
|  | 
 | ||||||
|  |          std::error_code ec; | ||||||
|  |          if (!std::filesystem::remove(logFile.second, ec)) | ||||||
|  |          { | ||||||
|  |             logger_->warn( | ||||||
|  |                "Could not remove file: {}, {}", filename, ec.message()); | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | LogManager& LogManager::Instance() | ||||||
|  | { | ||||||
|  |    static LogManager LogManager_ {}; | ||||||
|  |    return LogManager_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void QtLogMessageHandler(QtMsgType                 messageType, | ||||||
|  |                          const QMessageLogContext& context, | ||||||
|  |                          const QString&            message) | ||||||
|  | { | ||||||
|  |    static const auto qtLogger_ = scwx::util::Logger::Create("qt"); | ||||||
|  | 
 | ||||||
|  |    static const std::unordered_map<QtMsgType, spdlog::level::level_enum> | ||||||
|  |       levelMap_ {{QtMsgType::QtDebugMsg, spdlog::level::level_enum::debug}, | ||||||
|  |                  {QtMsgType::QtInfoMsg, spdlog::level::level_enum::info}, | ||||||
|  |                  {QtMsgType::QtWarningMsg, spdlog::level::level_enum::warn}, | ||||||
|  |                  {QtMsgType::QtCriticalMsg, spdlog::level::level_enum::err}, | ||||||
|  |                  {QtMsgType::QtFatalMsg, spdlog::level::level_enum::critical}}; | ||||||
|  | 
 | ||||||
|  |    spdlog::level::level_enum level = spdlog::level::level_enum::info; | ||||||
|  |    auto                      it    = levelMap_.find(messageType); | ||||||
|  |    if (it != levelMap_.cend()) | ||||||
|  |    { | ||||||
|  |       level = it->second; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    spdlog::source_loc location {}; | ||||||
|  |    if (context.file != nullptr && context.function != nullptr) | ||||||
|  |    { | ||||||
|  |       location = {context.file, context.line, context.function}; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (context.category != nullptr) | ||||||
|  |    { | ||||||
|  |       qtLogger_->log( | ||||||
|  |          location, level, "[{}] {}", context.category, message.toStdString()); | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       qtLogger_->log(location, level, message.toStdString()); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
							
								
								
									
										36
									
								
								scwx-qt/source/scwx/qt/manager/log_manager.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								scwx-qt/source/scwx/qt/manager/log_manager.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | 
 | ||||||
|  | #include <QObject> | ||||||
|  | #include <QThread> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace qt | ||||||
|  | { | ||||||
|  | namespace manager | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class LogManager : public QObject | ||||||
|  | { | ||||||
|  |    Q_OBJECT | ||||||
|  |    Q_DISABLE_COPY_MOVE(LogManager) | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |    explicit LogManager(); | ||||||
|  |    ~LogManager(); | ||||||
|  | 
 | ||||||
|  |    void Initialize(); | ||||||
|  |    void InitializeLogFile(); | ||||||
|  | 
 | ||||||
|  |    static LogManager& Instance(); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    class Impl; | ||||||
|  |    std::unique_ptr<Impl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace manager
 | ||||||
|  | } // namespace qt
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -21,6 +21,7 @@ namespace Logger | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| void                            Initialize(); | void                            Initialize(); | ||||||
|  | void                            AddFileSink(const std::string& baseFilename); | ||||||
| std::shared_ptr<spdlog::logger> Create(const std::string& name); | std::shared_ptr<spdlog::logger> Create(const std::string& name); | ||||||
| 
 | 
 | ||||||
| } // namespace Logger
 | } // namespace Logger
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| #include <mutex> | #include <mutex> | ||||||
| 
 | 
 | ||||||
| #include <spdlog/spdlog.h> | #include <spdlog/spdlog.h> | ||||||
|  | #include <spdlog/sinks/rotating_file_sink.h> | ||||||
| #include <spdlog/sinks/stdout_color_sinks.h> | #include <spdlog/sinks/stdout_color_sinks.h> | ||||||
| 
 | 
 | ||||||
| namespace scwx | namespace scwx | ||||||
|  | @ -12,9 +13,34 @@ namespace util | ||||||
| namespace Logger | namespace Logger | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
|  | static const std::string logPattern_ = "[%Y-%m-%d %T.%e] [%t] [%^%l%$] [%n] %v"; | ||||||
|  | 
 | ||||||
|  | static std::vector<std::shared_ptr<spdlog::sinks::sink>> extraSinks_ {}; | ||||||
|  | 
 | ||||||
| void Initialize() | void Initialize() | ||||||
| { | { | ||||||
|    spdlog::set_pattern("[%Y-%m-%d %T.%e] [%t] [%^%l%$] [%n] %v"); |    spdlog::set_pattern(logPattern_); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void AddFileSink(const std::string& baseFilename) | ||||||
|  | { | ||||||
|  |    constexpr std::size_t maxSize      = 20u * 1024u * 1024u; // 20 MB
 | ||||||
|  |    constexpr std::size_t maxFiles     = 5u; | ||||||
|  |    constexpr bool        rotateOnOpen = true; | ||||||
|  | 
 | ||||||
|  |    auto fileSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>( | ||||||
|  |       baseFilename, maxSize, maxFiles, rotateOnOpen); | ||||||
|  | 
 | ||||||
|  |    fileSink->set_pattern(logPattern_); | ||||||
|  | 
 | ||||||
|  |    spdlog::apply_all( | ||||||
|  |       [&](std::shared_ptr<spdlog::logger> logger) | ||||||
|  |       { | ||||||
|  |          auto& sinks = logger->sinks(); | ||||||
|  |          sinks.push_back(fileSink); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |    extraSinks_.push_back(fileSink); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::shared_ptr<spdlog::logger> Create(const std::string& name) | std::shared_ptr<spdlog::logger> Create(const std::string& name) | ||||||
|  | @ -26,6 +52,13 @@ std::shared_ptr<spdlog::logger> Create(const std::string& name) | ||||||
|    std::shared_ptr<spdlog::logger> logger = |    std::shared_ptr<spdlog::logger> logger = | ||||||
|       std::make_shared<spdlog::logger>(name, sink); |       std::make_shared<spdlog::logger>(name, sink); | ||||||
| 
 | 
 | ||||||
|  |    // Add additional registered sinks
 | ||||||
|  |    for (auto& extraSink : extraSinks_) | ||||||
|  |    { | ||||||
|  |       auto& sinks = logger->sinks(); | ||||||
|  |       sinks.push_back(extraSink); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|    // Register the logger, so it can be retrieved later using spdlog::get()
 |    // Register the logger, so it can be retrieved later using spdlog::get()
 | ||||||
|    spdlog::register_logger(logger); |    spdlog::register_logger(logger); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat