[c++20] Simple-Log - Feedback gewünscht



  • Nachdem ich nun die letzten beiden Wochen praktisch fast jede freie Minute an der Lib gearbeitet habe, habe ich mich nun dazu entschlossen den ersten "offiziellen" github alpha-release zu erstellen. Seit dem letzten Post sind einige neue UnitTests entstanden, Bugs gefixt und ein kompletter ReadyToGo-Header erstellt worden. Durch das c++17 inline Variable-Feature ist es ja sehr einfach geworden, globals nur auf Wunsch der User zu erstellen. D.h. wenn jemand den Header included ist er "Ready2Go" und kann direkt loslegen, erhält dafür aber eben auch drei, direkt von mir defininierte, globales. Alle anderen includieren einfach nicht.

    Nebenbei ist eine komplette FlushPolicy property entstanden, die entscheidet ob der Stream des Sinks nun geflushed werden soll oder nicht. Es ist kein riesen Aspekt aber dafür highly customizable (hey, Schlagwort 😃 ).

    Ich bin weiterhin über Feedback und Anregungen aller Art.

    MfG

    PS: Vielen Dank übrigens @Leon0402 und auch @Zhavok , die hier und da mich immer wieder mit ihrer Unwissenheit dazu bringen, das Ganze doch noch ein wenig zugänglicher zu machen. Aber jetzt ist Schluss damit 😃



  • @DNKpp sagte in [c++20] Simple-Log - Feedback gewünscht:

    Vielen Dank übrigens @Leon0402 und auch @Zhavok , die hier und da mich immer wieder mit ihrer Unwissenheit dazu bringen, das Ganze doch noch ein wenig zugänglicher zu machen. Aber jetzt ist Schluss damit

    Na da hast du ja wieder schöne Worte gefunden. Da hat man richtig Lust dir zu helfen^^ Zur Richtigstellung aber: Ob ich deinen (Beispiel) Code verstehe oder (unnötig) komplex finde sind im Zweifelsfall komplett unterschiedliche Sachen 😉
    So wie ich auch deinen funktionierenden ursprünglichen CMake Code verstanden habe, aber ihn trotzdem nicht gut fand. "Funktioniert" ist nicht das Maß aller Dinge.



  • @DNKpp sagte in [c++20] Simple-Log - Feedback gewünscht:

    log() << "Hallo," << " Welt" << "!"; // log stellt hier eine Logger Instanz dar. Der Operator () erstellt eine RecordBuilder Instanz, die über die << Ops weiter gereicht wird.

    Das sieht teuer aus. Weitergereicht wird so ja immer, egal ob geloggt wird oder nicht (log-level).
    Boost löst das meines Wissens über ein Macro. Nicht schön, aber wenn ich nichts loggen will, dann soll das nichts tun aber auch möglichst billig sein.



  • @Leon0402 sagte in [c++20] Simple-Log - Feedback gewünscht:

    @DNKpp sagte in [c++20] Simple-Log - Feedback gewünscht:

    Vielen Dank übrigens @Leon0402 und auch @Zhavok , die hier und da mich immer wieder mit ihrer Unwissenheit dazu bringen, das Ganze doch noch ein wenig zugänglicher zu machen. Aber jetzt ist Schluss damit

    Na da hast du ja wieder schöne Worte gefunden. Da hat man richtig Lust dir zu helfen^^ Zur Richtigstellung aber: Ob ich deinen (Beispiel) Code verstehe oder (unnötig) komplex finde sind im Zweifelsfall komplett unterschiedliche Sachen 😉
    So wie ich auch deinen funktionierenden ursprünglichen CMake Code verstanden habe, aber ihn trotzdem nicht gut fand. "Funktioniert" ist nicht das Maß aller Dinge.

    Passt schon, brauchst dich nicht zu verteidigen. Bin dir trotzdem dankbar für dein Feedback 😃

    @Jockelx sagte in [c++20] Simple-Log - Feedback gewünscht:

    @DNKpp sagte in [c++20] Simple-Log - Feedback gewünscht:

    log() << "Hallo," << " Welt" << "!"; // log stellt hier eine Logger Instanz dar. Der Operator () erstellt eine RecordBuilder Instanz, die über die << Ops weiter gereicht wird.

    Das sieht teuer aus. Weitergereicht wird so ja immer, egal ob geloggt wird oder nicht (log-level).
    Boost löst das meines Wissens über ein Macro. Nicht schön, aber wenn ich nichts loggen will, dann soll das nichts tun aber auch möglichst billig sein.

    Ich kann diese Argumentation in soweit nachvollziehen, dass sie sich wahrscheinlich auf den debug level beschränkt. Hier hatte ich in der Tat bereits über eine Lösung nachgedacht. Bisher bin ich leider noch dazu gekommen, da irgendetwas draus zu machen. Zusätzlich ist das Problem, dass ich die Nutzer ja nicht auf meine erstellten SeverityLevel festnageln möchte, sondern sie durchaus auch eigene nutzen können. Ist jedenfalls nicht ganz trivial.
    Abseits des debug Mode Arguments sehe ich den Grund allerdings nicht. Nachrichten werden abgesetzt, wenn es etwas an der derzeitigen Position zu berichten gibt, ganz unabhängig davon, ob das nun irgendwo in einer Konsole ausgegeben, in eine Datei gespeichert oder per Netzwerk verschickt werden soll. D.h. an dieser Stelle ist nicht klar, ob und von wem die Nachricht eigentlich verarbeitet werden soll. Daher lässt sich aus meiner Sicht auch keine sinnvolle Optimierung erstellen. Auch die boost Makros werden an dieser Stelle keine schwarze Magie besitzten, außer eben den genannten debug Mode zu no-open.



  • @DNKpp sagte in [c++20] Simple-Log - Feedback gewünscht:

    Zusätzlich ist das Problem, dass ich die Nutzer ja nicht auf meine erstellten SeverityLevel festnageln möchte, sondern sie durchaus auch eigene nutzen können. Ist jedenfalls nicht ganz trivial.

    Was hat das eine mit dem anderen zu tun?

    @DNKpp sagte in [c++20] Simple-Log - Feedback gewünscht:

    Auch die boost Makros werden an dieser Stelle keine schwarze Magie besitzten, außer eben den genannten debug Mode zu no-open.

    Doch, sofern ich es jetzt nicht komplett falsch verstehe:
    https://www.boost.org/doc/libs/1_66_0/libs/log/doc/html/log/tutorial/trivial_filtering.html

    Important
    
    Remember that the streaming expression is only executed if the record passed filtering. Don't specify business-critical calls in the streaming expression, as these calls may not get invoked if the record is filtered away. 
    


  • @Jockelx Ich hatte eigentlich eher erwartet, dass du jetzt tatsächlich auf NoOps hinaus wolltest und nicht auf Filter Mechaniken. Filtern ist in meiner Lib auf Sink lvl implementiert und wird dann von dem Sink auch nicht mehr weiter verwertet und geskipped.



  • @DNKpp
    Ja, ist mir klar, trotzdem wird ja erstmal immer alles weggestremed und dann ggf. verworfen.
    Wir verwenden bei uns boost, kapseln das allerdings vorher und dieses vorher kapseln sieht im Prinzip so aus wie bei dir. Und es macht einfach leider einen Performanceunterschied aus. Haben aber auch eine Lösung dafür.



  • @Jockelx Jep, das glaube ich! Ist auch logisch. Ich schreib mir das mal auf und denke darüber nach. Vielen Dank für deinen Input.

    EDIT:
    Ansonsten wäre es auch denkbar das bereits auf Logger lvl abzufangen. Wirkliche Filter wollte ich in Core eigentlich nicht einbauen.



  • @DNKpp sagte in [c++20] Simple-Log - Feedback gewünscht:

    @Jockelx Jep, das glaube ich! Ist auch logisch. Ich schreib mir das mal auf und denke darüber nach. Vielen Dank für deinen Input.

    EDIT:
    Ansonsten wäre es auch denkbar das bereits auf Logger lvl abzufangen. Wirkliche Filter wollte ich in Core eigentlich nicht einbauen.

    Ich denke annähernd Zero Overhead da lässt im Bedarfsfall machen, wenn z.B. der Ausdruck gLog() << logging::SetSev::debug im Release-Fall (bzw. wenn der Filter nicht greift) einen Null-Logger/Sink zurückgibt, bei dem die Log-Operationen einfach leere Funktionen sind, die letzendlich wegoptimiert werden.

    Dazu müsste Das Log-Level allerdings m.E. eine Compile-Time-Konstante sein, so dass das analog zu z.B. gLog<Severity::debug>() << ... funktioniert. Dann würden die Log-Level aber direkt einkompiliert und könnten nicht mehr programmatisch bestimmt werden - falls das wichtig ist. So wie ich deinen derzeitigen Code verstehe, muss für jeden Log-Aufruf derzeit mindestens einen Vergleich mit dem aktiven Log-Level gemacht werden, auch wenn im weiteren nichts mehr passiert.

    Allerdings hätte man auch mit so einer Lösung das Problem, dass die Ausdrücke, die in den Log-Aufrufen auftreten immer noch ausgewertet werden, inklusive Performance beeinflussender Nebeneffekte. Ein log() << "Text" würde da bei einem operator<<(const std::string&) immer noch einen temporären std::string aus dem char*-Literal konstruieren, auch wenn log() eine leere Funktion wäre. Diesem speziellen Fall könnte man aber evtl. mit einem operator<<(const T&) {} begegnen, der auch einen char* so wie er ist fressen würde, ohne ein temporäres Objekt zu erzeugen.

    Ich befürchte aber auch, dass man wirklichen Zero Overhead nur mit Makros hinbekommt, da sie m.E. die einzige Möglichkeit sind, Ausdrücke in Logger-Argumenten nur bedingt auszuwerten.

    Man bräuchte sowas wie:

    if constexpr (debug >= loglevel)
        log() << "Message: " << get_expensive_data_with_side_effects();
    

    Allerdings so, dass man es schön in einen einzeiligen Log-Aufruf packen kann ohne jedesmal dieses if constexpr-Gefummel schreiben zu müssen.

    Edit: Noch so ne fixe Idee am Rande, auch wenn's vielleicht etwas abschweift:

    #define log(loglevel) if constexpr (loglevel >= current_logger.loglevel()) current_logger
    
    log(debug) << "Message: " << get_expensive_data_with_side_effects();
    

    Ja, leider ein Makro, aber in der Anwendung merkt man kaum, dass es eins ist, da man trotzdem alle syntaktischen Freiheiten hat, die C++ einem bietet (Log-Messages nicht als Makro-Argumente). Könnte mir vorstellen, dass Boost das vielleicht ähnlich macht. Das wäre so tatsächlich Zero Ovehead wenn man mit compile-time-konstanten "Severities" arbeitet.



  • @Finnegan Hey, vielen Dank für deine Antwort. Die severity lvl sollte ja zu 99% zur Compiletime feststehen (habe zumindest noch nie erlebt, dass man das zur Runtime irgendwie bestimmen möchte. Vll ein guter Zeitpunkt, sich mal mit consteval zu beschäftigen.
    Alles in Allem bin ich nun nach zwei Tagen hin und her überlegen zu dem Schluss gekommen, dass ich
    a. eine alternative Logger Implementierung anbieten werde, die eben filtern kann
    b. Stand jetzt, eine separate Logger Klasse anbieten möchte, der eben von sich aus im Release Mode, zumindest fast, zur Noop wird. Das ist dann natürlich vom Severity Lvl entkoppelt und an den Build Modus gebunden. Wer etwas granulareres benötigt, der ist jederzeit in der Lage sich einen eigenen Logger zu bauen und dadurch auf exakt seine Bedürfnisse anzupassen.


Anmelden zum Antworten