//#pragma once #ifndef SINGLETONLOGGER_H #define SINGLETONLOGGER_H ///////////////////////////////////////////////////////////////////// // SingletonLogger.h - Provides logging to multiple streams // // ver 1.1 // // Jim Fawcett, CSE687 - Object Oriented Design, Fall 2018 // ///////////////////////////////////////////////////////////////////// /* * Package Operations: * ------------------- * This package defines Logger: * - A singleton logger that supports one logger for each category (int) * and supports multiple std::iostreams, with each write sending logs * to each of its streams. Logger will accept any stream derived from * std::iostream, e.g., ofstream and ostringstream. * - NoLock<0>: Logger<0,NoLock> definded in ISingletonLogger.h * A class used to provide a locking policy for loggers. It provides * empty lock and unlock methods, intended for use in a single-threaded * environment. The empty methods will be optimized away by the compiler. * - Lock<0>: Logger<0,Lock> defined in ISingletonLogger.h * A class used to provide a locking policy for loggers. It provides * lock and unlock methods that use a std::mutex to provide mutually * exclusive access to its instantiation method, getInstance. * * Required Files: * --------------- * SingletonLogger.h, SingletonLogger.cpp (needed only for testing) * ISingletonLogger.h * DateTime.h, DateTime.cpp * * Maintenance History: * -------------------- * ver 1.1 : 07 Oct 2018 * - added implementation of interface * ver 1.0 : 23 Sep 2018 * - first release */ #include #include #include // defines std::getline(std::istream, std::string) #include #include #include #include #include "ISingletonLogger.h" #include "../DateTime/DateTime.h" namespace SLogger { using Streams = std::vector; /////////////////////////////////////////////////////////////////// // Logger class // - Thread-safe singleton // - supports logging to multiple streams with a single write // - int template parameter supports multiple logs shared by // the value of that parameter. // - Locker template parameter supports removing locking in // single-threaded environments. // - uses template template parameter to ensure that // Logger and Locker use the same Category using Streams = std::vector; using Terminator = std::string; template class Locker> class Logger : public ILogger { public: Logger(const Logger&) = delete; Logger& operator=(const Logger&) = delete; void addStream(std::ostream* pStream) override; bool usingStream(std::ostream* pStream) override; bool removeStream(std::ostream* pStream) override; void setTerminator(const Terminator& term) override; void setAuthor(const std::string& name) override; void writeHead(const std::string& msg) override; void write(const std::string& text) override; void writeTail(const std::string& msg = "end of log") override; static Logger* getInstance(); private: Logger() { addStream(&std::cout); } static Logger* instance_; static Locker locker_; Streams streams_; Terminator trm_ = "\n "; // default item terminator std::string author_ = "no author"; }; // define static data members template class Locker> Logger* Logger::instance_; template class Locker> Locker Logger::locker_; // define methods //----< register a stream - adds to default std::cout >------------ template class Locker> void Logger::addStream(std::ostream* pStream) { streams_.push_back(pStream); } //----< is pStream registered with logger? >----------------------- template class Locker> bool Logger::usingStream(std::ostream* pStream) { Streams::iterator iter = streams_.begin(); for (size_t i = 0; i < streams_.size(); ++i) { if (pStream == streams_[i]) { return true; } ++iter; } return false; } //----< remove stream - can use to remove std::cout >-------------- template class Locker> bool Logger::removeStream(std::ostream* pStream) { Streams::iterator iter = streams_.begin(); for (size_t i = 0; i < streams_.size(); ++i) { if (pStream == streams_[i]) { streams_.erase(iter); return true; } ++iter; } return false; } //----< set log write terminator - always added to write >--------- template class Locker> void Logger::setTerminator(const Terminator& term) { trm_ = term; } //----< add log author's name >------------------------------------ template class Locker> void Logger::setAuthor(const std::string& name) { author_ = name; } //----< write first log entry with message >----------------------- template class Locker> void Logger::writeHead(const std::string& msg) { std::string headerMsg = msg + " : " + author_ + trm_; headerMsg += Utilities::DateTime().now() + trm_; for (auto pStrm_ : streams_) { *pStrm_ << headerMsg; } } //----< write log entry >------------------------------------------ template class Locker> void Logger::write(const std::string& text) { std::string logMsg = text + trm_; for (auto pStrm_ : streams_) *pStrm_ << logMsg; } //----< write last entry of log >---------------------------------- template class Locker> void Logger::writeTail(const std::string& msg) { for (auto pStrm_ : streams_) *pStrm_ << msg.c_str(); } //----< get pointer to singleton logger >-------------------------- // // Thread-safe singleton access: // - Does not attempt to improve performance by double-check locking // - That may fail occasionally, in C++, due to caching of instance_. // - Since accesses are rare, usually only a very few times per // execution, performance degradation is very small. template class Locker> Logger* Logger::getInstance() { Locker::lock(); if (instance_ == nullptr) { instance_ = new Logger(); } Locker::unlock(); return instance_; } } #endif