Du bist nicht angemeldet.

Werbeanzeige

1

11.02.2021, 22:27

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

C++20 ist nun bereits schon auf den Weg in die Compiler und es juckt mich in den Fingern die neuen Dinge direkt auszuprobieren. Allerdings soll diese lib nicht nur als Spielplaztz für Neues dienen, sondern soll auch in naher Zukunft produktiv von mir eingesetzt werden.

Derzeit sind leider noch nicht alle Features (vollends) implementiert (modules, source_location, ranges, etc.), weswegen C++20 natürlich nur bedingt zum Einsatz kommen kann. Ich werde daher von Zeit zu Zeit immer wieder Dinge austauschen und umbauen, wobei die generelle API erhalten bleiben soll.

Neben der intensiveren Einarbeitung in den neuen C++ Standard versuche ich dieses Projekt ebenfalls dazu zu nutzen, mich in CMake weiterzuentwickeln und etwas über automatisierungen über github workflows (und auch ganz Allgemein einen professionelleren Umgang mit Git) zu lernen. Es geht mir daher eher weniger darum mit irgendwelchen existierenden (oder noch kommenden) Libraries zu konkurieren oder alles "besser" machen zu wollen. Ich möchte experiementen, lernen und Erfahrungen gewinnen und im optimalfall ein solides Produkt abliefern. Daher wäre ich sehr dankbar über Jeden, der einfach mal über das Projekt schauen würde und Feedback da lässt.

Das Repo findet ihr hier: https://github.com/DNKpp/Simple-Log


workflows
Jeder push ins github repo lässt nun 4 build&test actions starten: msvc (win), clang-cl (win), clang (ubuntu), gcc (ubuntu)
Zusätzlich triggered jeder push in den master branch einen doxygen worker, der mir aus den source files (oder viel eher headern) eine Dokumentation erstellt und diese direkt als github-page bereitstellt. Diese ist hier zu finden.

CMake
Das komplette Projekt kann mit Hilfe von CMake gebaut und auch als library in anderen Projekten eingebaut werden (tatsächlich ist die Library an sich derzeit Header-Only. Lediglich Tests und Examples lassen sich wirklich bauen). Wie in der Readme zu lesen, muss lediglich das target "simple_log" über cmake gelinked werden. Gleichzeitig lässt sich das Bauen der Beispiele als Option steuern.
Pullen über FetchContent sollte daher auch ohne Probleme möglich sein (noch nicht getestet, ist aber ein Ziel).

Die Library selbst
Mir war es wichtig, dass es eine einfach zu nutzende API gibt, die jedoch von Nutzern anpassbar bleibt. Kern und Angelpunkt sind folgende drei Bereiche:
* Core
* Sinks
* Logger

Core
Dies ist praktisch der zentrale Bestandteil der library. An Cores können Sinks registriert werden, welche alle Nachrichten weitergeleitet bekommen, die über verbundene Logger instanzen generiert wurden. Es ist daher zwingend erforderlich, dass mindestens eine Core Instanz existert, welche ihre Logger auch "überlebt". Die Anzahl ist allerdings nicht auf eins limiert (kein Singleton, hurra!). Intern nutzt jeder Core seinen eigenen WorkerThread, welcher durch eine simple BlockingQueue Implementierung gefüttert wird.

Sinks
Das sind Objekte, mit deren Hilfe Nachrichten in Dateien gespeichert oder auf der Konsole ausgegeben werden können. Prinzipiell bietet das BasicSink ein Interface, dass sich mit jedem std::ostream benutzen lässt. Auf Sink Ebene lassen sich zusätzlich Filter installieren, mit deren Hilfe bestimmte Arten von Nachrichten ignoriert werden können. Ebenso lässt sich das Format, in welchem die Nachrichten an die streams übergeben werden sollen, anpassen.

Logger
Diese Objekte sind praktisch das Hauptinterface um eine Nachricht abzusetzen. Logger sind an sich leichtgewichtig, sind aber an eine explizite Core Instanz gebunden. Logger bieten die Möglichkeit default Severities oder Channel an eine Nachricht anzuhängen. Diese Default Werte lassen sich allerdings auch einfache Art und Weise temporär auf Nachrichten-Level überschreiben.. Eine Nachricht lässt sich recht simpel via << operatoren zusammenbauen.

C-/C++-Quelltext

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


Record
Erwähnenswert ist an dieser Stelle noch das Record System. Ein Record ist hier nichts anderes als ein concept, dass das von der lib erwartete Interface und einige typedefs vorgibt. Die Library stellt mit BaseRecord eine eigene Implementierung zur Verfügung, die eben dieses concept erfüllt. Es ist allerdings nicht notwendig dieses auch zu nutzen. Das eröffnet Nutzern Möglichkeiten von einfachen Typenanpassungen der Member (anderer SeverityLevel typ oder Channel typ) bis hin zu kompletten custom Implementierungen mit vielen weiteren Membern, die fürs Logging benötigt werden.

Für mehr Infos, einfach mal hier nachschauen: https://dnkpp.github.io/Simple-Log/

Mit diesen drei Puzzleteilchen ist es auch schon möglich die komplette logging Architektur zu nutzen.

Beispiel
Das hier wäre ein simples Beispiel um simples logging zu ermöglichen (mal ganz dreist das Basic Example kopiert :D).

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// This header contains preset types, which users can use to get an easy start with the library.
#include <Simple-Log/PresetTypes.hpp>

#include <iostream>
#include <memory>

/* let's use a namespace alias for convenience; be aware: if you use cmath, some stl implementations will bloat your global namespace with a log function declaration (c-relict).
Thus to make it compatible with all compilers, I'll use logging as alias instead.

All preset type alias are located in the sl::log::pre namespace, thus they do not interfere with the actual library if you don't want them to.
It is no good style just importing everything into your namespace. Just create an namespace alias like so. This way it's very easy to make it less verbose for you.
*/
namespace logging = sl::log::pre;

// We're using a factory function here, but this isn't necessary. You could also create a plain Core instance and set it up later in main
inline std::unique_ptr<logging::Core_t> makeLoggingCore()
{
    /* an sl::log::Core is non-copy and non-movable, thus we will store it in an unique_ptr, so we
     * can safely move it into our global.*/
    auto core = std::make_unique<logging::Core_t>();

    /* register a BasicSink and link it to the std::cout stream. This will simply print all incoming
    messages onto the console.*/
    core->makeSink<logging::BasicSink_t>(std::cout);
    return core;
}

/* For conveniences we will simply store the core and our default logger as a global. Feel free to do it
otherwise. Just make sure the Core instance doesn't get destructed before all related Logger instances.*/
inline std::unique_ptr<logging::Core_t> gLoggingCore{ makeLoggingCore() };
inline logging::Logger_t gLog{ *gLoggingCore, logging::SeverityLevel::info };

int main()
{
    gLog() << "Hello, World!";  // This will print this message with the "info" severity
    // override default logger settings at the beginning
    gLog() << logging::SetSev::debug << "Mighty debug message";
    // or at the end
    gLog() << "Print my important hint!" << logging::SetSev::hint;
    // or in between of messages
    gLog() << "This" << " will " << logging::SetSev::warning << " create " << " a " << " concatenated " << " warning " << " message";
    // and using default setup again
    gLog() << "Back to info";
}

/*Core will make sure, that all pending Records will be processed before it gets destructed.*/
/*
 * The above code will for example generate this output:
 *  18:49:32.047 >>> info:: Hello, World!
 *  18:49:32.047 >>> debug:: Mighty debug message
 *  18:49:32.047 >>> hint:: Print my important hint!
 *  18:49:32.047 >>> warning:: This will  create  a  concatenated  warning  message
 *  18:49:32.047 >>> info:: Back to info
 *
 * Keep in mind, you are completely free how you are going to format your message. This is just the default one.
 */


Nachdem nun wahrscheinlich die Wenigsten hier bis zum Schluss lesen, würde ich mich dennoch über konstruktive Kritik oder Anmerkungen freuen.

Mit freundlichen Grüßen
Dominic

Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von »DNKpp« (16.02.2021, 11:50)


2

15.02.2021, 20:52

Nun, heute dann doch noch mal eine etwas größere Überarbeitung. Statt wie bisher Nutzer auf eine feste Record Implementierung festzunageln, gibt es jetzt lediglich ein Concept, welches das benötigte Interface vorschreibt. Dazu musste ich natürlich den Rest der lib templatisieren (was ich schon geahnt habe und daher sowieso header-only war :D), was letztendlich noch dazu geführt hat, dass ich noch den ein oder anderen alias für die Zugänglichkeit hinzufügen musste. Mit inludieren des "PresetTypes.hpp"-Headers ist man allerdings direkt ready-to-go und kann direkt loslegen. Customizations sind jedoch nun praktisch überall möglich.

Im Zuge des Reworks sind auch die "UserData" Member aus den Records rausgefallen. Sollte man sowas benötigen, ist es ja recht einfach diese selbst hinzuzufügen.

3

23.02.2021, 23:39

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 :D ).

Ich bin weiterhin über Feedback und Anregungen aller Art.

MfG

Werbeanzeige