I want to make a Logger that can be used like std::cout
, but I want to log some extra data like date, time, __LINE__
, __func__
, and __FILE__
which should be saved to the file automatically.
Example
ToolLogger log;
log << "some data" << std::endl;
Expected output
[14.11.2015 21:10:12.344 (main.cpp) (main,14): some data
Inadequate solution
To do this I have to put macros like __LINE__
direct in the line where I call my logger, otherwise the macros won't work correct. I found that I can replace std::endl
with my macro that will do this black magic like this:
#define __FILENAME__ (strrchr(__FILE__,'/') ? strrchr(__FILE__,'/') + 1 : __FILE__)
#define logendl \
((ToolLogger::fileName = __FILENAME__).empty() ? "" : "") \
<< ((ToolLogger::line = __LINE__) ? "" : "") \
<< ((ToolLogger::function = __func__).empty() ? "" : "") \
<< std::endl
The macro logendl
uses static variables from my ToolLogger
class to save the values of __LINE__
, __func__
and __FILE__
needed later. So actually using the logger will looks like this:
ToolLogger log;
log << "some data" << logendl;
In the class i have to overload the operator<<
to get this to work, and I need two of them. One for taking the normal values like std::string
or int
, and the other to take the std::endl
manipulator. Here is the most important things from my class:
class ToolLogger
{
public:
// standard operator<< //
template<typename T>
ToolLogger& operator<< (const T& str)
{
out << str;
return *this;
}
// operator<< for taking the std::endl manipulator //
typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
typedef CoutType& (*StandardEndLine)(CoutType&);
ToolLogger& operator<<(StandardEndLine manip)
{
// save fileName, line and function to the file //
// and all what is already in stringstream //
// clear stringstream //
return *this;
}
static string fileName;
static int line;
static string function;
private:
ofstream file;
std::stringstream out;
};
string ToolLogger::fileName;
int ToolLogger::line;
string ToolLogger::function;
Problem
The problem in this solution is that I can use my logger in two ways:
log << "some data" << logendl; // correct //
log << "some data" << std::endl; // compiles -> wrong /
So actually I need to remove the operator<<
from my class that takes std::endl
manipulator, and solve it other way, but how to do it? I was thinking about changing std::endl
in logendl
macro to other custom manipulator, and then this custom manipulator will do the work that is actually doing the operator<<
, but I have no idea how to do it. I'm looking for other solution, any suggestions?
I have solved my own problem. Other answers posted here may be better than main, but I wanted to use logger in a simple way just like in C++
std::cout
is used. Also my solution may not be optimal and may lead to other problems, but it meets my requirements.I have added a custom
std::ostream
and changed macro to use the
endl
function fromCustomOstream
Also the
operator<<
from the main class has been changedNow the logger can be used just like I wanted
Here's what I do. It kind of skirts your question. That is, is does away with having to define an
endl
. What I do is separate out aLogger
class (which just takes strings and outputs then to wherever you need them to go) from aLogMessage
class which builds a message.The benefits are:
Each class, on it's own, is pretty simple.
Very simple macros. I don't define the macro below but it's easy enough to do.
No need to define an
endl
. The message ends at the semicolon when the LogMessage class destructsLet me know what you think:
You might have a
LoggerAt
class with aLoggerAt(const char*filename, int lineno)
constructor (perhaps a subclass ofstd::ostringstream
, etc...), then defineIn some of my C++ projects I have coded:
And using something like
MOM_INFORM("x=" << " point:" << pt);
where you could imagine the usualPoint pt;
example with appropriatestd::ostream& operator << (std::ostream&out, const Point&point)
function.Notice that to use conveniently
__FILE__
and__LINE__
you'll better use macros.