Skip to content

Commit

Permalink
#5231: Global log stream references can be obtained via the ILogWrite…
Browse files Browse the repository at this point in the history
…r interface.
  • Loading branch information
codereader committed Apr 28, 2020
1 parent f0909e1 commit a99f360
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 54 deletions.
15 changes: 15 additions & 0 deletions include/ilogwriter.h
@@ -1,6 +1,7 @@
#pragma once

#include <string>
#include <mutex>

namespace applog
{
Expand Down Expand Up @@ -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
{
Expand All @@ -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
Expand Down
20 changes: 20 additions & 0 deletions include/imodule.h
Expand Up @@ -13,6 +13,7 @@
#include <vector>

#include "itextstream.h"
#include "ilogwriter.h"

/**
* \defgroup module Module system
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion radiant/Radiant.cpp
Expand Up @@ -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()
Expand Down
7 changes: 4 additions & 3 deletions radiant/RadiantApp.cpp
Expand Up @@ -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);
Expand All @@ -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)
{
Expand Down Expand Up @@ -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();
Expand Down
26 changes: 16 additions & 10 deletions radiant/log/COutRedirector.cpp
Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions radiant/log/COutRedirector.h
Expand Up @@ -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();
Expand Down
46 changes: 22 additions & 24 deletions radiant/log/LogFile.cpp
Expand Up @@ -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();
Expand Down Expand Up @@ -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
4 changes: 2 additions & 2 deletions radiant/log/LogFile.h
Expand Up @@ -24,8 +24,6 @@ class LogFile :
*/
LogFile(const std::string& fullPath);

virtual ~LogFile();

// Returns true if the log stream was successfully opened
bool isOpen();

Expand All @@ -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
20 changes: 10 additions & 10 deletions radiant/log/LogStream.cpp
Expand Up @@ -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<StringLogDevice>();

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
}

Expand Down
2 changes: 1 addition & 1 deletion radiant/log/LogStream.h
Expand Up @@ -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();
Expand Down
25 changes: 24 additions & 1 deletion radiant/log/LogWriter.cpp
@@ -1,6 +1,18 @@
#include "LogWriter.h"

namespace applog {
#include <cassert>
#include <tuple>

namespace applog
{

LogWriter::LogWriter()
{
for (auto level : AllLogLevels)
{
_streams.emplace(level, level);
}
}

void LogWriter::write(const char* p, std::size_t length, LogLevel level)
{
Expand All @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions radiant/log/LogWriter.h
@@ -1,8 +1,11 @@
#pragma once

#include <set>
#include <map>
#include "ilogwriter.h"

#include "LogStream.h"

namespace applog
{

Expand All @@ -14,13 +17,20 @@ class LogWriter :
typedef std::set<ILogDevice*> LogDevices;
LogDevices _devices;

std::map<LogLevel, LogStream> _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
Expand Down

0 comments on commit a99f360

Please sign in to comment.