Logger в стиле C++, поддерживающий макрос __LINE__ и другие
Я хочу сделать Logger, который можно использовать как std::cout
, но я хочу записать некоторые дополнительные данные, такие как дата, время, __LINE__
, __func__
, а также __FILE__
который должен быть сохранен в файл автоматически.
пример
ToolLogger log;
log << "some data" << std::endl;
Ожидаемый результат
[14.11.2015 21:10:12.344 (main.cpp) (main,14): some data
Неадекватное решение
Для этого я должен поставить макросы как __LINE__
прямо в строке, где я вызываю мой регистратор, иначе макросы не будут работать правильно. Я обнаружил, что могу заменить std::endl
с моим макросом, который будет делать эту черную магию следующим образом:
#define __FILENAME__ (strrchr(__FILE__,'/') ? strrchr(__FILE__,'/') + 1 : __FILE__)
#define logendl \
((ToolLogger::fileName = __FILENAME__).empty() ? "" : "") \
<< ((ToolLogger::line = __LINE__) ? "" : "") \
<< ((ToolLogger::function = __func__).empty() ? "" : "") \
<< std::endl
Макрос logendl
использует статические переменные из моего ToolLogger
класс для сохранения значений __LINE__
, __func__
а также __FILE__
нужно позже. Так что на самом деле использование логгера будет выглядеть так:
ToolLogger log;
log << "some data" << logendl;
В классе я должен перегружать operator<<
чтобы заставить это работать, и мне нужно два из них. Один для принятия нормальных значений, таких как std::string
или же int
, а другой взять std::endl
Манипулятор. Вот самые важные вещи из моего класса:
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;
проблема
Проблема в этом решении заключается в том, что я могу использовать свой регистратор двумя способами:
log << "some data" << logendl; // correct //
log << "some data" << std::endl; // compiles -> wrong /
Так что на самом деле мне нужно удалить operator<<
из моего класса, который берет std::endl
манипулятор, и решить его по-другому, но как это сделать? Я думал об изменении std::endl
в logendl
макрос в другой пользовательский манипулятор, а затем этот пользовательский манипулятор будет делать работу, которая на самом деле делает operator<<
, но я понятия не имею, как это сделать. Я ищу другое решение, какие-либо предложения?
3 ответа
Вот что я делаю. Это как бы обходит ваш вопрос. То есть это избавляет от необходимости определять endl
, Что я делаю, это выделить Logger
класс (который просто берет строки и выводит их туда, куда вам нужно, чтобы пойти) из LogMessage
класс, который создает сообщение.
Преимущества:
Каждый класс сам по себе довольно прост.
Очень простые макросы. Я не определяю макрос ниже, но это достаточно легко сделать.
Нет необходимости определять
endl
, Сообщение заканчивается точкой с запятой, когда класс LogMessage разрушает
Дайте мне знать, что вы думаете:
#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;
}
Вы могли бы иметь LoggerAt
класс с LoggerAt(const char*filename, int lineno)
конструктор (возможно, подкласс std::ostringstream
и т. д.), затем определите
#define LOG(Out) do {LoggerAt(__FILE__,__LINE__) \
<< Out << std::endl; }while(0)
В некоторых моих проектах на C++ я написал:
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)
И используя что-то вроде MOM_INFORM("x=" << " point:" << pt);
где вы могли бы представить себе обычный Point pt;
пример с соответствующим std::ostream& operator << (std::ostream&out, const Point&point)
функция.
Обратите внимание, что использовать удобно __FILE__
а также __LINE__
тебе лучше использовать макросы.
Я решил свою проблему. Другие ответы, размещенные здесь, могут быть лучше, чем main, но я хотел использовать logger так же просто, как в C++ std::cout
используется. Также моё решение может быть неоптимальным и может привести к другим проблемам, но оно отвечает моим требованиям.
Я добавил кастом std::ostream
class CustomOstream : public std::ostream
{
public:
static CustomOstream& endl( CustomOstream& out )
{
return out;
}
};
и изменил макрос, чтобы использовать endl
функция от 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
Так же operator<<
из основного класса был изменен
ToolLogger& operator<< (CustomOstream& (*f)(CustomOstream&))
{
// do something //
return *this;
}
Теперь регистратор можно использовать так, как я хотел
log << "some data" << logendl; // correct //
log << "some data" << std::endl; // won't compile -> correct //