Logger Class: Wie Verwendung maximal einfach?



  • Abend!

    Sagen wir ich habe eine Logerklasse Logger mit Methoden wie log(LOG_LEVEL level, const string& message);

    Wie würdet ihr das machen, so dass man diesen Logger sehr bequem benutzen kann. Am besten wäre es evtl, wenn man einfach nur einen Header (Logger.h) inkludieren muss und dann sofort loggen könnte (mit Logger.log(...)

    Wie könnte man das machen? In dem .cpp File eine globale Instanz vom Logger anlegen und dann im Header ein extern? Oder ein Singleton? Oder was ganz anderes?



  • Wir benutzen log4cxx (eine etwas angepasste Version, weil das doch paar Macken hat). Das definiert Macros. Die Macros gehen wahrscheinlich auf ein Singleton Objekt, habs mir nie genauer angeschaut. Ist einfach genug zu benutzen.



  • Also ist das Singleton Pattern eine gute Idee fuer einen Logger?

    Wenn ich ein Makro ala LOG_INFO("Eine Meldung") habe, muss LOG_INFO ja irgend ein globales Objekt aufrufen...



  • Loggi schrieb:

    Sagen wir ich habe eine Logerklasse Logger mit Methoden wie log(LOG_LEVEL level, const string& message);

    Wie würdet ihr das machen, so dass man diesen Logger sehr bequem benutzen kann.

    ich würde das so ändern, dass mir eine std::ostream-Schnittstelle zur Verfügung steht, damit ist man zum einen konform zum Standard und zum anderen wesentlich flexibler als mit einem std::string.
    Im einfachsten Fall mit std::clog:

    int wert = ...;
        if( wert > zulaessiger_wert ) {
            clog << "Der Wert " << wert << " ist zu gross; zulaesig: " <<  zulaessiger_wert << endl;
            // ...
    

    da benötigt man nur #include <iostream>. Die Output von std::clog kann man sich hin biegen, wo immer man es hin haben will. Inklusive Hinzufügen eines Timestamps. Suche hier im Forum z.B. nach rdbuf oder schau Dir diesen Beitrag von mir an.

    Bleibt noch der LOGLEVEL. Das kann man z.B. über ein eigenes Makro lösen - in der einfachsten Form:

    #define LOG( level, ausgabe ) if( level <= CURRENT_LOG_LEVEL ) { std::clog << ausgabe << std::endl }
        // .. und später
        int wert = ...;
        if( wert > zulaessiger_wert ) {
            LOG( error_level, "Der Wert " << wert << " ist zu gross; zulaesig: " <<  zulaessiger_wert );
            // ...
    

    Loggi schrieb:

    Also ist das Singleton Pattern eine gute Idee fuer einen Logger?

    Wenn ich ein Makro ala LOG_INFO("Eine Meldung") habe, muss LOG_INFO ja irgend ein globales Objekt aufrufen...

    Genau - std::clog ist so ein globales Objekt. Und das muss kein Singleton sein, da es ja noch weitere Objekte derselbe Klasse geben darf. Global zugänglich reicht.

    Gruß
    Werner



  • böses Makro.

    Was wenn ich

    if(foo) LOG("dies");
    else LOG("das");
    

    schreiben will?



  • Shade Of Mine schrieb:

    böses Makro.

    Was wenn ich

    if(foo) LOG("dies");
    else LOG("das");
    

    schreiben will?

    Das war nur eine Skizze (deshalb "in der einfachsten Form") - was würdest Du denn vorschlagen, um den Gebrauch sicherer zu machen?



  • Werner Salomon schrieb:

    Das war nur eine Skizze (deshalb "in der einfachsten Form") - was würdest Du denn vorschlagen, um den Gebrauch sicherer zu machen?

    #define LOG(...) if(0)noop();else { ... }
    //oder
    #define LOG(...) ((void)doLog(...)
    

    noop ist eine inline funktion die nichts tut und doLog wäre eine Funktion die den Code implementiert den du im Makro selber stehen hast.

    Makros sind tricky und sollten deshalb wenn möglich nicht komplex sein.


  • Mod

    Wie wäre es zumindest mit dem guten alten do {...} while(0) drumherum? Ansonsten würde mich erst einmal interessieren, was der Threadersteller auf deinen berechtigten Einwand von wegen clog zu antworten hat.



  • Die do {} while(0) und if(0) Varianten erzeugen Warnungen wegen den Konstanten Ausdrücken... nur so als Hinweis. Kann sehr mühsam sein...



  • von dem Makro halte ich nicht viel, außerdem geht dadurch auch die std::ostream-Syntax teilweise wieder den Bach runter. Ich hatte mir mal sowas überlegt (aber nie ausprobiert):

    #include <iostream>
    #define CURRENT_LOGLEVEL 2
    
    class Logger
    {
      bool doLog;
      Logger(bool b) : doLog(b) {}
      friend Logger log(std::size_t);
    
    public:
    
      template <class T>
      Logger const& operator<<(T const& t) const
      { 
        if (doLog) std::clog << t;
        return *this;
      }
    
      ~Logger() { *this << '\n'; }
    };
    
    Logger log(std::size_t level)
    { return Logger(level > CURRENT_LOGLEVEL); }
    
    int main()
    {
      log(2) << "2";
      log(3) << 3;
      log(4) << '4' << " usw...";
    }
    


  • pumuckl schrieb:

    von dem Makro halte ich nicht viel, außerdem geht dadurch auch die std::ostream-Syntax teilweise wieder den Bach runter. Ich hatte mir mal sowas überlegt (aber nie ausprobiert):

    Ich finde Makros sind die einzig sinnvolle Lösung.
    Denn ich will nicht nur das Loggen was ich schreibe, sondern Metainformationen dabei haben. zB die aktuelle Uhrzeit, uU welches Modul, Funktion, Datei, etc. den Logeintrag generiert hat.

    Das meiste davon läuft nur mittels Makros. Die << Syntax ist dabei trivial zu behalten.

    Deine Idee ist aber nett.



  • theta schrieb:

    Die do {} while(0) und if(0) Varianten erzeugen Warnungen wegen den Konstanten Ausdrücken... nur so als Hinweis. Kann sehr mühsam sein...

    Bei welchem Compiler?
    while(true) {}
    ist zB ein standard idiom für endlosschleifen.

    if(NDEBUG) oder so sind auch nicht wirklich unnatürlich. Da sollte ein Compiler nicht warnen.



  • Shade Of Mine schrieb:

    theta schrieb:

    Die do {} while(0) und if(0) Varianten erzeugen Warnungen wegen den Konstanten Ausdrücken... nur so als Hinweis. Kann sehr mühsam sein...

    Bei welchem Compiler?
    while(true) {}
    ist zB ein standard idiom für endlosschleifen.

    if(NDEBUG) oder so sind auch nicht wirklich unnatürlich. Da sollte ein Compiler nicht warnen.

    VS2010 SP1, Warning Level 4

    int main()
    {
        do {} while(0);
    
        if(0) {}
    
        while(true) {}
    }
    

    Bei allen drei:

    warning C4127: conditional expression is constant

    if(NDEBUG) {}
    

    Kompiliert im Debug Mode gar nicht, weil NDEBUG nicht definiert ist. Ich weiss allerdings nicht, was der Standard dazu sagt.



  • Interessant. Den hab ich wohl aus guten Gründen aus.
    Eine von vielen sinnlosen Warnungen vom VC++...

    Muss man wohl statt 0 einfach __LINE__==-1 setzen.

    Und NDEBUG stimmt natürlich, ich hab eher an DEBUG_LEVEL und Co gedacht.



  • Shade Of Mine schrieb:

    Ich finde Makros sind die einzig sinnvolle Lösung.
    Denn ich will nicht nur das Loggen was ich schreibe, sondern Metainformationen dabei haben. zB die aktuelle Uhrzeit, uU welches Modul, Funktion, Datei, etc. den Logeintrag generiert hat.

    Das meiste davon läuft nur mittels Makros. Die << Syntax ist dabei trivial zu behalten.

    Gut, unter dem Gesichtspunkt kann man das nur mit dem Makro machen. Dann vereinfacht sich mein kram sogar etwas:

    #include <iostream>
    #define CURRENT_LOGLEVEL 2
    
    template <bool doLog> struct Logger
    { 
      template <class T>
      Logger& operator<< (T const& t)
      { return *this; }
    
      static Logger create(char const*, std::size_t, std::size_t)
      { return Logger(); }
    };
    
    template <>
    struct Logger<true>
    {
    private:
      Logger(char const* file, std::size_t line, std::size_t level) 
      { std::clog << "LOG(" << level << ')' << file << ':' << line << "> "; } 
    public:
      template <class T>
      std::ostream& operator<<(T const& t) const
      { std::clog << t; return std::clog; }
    
      ~Logger() { std::clog << '\n'; }
    
      static Logger create(char const* file, std::size_t line, std::size_t level)
      { return Logger(file, line, level); }
    };
    
    #define LOG(level) \
    Logger<(level>CURRENT_LOGLEVEL)>::create(__FILE__, __LINE__, level)
    
    int main()
    {
      LOG(2) << "2";
      LOG(3) << 3;
      LOG(4) << '4' << "usw...";
      LOG(5) << "5";
    }
    

    Dürfte alles geinlined werden.



  • Was dieser Log-Level bringen soll, ist mir zwar gerade ein Rätsel, aber du hast den Include <cstddef> für size_t vergessen.

    Warum nicht einfach sowas:

    enum log_type { info, warning, error, fatal };
    

    Oder so ähnlich.


Anmelden zum Antworten