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?
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 a Logger class (which just takes strings and outputs then to wherever you need them to go) from a LogMessage 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 destructs
Let me know what you think:
#include <iostream>
#include <sstream>
#include <string>
// logger class
// this is not complete, it exists just to illustrate the LogIt function
class Logger
{
public:
void LogIt(const std::string & s)
{
std::cout << s << std::endl;
}
};
// builds a logging message; outputs it in the destructor
class LogMessage
{
public:
// constructor
// takes identifying info of message. You can add log level if needed
LogMessage(const char * file, const char * function, int line)
{
os << file << ": " << function << '(' << line << ") ";
}
// output operator
template<typename T>
LogMessage & operator<<(const T & t)
{
os << t;
return *this;
}
// output message to Logger
~LogMessage()
{
Logger logger; // get logger here (perhaps it's a singleton?)
logger.LogIt(os.str());
}
private:
std::ostringstream os;
};
int main()
{
// example usage
// typically this is invoked via a simple macro to reduce typing of the LogMessage constructor
LogMessage(__FILE__, __func__, __LINE__) << "this is an int " << 5;
}
You might have a LoggerAt class with a LoggerAt(const char*filename, int lineno) constructor (perhaps a subclass of std::ostringstream, etc...), then define
#define LOG(Out) do {LoggerAt(__FILE__,__LINE__) \
<< Out << std::endl; }while(0)
In some of my C++ projects I have coded:
void mom_inform_at(const char*fil, int lin, std::ostringstream& out)
{ out.flush();
std::clog << fil << ":" << lin
<< " INFORM: " << out.str() << std::endl ;
}
#define MOM_INFORM_AT(Fil,Lin,Output) do { \
std::ostringstream out_##Lin; \
out_##Lin << mom_outlog << Output ; \
mom_inform_at(Fil,Lin,out_##Lin); \
} while(0)
#define MOM_INFORM_AT_BIS(Fil,Lin,Output) \
MOM_INFORM_AT(Fil,Lin,Output)
#define MOM_INFORM(Out) \
MOM_INFORM_AT_BIS(__FILE__,__LINE__,Out)
And using something like MOM_INFORM("x=" << " point:" << pt); where you could imagine the usual Point pt; example with appropriate std::ostream& operator << (std::ostream&out, const Point&point) function.
Notice that to use conveniently __FILE__ and __LINE__ you'll better use macros.
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
class CustomOstream : public std::ostream
{
public:
static CustomOstream& endl( CustomOstream& out )
{
return out;
}
};
and changed macro to use the endl function from CustomOstream
#define __FILENAME__ (strrchr(__FILE__,'/') ? strrchr(__FILE__,'/') + 1 : __FILE__)
#define logendl \
((ToolLogger::fileName = __FILENAME__).empty() ? "" : "") \
<< ((ToolLogger::line = __LINE__) ? "" : "") \
<< ((ToolLogger::function = __func__).empty() ? "" : "") \
<< ToolLogger::CustomOstream::endl
Also the operator<< from the main class has been changed
ToolLogger& operator<< (CustomOstream& (*f)(CustomOstream&))
{
// do something //
return *this;
}
Now the logger can be used just like I wanted
log << "some data" << logendl; // correct //
log << "some data" << std::endl; // won't compile -> correct //
来源:https://stackoverflow.com/questions/33712798/c-style-logger-that-supports-line-macro-and-others