Introduction
This tip presents a simple class for printing of log information. The developed class uses a variadic template
of C++11 and std::lock_guard<std::mutex>
for flexible printing of log information.
Background
In some my last projects, I needed about a simple class for printing some log information for checking the correctness of the program working. In some previous projects, I used ellipsis parameters - old technique of C:
void foo(parm_list, ...);
void foo(...);
It is an old technique which allows to type any amount of arguments without the need of overriding of method signature.
However, since starting to use C++11 on Visual Studio 2013 and MinGW 4.82, I found that my old solution with ellipsis parameters is not working. I needed code for logging any amount information at the specific point of code, and it is nonsense to write hundred overridings of print out method. I had decided to use a variadic template
of C++11 for getting the solution which close to the ellipsis parameters technique of C programming language.
Using the Code
The whole code of the class LogPringOut
is presented in the next listing:
#include <iostream>
#include <map>
#include <mutex>
class LogPrintOut
{
public:
enum Level
{
INFO_LEVEL=1,
ERROR_LEVEL=2
};
static LogPrintOut& getInstance()
{
static LogPrintOut instance;
return instance;
}
void setVerbose(bool state)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
mVerbose = state;
}
bool setPtrLogPrintOutStream(Level level, std::wostream* pwostream, bool selfReleased)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
bool result = false;
if (pwostream == nullptr)
return result;
releaseStream(level);
Stream stream;
stream.pwostream = pwostream;
stream.selfReleased = selfReleased;
mLevelStreams[level] = stream;
result = true;
return result;
}
bool releaseLogPrintOutStream(Level level)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
return releaseStream(level);
}
template<typename T>
inline bool printOutln(Level level, const T *data)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
bool result = false;
if (mVerbose)
{
auto itr = mLevelStreams.find(level);
if (itr == mLevelStreams.end())
return result;
if (itr->second.pwostream == nullptr)
return result;
switch (level)
{
case CaptureManager::LogPrintOut::INFO_LEVEL:
(*(itr->second.pwostream)) << L"INFO_LEVEL: ";
break;
case CaptureManager::LogPrintOut::ERROR_LEVEL:
(*(itr->second.pwostream)) << L"ERROR_LEVEL: ";
break;
default:
break;
}
(*(itr->second.pwostream))<<(data) << std::endl;
itr->second.pwostream->flush();
result = true;
}
return result;
}
template<typename T>
inline bool printOutln(Level level, const T data)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
bool result = false;
if (mVerbose)
{
auto itr = mLevelStreams.find(level);
if (itr == mLevelStreams.end())
return result;
if (itr->second.pwostream == nullptr)
return result;
switch (level)
{
case CaptureManager::LogPrintOut::INFO_LEVEL:
(*(itr->second.pwostream)) << L"INFO_LEVEL: ";
break;
case CaptureManager::LogPrintOut::ERROR_LEVEL:
(*(itr->second.pwostream)) << L"ERROR_LEVEL: ";
break;
default:
break;
}
(*(itr->second.pwostream)) << (data) << std::endl;
itr->second.pwostream->flush();
result = true;
}
return result;
}
template <typename T, typename... Args>
inline bool printOutln(Level level, const T data, const Args... rest)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
bool result = false;
if (mVerbose)
{
auto itr = mLevelStreams.find(level);
if (itr == mLevelStreams.end())
return result;
if (itr->second.pwostream == nullptr)
return result;
switch (level)
{
case CaptureManager::LogPrintOut::INFO_LEVEL:
(*(itr->second.pwostream)) << L"INFO_LEVEL: ";
break;
case CaptureManager::LogPrintOut::ERROR_LEVEL:
(*(itr->second.pwostream)) << L"ERROR_LEVEL: ";
break;
default:
break;
}
write(itr->second.pwostream, data, rest...);
itr->second.pwostream->operator<<(std::endl);
itr->second.pwostream->flush();
result = true;
}
return result;
}
template <typename T, typename... Args>
inline bool printOutln(Level level, const T *data, const Args... rest)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
bool result = false;
if (mVerbose)
{
auto itr = mLevelStreams.find(level);
if (itr == mLevelStreams.end())
return result;
if (itr->second.pwostream == nullptr)
return result;
switch (level)
{
case CaptureManager::LogPrintOut::INFO_LEVEL:
(*(itr->second.pwostream)) << L"INFO_LEVEL: ";
break;
case CaptureManager::LogPrintOut::ERROR_LEVEL:
(*(itr->second.pwostream)) << L"ERROR_LEVEL: ";
break;
default:
break;
}
write(itr->second.pwostream, data, rest...);
itr->second.pwostream->operator<<(std::endl);
itr->second.pwostream->flush();
result = true;
}
return result;
}
template<typename T>
inline bool printOut(Level level, const T *data)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
bool result = false;
if (mVerbose)
{
auto itr = mLevelStreams.find(level);
if (itr == mLevelStreams.end())
return result;
if (itr->second.pwostream == nullptr)
return result;
switch (level)
{
case CaptureManager::LogPrintOut::INFO_LEVEL:
(*(itr->second.pwostream)) << L"INFO_LEVEL: ";
break;
case CaptureManager::LogPrintOut::ERROR_LEVEL:
(*(itr->second.pwostream)) << L"ERROR_LEVEL: ";
break;
default:
break;
}
(*(itr->second.pwostream)) << (data);
itr->second.pwostream->flush();
result = true;
}
return result;
}
template<typename T>
inline bool printOut(Level level, const T data)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
bool result = false;
if (mVerbose)
{
auto itr = mLevelStreams.find(level);
if (itr == mLevelStreams.end())
return result;
if (itr->second.pwostream == nullptr)
return result;
switch (level)
{
case CaptureManager::LogPrintOut::INFO_LEVEL:
(*(itr->second.pwostream)) << L"INFO_LEVEL: ";
break;
case CaptureManager::LogPrintOut::ERROR_LEVEL:
(*(itr->second.pwostream)) << L"ERROR_LEVEL: ";
break;
default:
break;
}
(*(itr->second.pwostream)) << (data);
itr->second.pwostream->flush();
result = true;
}
return result;
}
template <typename T, typename... Args>
inline bool printOut(Level level, const T data, const Args... rest)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
bool result = false;
if (mVerbose)
{
auto itr = mLevelStreams.find(level);
if (itr == mLevelStreams.end())
return result;
if (itr->second.pwostream == nullptr)
return result;
switch (level)
{
case CaptureManager::LogPrintOut::INFO_LEVEL:
(*(itr->second.pwostream)) << L"INFO_LEVEL: ";
break;
case CaptureManager::LogPrintOut::ERROR_LEVEL:
(*(itr->second.pwostream)) << L"ERROR_LEVEL: ";
break;
default:
break;
}
write(itr->second.pwostream, data, rest...);
itr->second.pwostream->flush();
result = true;
}
return result;
}
template <typename T, typename... Args>
inline bool printOut(Level level, const T *data, const Args... rest)
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
bool result = false;
if (mVerbose)
{
auto itr = mLevelStreams.find(level);
if (itr == mLevelStreams.end())
return result;
if (itr->second.pwostream == nullptr)
return result;
switch (level)
{
case CaptureManager::LogPrintOut::INFO_LEVEL:
(*(itr->second.pwostream)) << L"INFO_LEVEL: ";
break;
case CaptureManager::LogPrintOut::ERROR_LEVEL:
(*(itr->second.pwostream)) << L"ERROR_LEVEL: ";
break;
default:
break;
}
write(itr->second.pwostream, data, rest...);
itr->second.pwostream->flush();
result = true;
}
return result;
}
private:
struct Stream
{
std::wostream* pwostream = nullptr;
bool selfReleased = false;
};
std::map<level, stream=""> mLevelStreams;
bool mVerbose = true;
std::mutex mLogPrintOutMutex;
LogPrintOut()
{
setPtrLogPrintOutStream(LogPrintOut::INFO_LEVEL, &std::wcout, false);
setPtrLogPrintOutStream(LogPrintOut::ERROR_LEVEL, &std::wcout, false);
}
~LogPrintOut()
{
std::lock_guard<std::mutex> lock(mLogPrintOutMutex);
releaseStreams();
}
LogPrintOut(const LogPrintOut&);
LogPrintOut& operator=(const LogPrintOut&);
void releaseStreams()
{
auto itr = mLevelStreams.begin();
for (; itr != mLevelStreams.end(); ++itr)
{
if (itr->second.selfReleased && itr->second.pwostream != nullptr)
{
itr->second.pwostream->flush();
delete itr->second.pwostream;
}
}
mLevelStreams.clear();
}
bool releaseStream(Level level)
{
bool result = false;
auto itr = mLevelStreams.find(level);
if (itr != mLevelStreams.end())
{
if (itr->second.selfReleased && itr->second.pwostream != nullptr)
{
itr->second.pwostream->flush();
delete itr->second.pwostream;
mLevelStreams.erase(itr);
result = true;
}
}
return result;
}
template<typename T>
inline void write(std::wostream* pwostream, const T *data)
{
(*pwostream) << (data);
}
template<typename T>
inline void write(std::wostream* pwostream, const T data)
{
(*pwostream) << (data);
}
template <typename T, typename... Args>
inline void write(std::wostream* pwostream, const T *data, const Args... rest)
{
(*pwostream) << (data);
write(pwostream, rest...);
}
template <typename T, typename... Args>
inline void write(std::wostream* pwostream, const T data, const Args... rest)
{
(*pwostream) << (data);
write(pwostream, rest...);
}
};
The class has the template methods and is defined in header file.
The class is written on singelton pattern and has main access method LogPrintOut& getInstance()
. Method void setVerbose(bool state)
allows to control of enable and disable printing out. Enumeration Level
is used for defining of target out for printing - INFO_LEVEL, ERROR_LEVEL
. The method bool setPtrLogPrintOutStream(Level level, std::wostream* pwostream, bool selfReleased)
is used for setting to the specific level, pointer on output stream pwostream and flag for manual releasing of output stream pointer selfReleased.
The next code presents how to set and release the output stream pointer.
wostringstream wost;
auto result = LogPrintOut::getInstance().setPtrLogPrintOutStream
(LogPrintOut::INFO_LEVEL, &wost, false);
result = LogPrintOut::getInstance().setPtrLogPrintOutStream
(LogPrintOut::INFO_LEVEL, new std::wofstream("app.log"), true);
result = LogPrintOut::getInstance().releaseLogPrintOutStream
(LogPrintOut::INFO_LEVEL);
The next main methods are an overridden form of the methods printOutln
and printOut
:
template<typename T>
inline bool printOutln(Level level, const T *data)
template<typename T>
inline bool printOutln(Level level, const T data)
template <typename T, typename... Args>
inline bool printOutln(Level level, const T *data, const Args... rest)
template <typename T, typename... Args>
inline bool printOutln(Level level, const T data, const Args... rest)
template<typename T>
inline bool printOut(Level level, const T *data)
template<typename T>
inline bool printOut(Level level, const T data)
template <typename T, typename... Args>
inline bool printOut(Level level, const T *data, const Args... rest)
template <typename T, typename... Args>
inline bool printOut(Level level, const T data, const Args... rest)
The method printOutln is used for print out information with the break the line and the method printOut is used for print out information without the break the line. Each of the methods has an overridden signature for using template variables by value - const T data
, and by pointer on - const T *data
. The main important part is using of typename... Args
signature - variadic template
of C++11. It allows to present the different types of arguments as massive of templates. Compilator unrolls that massive of templates by the next way:
template <typename T>
inline void write(std::wostream* pwostream, const T data)
{
(*pwostream) << (data);
}
template <typename T, typename... Args>
inline void write(std::wostream* pwostream, const T data, const Args... rest)
{
(*pwostream) << (data);
write(pwostream, rest...);
}
Compilator calls code of
template <typename T, typename... Args>
inline void write(std::wostream* pwostream, const T data, const Args... rest)
method till massive of templates has finished - in that case compiler will set code of
template <typename T>
inline void write(std::wostream* pwostream, const T data)
method. It means that compilator digs the massive of template recursively and build fast recursive code.
How it can be used? Look at the next code:
wostringstream wost;
bool result = LogPrintOut::getInstance().setPtrLogPrintOutStream(LogPrintOut::INFO_LEVEL, &wost, false);
LogPrintOut::getInstance().printOutln
(LogPrintOut::INFO_LEVEL, L"Line number: ", 9);
LogPrintOut::getInstance().printOutln
(LogPrintOut::INFO_LEVEL, 9, L"Line number: ");
LogPrintOut::getInstance().printOutln
(LogPrintOut::INFO_LEVEL, L"Line number: ", 9, L"Line number: ");
LogPrintOut::getInstance().printOutln
(LogPrintOut::INFO_LEVEL, L"Line number: ", L"Second argument");
LogPrintOut::getInstance().printOutln
(LogPrintOut::INFO_LEVEL, L"Line number: ", L"Second argument", 9);
So, you can see that we can change any order and type of arguments without changing the original signature of printOutln method - all such work is executed by compiler!!!. It is close enough to the ellipsis parameters technique and can be considered as replacement of it in some tasks.
You can say - it will work well with the primary buildin types like as int
, float
, double
, etc. How about the developer defined types like classes. This case is presented in the next code listing:
class TextClass
{
public:
TextClass(const wchar_t *pStr)
{
text = pStr;
}
~TextClass()
{
}
wstring getText() const
{
return text;
}
private:
wstring text;
};
wostream& operator<<(wostream& s, const TextClass & textClass)
{
return s << textClass.getText();
}
wostream& operator<<(wostream& s, const TextClass *ptrTextClass)
{
return s << ptrTextClass->getText();
}
.
.
.
.
wostringstream wost;
bool result = LogPrintOut::getInstance().setPtrLogPrintOutStream
(LogPrintOut::INFO_LEVEL, &wost, false);
LogPrintOut::getInstance().printOut(LogPrintOut::INFO_LEVEL,
TextClass(L"Stack instance of TextClass"));
TextClass *ptrTextClass = new TextClass(L"in the heap instance of TextClass");
LogPrintOut::getInstance().printOut(LogPrintOut::INFO_LEVEL, ptrTextClass);
LogPrintOut::getInstance().printOut(LogPrintOut::INFO_LEVEL,
ptrTextClass, TextClass(L" and Stack instance of TextClass"));
All we need is define the new signature for overridden method operator<<
of std::wostream
class for reference and pointer of the developer defined class.
Code of the LogPrintOut
class can be downloaded at this link.
Points of Interest
In some projects, I had multithread patterns of code and I included into the class LogPrintOut
primitive of multithread access synchronization - std::lock_guard<std::mutex> lock(mLogPrintOutMutex)
for simple management of printing of log information in the different threads.