diff --git a/include/ilogwriter.h b/include/ilogwriter.h index a9f8e45fdd..ca4cd168c9 100644 --- a/include/ilogwriter.h +++ b/include/ilogwriter.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace applog { @@ -41,6 +42,9 @@ class ILogDevice /** * Central logging hub, dispatching any incoming log messages * to all attached ILogDevices. + * + * Also offers the stream references to set up the rMessage/rWarning streams + * in every module. */ class ILogWriter { @@ -54,6 +58,17 @@ class ILogWriter */ virtual void write(const char* p, std::size_t length, LogLevel level) = 0; + /** + * Returns the stream reference for the given log level. These are used + * to initialise the global rMessage/rWarning streams used by the application. + */ + virtual std::ostream& getLogStream(LogLevel level) = 0; + + /** + * Returns the synchronization object for writing to the streams. + */ + virtual std::mutex& getStreamLock() = 0; + /** * greebo: Use these methods to attach/detach a log device from the * writer class. After attaching a device, all log output diff --git a/include/imodule.h b/include/imodule.h index 3f54e541d7..b40db965df 100644 --- a/include/imodule.h +++ b/include/imodule.h @@ -13,6 +13,7 @@ #include #include "itextstream.h" +#include "ilogwriter.h" /** * \defgroup module Module system @@ -367,6 +368,25 @@ namespace module {} }; + // greebo: This should be called once by each module at load time to initialise + // the OutputStreamHolders + inline void initialiseStreams(applog::ILogWriter& logWriter) + { + GlobalOutputStream().setStream(logWriter.getLogStream(applog::LogLevel::Standard)); + GlobalWarningStream().setStream(logWriter.getLogStream(applog::LogLevel::Warning)); + GlobalErrorStream().setStream(logWriter.getLogStream(applog::LogLevel::Error)); + +#ifndef NDEBUG + GlobalDebugStream().setStream(logWriter.getLogStream(applog::LogLevel::Verbose)); +#endif + + // Set up the mutex for thread-safe logging + GlobalOutputStream().setLock(logWriter.getStreamLock()); + GlobalWarningStream().setLock(logWriter.getStreamLock()); + GlobalErrorStream().setLock(logWriter.getStreamLock()); + GlobalDebugStream().setLock(logWriter.getStreamLock()); + } + // greebo: This should be called once by each module at load time to initialise // the OutputStreamHolders inline void initialiseStreams(const ApplicationContext& ctx) diff --git a/radiant/Radiant.cpp b/radiant/Radiant.cpp index db1cde087c..b6dcc3173c 100644 --- a/radiant/Radiant.cpp +++ b/radiant/Radiant.cpp @@ -18,7 +18,7 @@ class Radiant : _context(context) { // Set the stream references for rMessage(), redirect std::cout, etc. - applog::LogStream::InitialiseStreams(); + applog::LogStream::InitialiseStreams(getLogWriter()); } ~Radiant() diff --git a/radiant/RadiantApp.cpp b/radiant/RadiantApp.cpp index 8c9bb0a35d..2658485c21 100644 --- a/radiant/RadiantApp.cpp +++ b/radiant/RadiantApp.cpp @@ -50,9 +50,6 @@ bool RadiantApp::OnInit() _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif - // Set the stream references for rMessage(), redirect std::cout, etc. - applog::LogStream::InitialiseStreams(); - // Stop wx's unhelpful debug messages about missing keyboard accel // strings from cluttering up the console wxLog::SetLogLevel(wxLOG_Warning); @@ -67,6 +64,8 @@ bool RadiantApp::OnInit() _coreModule.reset(new module::CoreModule(_context)); auto* radiant = _coreModule->get(); + + module::initialiseStreams(radiant->getLogWriter()); } catch (module::CoreModule::FailureException & ex) { @@ -151,7 +150,9 @@ int RadiantApp::OnExit() // Close the log file if (_logFile) { + _logFile->close(); _coreModule->get()->getLogWriter().detach(_logFile.get()); + _logFile.reset(); } _coreModule.reset(); diff --git a/radiant/log/COutRedirector.cpp b/radiant/log/COutRedirector.cpp index 03b2c32766..e7cfeae6c3 100644 --- a/radiant/log/COutRedirector.cpp +++ b/radiant/log/COutRedirector.cpp @@ -4,35 +4,41 @@ namespace applog { -COutRedirector::COutRedirector() { +COutRedirector::COutRedirector(ILogWriter& logWriter) +{ // Remember the old std::cout buffer _oldCOutStreamBuf = std::cout.rdbuf(); _oldCErrStreamBuf = std::cerr.rdbuf(); - std::cout.rdbuf(getGlobalOutputStream().rdbuf()); - std::cerr.rdbuf(getGlobalErrorStream().rdbuf()); + std::cout.rdbuf(logWriter.getLogStream(LogLevel::Standard).rdbuf()); + std::cerr.rdbuf(logWriter.getLogStream(LogLevel::Error).rdbuf()); } -COutRedirector::~COutRedirector() { +COutRedirector::~COutRedirector() +{ std::cout.rdbuf(_oldCOutStreamBuf); std::cerr.rdbuf(_oldCErrStreamBuf); } // A call to init() will redirect the std::cout output to the log -void COutRedirector::init() { - if (InstancePtr() == NULL) { - InstancePtr() = COutRedirectorPtr(new COutRedirector); +void COutRedirector::init(ILogWriter& logWriter) +{ + if (!InstancePtr()) + { + InstancePtr().reset(new COutRedirector(logWriter)); } } // A call to destroy() will stop redirecting std::cout -void COutRedirector::destroy() { +void COutRedirector::destroy() +{ // Clear the shared_ptr, this will disable all redirects - InstancePtr() = COutRedirectorPtr(); + InstancePtr().reset(); } // Contains the static singleton instance -COutRedirectorPtr& COutRedirector::InstancePtr() { +COutRedirectorPtr& COutRedirector::InstancePtr() +{ static COutRedirectorPtr _instancePtr; return _instancePtr; } diff --git a/radiant/log/COutRedirector.h b/radiant/log/COutRedirector.h index 2f26b2bcf4..bebaeba567 100644 --- a/radiant/log/COutRedirector.h +++ b/radiant/log/COutRedirector.h @@ -15,11 +15,11 @@ class COutRedirector std::streambuf* _oldCOutStreamBuf; std::streambuf* _oldCErrStreamBuf; public: - COutRedirector(); + COutRedirector(ILogWriter& logWriter); ~COutRedirector(); // A call to init() will redirect the std::cout output to the log - static void init(); + static void init(ILogWriter& logWriter); // A call to destroy() will stop redirecting std::cout static void destroy(); diff --git a/radiant/log/LogFile.cpp b/radiant/log/LogFile.cpp index d80995f2af..4d1a177818 100644 --- a/radiant/log/LogFile.cpp +++ b/radiant/log/LogFile.cpp @@ -23,30 +23,6 @@ LogFile::LogFile(const std::string& fullPath) : _logStream(_logFilePath.c_str()) {} -LogFile::~LogFile() -{ -#if defined(__linux__) - // put_time still unavailable even with GCC 4.9 - rMessage() << " Closing log file." << std::endl; -#else - std::time_t t = std::time(nullptr); - std::tm tm = *std::localtime(&t); - rMessage() << std::put_time(&tm, TIME_FMT) << " Closing log file." << std::endl; -#endif - - // Insert the last few remaining bytes into the stream - if (!_buffer.empty()) - { - _logStream << _buffer << std::endl; - _buffer.clear(); - } - - _logStream.flush(); - _logStream.close(); - - LogWriter::Instance().detach(this); -} - bool LogFile::isOpen() { return _logStream.good(); @@ -125,4 +101,26 @@ LogFilePtr& LogFile::InstancePtr() { } #endif +void LogFile::close() +{ +#if defined(__linux__) + // put_time still unavailable even with GCC 4.9 + rMessage() << " Closing log file." << std::endl; +#else + std::time_t t = std::time(nullptr); + std::tm tm = *std::localtime(&t); + rMessage() << std::put_time(&tm, TIME_FMT) << " Closing log file." << std::endl; +#endif + + // Insert the last few remaining bytes into the stream + if (!_buffer.empty()) + { + _logStream << _buffer << std::endl; + _buffer.clear(); + } + + _logStream.flush(); + _logStream.close(); +} + } // namespace applog diff --git a/radiant/log/LogFile.h b/radiant/log/LogFile.h index 25554d2c01..90e784516c 100644 --- a/radiant/log/LogFile.h +++ b/radiant/log/LogFile.h @@ -24,8 +24,6 @@ class LogFile : */ LogFile(const std::string& fullPath); - virtual ~LogFile(); - // Returns true if the log stream was successfully opened bool isOpen(); @@ -37,6 +35,8 @@ class LogFile : * called by the LogWriter class, but it can be called independently. */ void writeLog(const std::string& outputStr, LogLevel level) override; + + void close(); }; } // namespace applog diff --git a/radiant/log/LogStream.cpp b/radiant/log/LogStream.cpp index fe329d956e..731812253d 100644 --- a/radiant/log/LogStream.cpp +++ b/radiant/log/LogStream.cpp @@ -41,29 +41,29 @@ std::ostream& getGlobalWarningStream() return _stream; } -void LogStream::InitialiseStreams() +void LogStream::InitialiseStreams(ILogWriter& logWriter) { // Instantiate a temporary buffer, which copies the log until the // console is ready. The buffer's contents will then be copied over StringLogDevice::InstancePtr() = std::make_shared(); - GlobalOutputStream().setStream(getGlobalOutputStream()); - GlobalWarningStream().setStream(getGlobalWarningStream()); - GlobalErrorStream().setStream(getGlobalErrorStream()); + GlobalOutputStream().setStream(logWriter.getLogStream(applog::LogLevel::Standard)); + GlobalWarningStream().setStream(logWriter.getLogStream(applog::LogLevel::Warning)); + GlobalErrorStream().setStream(logWriter.getLogStream(applog::LogLevel::Error)); #ifndef NDEBUG - GlobalDebugStream().setStream(getGlobalOutputStream()); + GlobalDebugStream().setStream(logWriter.getLogStream(applog::LogLevel::Verbose)); #endif - GlobalOutputStream().setLock(GetStreamLock()); - GlobalWarningStream().setLock(GetStreamLock()); - GlobalErrorStream().setLock(GetStreamLock()); - GlobalDebugStream().setLock(GetStreamLock()); + GlobalOutputStream().setLock(logWriter.getStreamLock()); + GlobalWarningStream().setLock(logWriter.getStreamLock()); + GlobalErrorStream().setLock(logWriter.getStreamLock()); + GlobalDebugStream().setLock(logWriter.getStreamLock()); #if !defined(POSIX) || !defined(_DEBUG) // Redirect std::cout to the log, except on Linux debug builds where // logging to the console is more useful - COutRedirector::init(); + COutRedirector::init(logWriter); #endif } diff --git a/radiant/log/LogStream.h b/radiant/log/LogStream.h index 505ecb3827..e1d8db5e24 100644 --- a/radiant/log/LogStream.h +++ b/radiant/log/LogStream.h @@ -31,7 +31,7 @@ class LogStream : // Gets called immediately after entering main() // Sets up the stream references for rMessage(), redirects std::cout, etc. - static void InitialiseStreams(); + static void InitialiseStreams(ILogWriter& logWriter); // Hands back the original streambuf to std::cout static void ShutdownStreams(); diff --git a/radiant/log/LogWriter.cpp b/radiant/log/LogWriter.cpp index 5d91c2b405..79f84b98e8 100644 --- a/radiant/log/LogWriter.cpp +++ b/radiant/log/LogWriter.cpp @@ -1,6 +1,18 @@ #include "LogWriter.h" -namespace applog { +#include +#include + +namespace applog +{ + +LogWriter::LogWriter() +{ + for (auto level : AllLogLevels) + { + _streams.emplace(level, level); + } +} void LogWriter::write(const char* p, std::size_t length, LogLevel level) { @@ -14,6 +26,17 @@ void LogWriter::write(const char* p, std::size_t length, LogLevel level) } } +std::ostream& LogWriter::getLogStream(LogLevel level) +{ + assert(_streams.find(level) != _streams.end()); + return _streams.at(level); +} + +std::mutex& LogWriter::getStreamLock() +{ + return LogStream::GetStreamLock(); +} + void LogWriter::attach(ILogDevice* device) { _devices.insert(device); diff --git a/radiant/log/LogWriter.h b/radiant/log/LogWriter.h index f3f959fdcb..3544289280 100644 --- a/radiant/log/LogWriter.h +++ b/radiant/log/LogWriter.h @@ -1,8 +1,11 @@ #pragma once #include +#include #include "ilogwriter.h" +#include "LogStream.h" + namespace applog { @@ -14,13 +17,20 @@ class LogWriter : typedef std::set LogDevices; LogDevices _devices; + std::map _streams; + public: + LogWriter(); + /** * greebo: Writes the given buffer p with the given length to the * various output devices (i.e. Console and Log file). */ void write(const char* p, std::size_t length, LogLevel level) override; + std::ostream& getLogStream(LogLevel level) override; + std::mutex& getStreamLock() override; + /** * greebo: Use these methods to attach/detach a log device from the * writer class. After attaching a device, all log output