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 //
Другие вопросы по тегам