Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4847 logger singleton 3.1 #5119

Merged
merged 12 commits into from
Mar 27, 2024
5 changes: 3 additions & 2 deletions src/utilities/core/LogSink.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ namespace openstudio {

namespace detail {
class LogSink_Impl;
}
class Logger_Impl;
} // namespace detail

/// LogSink is a class for managing sinks for log messages, e.g. files, streams, etc.
class UTILITIES_API LogSink
Expand Down Expand Up @@ -76,7 +77,7 @@ class UTILITIES_API LogSink
void setFormatter(const boost::log::formatter& fmter);

protected:
friend class LoggerImpl;
friend class detail::Logger_Impl;

// does not register in the global logger
LogSink();
Expand Down
201 changes: 121 additions & 80 deletions src/utilities/core/Logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,134 +4,175 @@
***********************************************************************************************************************/

#include "Logger.hpp"
#include "Logger_Impl.hpp"

#include <boost/log/common.hpp>
#include <boost/log/attributes/function.hpp>
#include <boost/log/attributes/clock.hpp>

#include <boost/core/null_deleter.hpp>
#include <utility>

namespace sinks = boost::log::sinks;
namespace keywords = boost::log::keywords;

namespace openstudio {

std::shared_ptr<LoggerImpl> Logger::obj = nullptr;

/// convenience function for SWIG, prefer macros in C++
void logFree(LogLevel level, const std::string& channel, const std::string& message) {
BOOST_LOG_SEV(openstudio::Logger::instance().loggerFromChannel(channel), level) << message;
}

LoggerImpl::LoggerImpl() {
// Make current thread id attribute available to logging
boost::log::core::get()->add_global_attribute("ThreadId", boost::log::attributes::make_function(&std::this_thread::get_id));

// We have to provide an null deleter to avoid destroying the global stream
boost::shared_ptr<std::ostream> stdOut(&std::cout, boost::null_deleter());
m_standardOutLogger.setStream(stdOut);
m_standardOutLogger.setLogLevel(Warn);
this->addSink(m_standardOutLogger.sink());

// We have to provide an empty deleter to avoid destroying the global stream
boost::shared_ptr<std::ostream> stdErr(&std::cerr, boost::null_deleter());
m_standardErrLogger.setStream(stdErr);
m_standardErrLogger.setLogLevel(Warn);
//this->addSink(m_standardErrLogger.sink());
}
namespace detail {

LoggerImpl::~LoggerImpl() {
// unregister Qt message handler
//qInstallMsgHandler(consoleLogQtMessage);
}
Logger_Impl::Logger_Impl() {
// Make current thread id attribute available to logging
boost::log::core::get()->add_global_attribute("ThreadId", boost::log::attributes::make_function(&std::this_thread::get_id));

LogSink LoggerImpl::standardOutLogger() const {
std::shared_lock l{m_mutex};
// We have to provide an null deleter to avoid destroying the global stream
boost::shared_ptr<std::ostream> stdOut(&std::cout, boost::null_deleter());
m_standardOutLogger.setStream(stdOut);
m_standardOutLogger.setLogLevel(Warn);
this->addSink(m_standardOutLogger.sink());

return m_standardOutLogger;
}
// We have to provide an empty deleter to avoid destroying the global stream
boost::shared_ptr<std::ostream> stdErr(&std::cerr, boost::null_deleter());
m_standardErrLogger.setStream(stdErr);
m_standardErrLogger.setLogLevel(Warn);
//this->addSink(m_standardErrLogger.sink());
}

LogSink LoggerImpl::standardErrLogger() const {
std::shared_lock l{m_mutex};
LogSink Logger_Impl::standardOutLogger() const {
std::shared_lock l{m_mutex};

return m_standardErrLogger;
}
return m_standardOutLogger;
}

LogSink Logger_Impl::standardErrLogger() const {
std::shared_lock l{m_mutex};

LoggerType& LoggerImpl::loggerFromChannel(const LogChannel& logChannel) {
std::shared_lock l{m_mutex};
return m_standardErrLogger;
}

LoggerType& Logger_Impl::loggerFromChannel(const LogChannel& logChannel) {
std::shared_lock l{m_mutex};

auto it = m_loggerMap.find(logChannel);
if (it == m_loggerMap.end()) {
//LoggerType newLogger(keywords::channel = logChannel, keywords::severity = Debug);
LoggerType newLogger(keywords::channel = logChannel);
auto it = m_loggerMap.find(logChannel);
if (it == m_loggerMap.end()) {
//LoggerType newLogger(keywords::channel = logChannel, keywords::severity = Debug);
LoggerType newLogger(keywords::channel = logChannel);

std::pair<LogChannel, LoggerType> newPair(logChannel, newLogger);
std::pair<LogChannel, LoggerType> newPair(logChannel, newLogger);

// Drop the read lock and grab a write lock - we need to add the new file to the map
// this will reduce contention when multiple threads trying to log at once.
l.unlock();
std::unique_lock l2{m_mutex};
// Drop the read lock and grab a write lock - we need to add the new file to the map
// this will reduce contention when multiple threads trying to log at once.
l.unlock();
std::unique_lock l2{m_mutex};

std::pair<LoggerMapType::iterator, bool> inserted = m_loggerMap.insert(newPair);
std::pair<LoggerMapType::iterator, bool> inserted = m_loggerMap.insert(newPair);

return inserted.first->second;
return inserted.first->second;
}

return it->second;
}

return it->second;
}
bool Logger_Impl::findSink(boost::shared_ptr<LogSinkBackend> sink) {
std::unique_lock l{m_mutex};

bool LoggerImpl::findSink(boost::shared_ptr<LogSinkBackend> sink) {
std::unique_lock l{m_mutex};
auto it = m_sinks.find(sink);

auto it = m_sinks.find(sink);
return (it != m_sinks.end());
}

return (it != m_sinks.end());
}
void Logger_Impl::addSink(boost::shared_ptr<LogSinkBackend> sink) {
std::shared_lock l{m_mutex};

void LoggerImpl::addSink(boost::shared_ptr<LogSinkBackend> sink) {
std::shared_lock l{m_mutex};
auto it = m_sinks.find(sink);
if (it == m_sinks.end()) {

auto it = m_sinks.find(sink);
if (it == m_sinks.end()) {
// Drop the read lock and grab a write lock - we need to add the new file to the map
// this will reduce contention when multiple threads trying to log at once.
l.unlock();
std::unique_lock l2{m_mutex};

// Drop the read lock and grab a write lock - we need to add the new file to the map
// this will reduce contention when multiple threads trying to log at once.
l.unlock();
std::unique_lock l2{m_mutex};
m_sinks.insert(sink);

// Register the sink in the logging core
boost::log::core::get()->add_sink(sink);
}
}

m_sinks.insert(sink);
void Logger_Impl::removeSink(boost::shared_ptr<LogSinkBackend> sink) {
std::shared_lock l{m_mutex};

// Register the sink in the logging core
boost::log::core::get()->add_sink(sink);
auto it = m_sinks.find(sink);
if (it != m_sinks.end()) {

// Drop the read lock and grab a write lock - we need to add the new file to the map
// this will reduce contention when multiple threads trying to log at once.
l.unlock();
std::unique_lock l2{m_mutex};

m_sinks.erase(it);

// Register the sink in the logging core
boost::log::core::get()->remove_sink(sink);
}
}

void Logger_Impl::addTimeStampToLogger() {
std::unique_lock l{m_mutex};

// Add a TimeStamp attribute, same as boost::log::add_common_attributes() would do
[[maybe_unused]] auto [it, inserted] = boost::log::core::get()->add_global_attribute("TimeStamp", boost::log::attributes::local_clock{});
// if (!use) {
// boost::log::core::get()->remove_global_attribute(it);
// }
}

} // namespace detail

Logger::Logger() : m_impl(std::shared_ptr<detail::Logger_Impl>(new detail::Logger_Impl())) {}

Logger& Logger::instance() {
static Logger instance;
return instance;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private constructor, and public ::instance()

}

void LoggerImpl::removeSink(boost::shared_ptr<LogSinkBackend> sink) {
std::shared_lock l{m_mutex};
/// get logger for standard out
LogSink Logger::standardOutLogger() const {
return m_impl->standardOutLogger();
}

auto it = m_sinks.find(sink);
if (it != m_sinks.end()) {
LogSink Logger::standardErrLogger() const {
return m_impl->standardErrLogger();
}

// Drop the read lock and grab a write lock - we need to add the new file to the map
// this will reduce contention when multiple threads trying to log at once.
l.unlock();
std::unique_lock l2{m_mutex};
/// get a logger from a LogChannel enumeration, if logChannel does not
/// exist a new logger will be set up at the default level
LoggerType& Logger::loggerFromChannel(const LogChannel& logChannel) {
return m_impl->loggerFromChannel(logChannel);
}

m_sinks.erase(it);
/// is the sink found in the logging core
bool Logger::findSink(boost::shared_ptr<LogSinkBackend> sink) {
return m_impl->findSink(std::move(sink));
}

// Register the sink in the logging core
boost::log::core::get()->remove_sink(sink);
}
/// adds a sink to the logging core, equivalent to logSink.enable()
void Logger::addSink(boost::shared_ptr<LogSinkBackend> sink) {
m_impl->addSink(std::move(sink));
}

void LoggerImpl::addTimeStampToLogger() {
std::unique_lock l{m_mutex};
/// removes a sink to the logging core, equivalent to logSink.disable()
void Logger::removeSink(boost::shared_ptr<LogSinkBackend> sink) {
m_impl->removeSink(std::move(sink));
}

// Add a TimeStamp attribute, same as boost::log::add_common_attributes() would do
[[maybe_unused]] auto [it, inserted] = boost::log::core::get()->add_global_attribute("TimeStamp", boost::log::attributes::local_clock{});
// if (!use) {
// boost::log::core::get()->remove_global_attribute(it);
// }
// cppcheck-suppress functionConst
void Logger::addTimeStampToLogger() {
m_impl->addTimeStampToLogger();
}

} // namespace openstudio
69 changes: 19 additions & 50 deletions src/utilities/core/Logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,14 @@

#include "../UtilitiesAPI.hpp"

#include "Singleton.hpp"
#include "Exception.hpp"
#include "Compare.hpp"
#include "Compare.hpp" // NOTE that this this is not needed for this header but so many compilation errors if I omit it
// since other files transitively use Compare
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An unfortunate discovery. Cleaning up ours headers would be good, but that's not what I'm trying to achieve here.

#include "LogMessage.hpp"
#include "LogSink.hpp"

#include <boost/shared_ptr.hpp>
#include "Exception.hpp"

#include <sstream>
#include <set>
#include <map>
#include <memory>
#include <shared_mutex>

/// defines method logChannel() to get a logger for a class
#define REGISTER_LOGGER(__logChannel__) \
Expand Down Expand Up @@ -51,21 +47,24 @@
namespace openstudio {

class OSWorkflow;
class LogSink;
namespace detail {
class Logger_Impl;
class LogSink_Impl;
} // namespace detail

/// convenience function for SWIG, prefer macros in C++
UTILITIES_API void logFree(LogLevel level, const std::string& channel, const std::string& message);

class Logger;

/** Primary logging class.
*/
class UTILITIES_API LoggerImpl
class UTILITIES_API Logger
{
friend class Logger;

public:
/// destructor, cleans up, writes xml file footers, etc
~LoggerImpl();
static Logger& instance();

Logger(const Logger& other) = delete;
Logger(Logger&& other) = delete;
Logger& operator=(const Logger&) = delete;
Logger& operator=(Logger&&) = delete;

/// get logger for standard out
LogSink standardOutLogger() const;
Expand Down Expand Up @@ -94,40 +93,10 @@ class UTILITIES_API LoggerImpl
void addTimeStampToLogger();

private:
/// private constructor
LoggerImpl();

mutable std::shared_mutex m_mutex;

/// standard out logger
LogSink m_standardOutLogger;

/// standard err logger
LogSink m_standardErrLogger;
Logger();
~Logger() = default;

/// map of std::string to logger
using LoggerMapType = std::map<std::string, LoggerType, openstudio::IstringCompare>;
LoggerMapType m_loggerMap;

/// current sinks, kept here so don't destruct when LogSink wrapper goes out of scope
using SinkSetType = std::set<boost::shared_ptr<LogSinkBackend>>;
SinkSetType m_sinks;
};

class UTILITIES_API Logger
{
public:
Logger() = delete;

static LoggerImpl& instance() {
if (!obj) {
obj = std::shared_ptr<LoggerImpl>(new LoggerImpl());
}
return *obj;
}

private:
static std::shared_ptr<LoggerImpl> obj;
std::shared_ptr<detail::Logger_Impl> m_impl;
Copy link
Collaborator Author

@jmarrec jmarrec Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the Logger is a Meyers singleton, I'm not sure whether the Logger_Impl class is really needed at all. But it feels familiar at least, and can allow us to shorten the public includes.

The dowside is that there's an extra method call for each (the forward to impl)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I don't think we really need an Impl at all.

};

} // namespace openstudio
Expand Down