====== std.log — Logging ====== → [[lyx_-_programmiersprache:units|Zurück zur Unit-Übersicht]] Logging mit fünf Leveln (DEBUG, INFO, WARN, ERROR, FATAL), umschaltbarem Ausgabeziel (stdout oder Datei), optionalem Zeitstempel, ANSI-Farben und einem Callback zum Weiterleiten an externe Systeme. Zusätzlich eine einfache JSON-Zeilenausgabe (''log_info_kv'') für maschinelle Auswertung. Einsatzbereiche: Serverdienste, Hintergrundjobs, CLI-Werkzeuge, Produktions-Diagnose. **Autor:** Andreas Röne\\ **Copyright:** 2024–2025 Andreas Röne\\ **Quelle:** ''std/log.lyx'' **Zwei Ausgabewege — nur einer respektiert die Konfiguration.**\\ ''log_emit'' ist der konfigurierbare Weg: Level-Filter, Datei-Sink, Zeitstempel. Die Kurzfunktionen ''log_info''/''log_debug''/''log_warn''/''log_error''/''log_fatal'' schreiben dagegen **immer** und **immer nach stdout** — sie ignorieren ''set_log_level'' und ''log_set_file''. Details unter [[#Fallstricke]]. Für alles außer Wegwerf-Skripten: ''log_emit'' verwenden. ===== Import ===== import std.log; ---- ===== Konstanten ===== ^ Name ^ Typ ^ Wert ^ Bedeutung ^ | ''LOG_LEVEL_DEBUG'' | ''int64'' | 0 | Detailinformation für die Fehlersuche | | ''LOG_LEVEL_INFO'' | ''int64'' | 1 | Normaler Betriebsablauf (**Voreinstellung**) | | ''LOG_LEVEL_WARN'' | ''int64'' | 2 | Auffälligkeit, Betrieb läuft weiter | | ''LOG_LEVEL_ERROR'' | ''int64'' | 3 | Fehlgeschlagene Operation | | ''LOG_LEVEL_FATAL'' | ''int64'' | 4 | Abbruchbedingung — ''log_fatal'' beendet den Prozess | Die Zahlenwerte sind geordnet: ''log_emit'' verwirft alles unterhalb des gesetzten Levels. ''set_log_level(LOG_LEVEL_WARN)'' lässt also WARN, ERROR und FATAL durch. ---- ===== Funktionen ===== ==== Ausgabe (konfigurierbar) ==== ^ Signatur ^ Beschreibung ^ | ''log_emit(level: int64, msg: pchar): void'' | Zentrale Ausgabe. Prüft den Level-Filter, setzt optional Zeitstempel und Farbe, schreibt Präfix ''[LEVEL] '' plus Nachricht und Zeilenumbruch auf das aktuelle Ziel | | ''log_debugf(msg: pchar, n: int64): void'' | Wie ''log_emit(LOG_LEVEL_DEBUG, …)'', hängt ''n'' als Dezimalzahl an die Nachricht | | ''log_infof(msg: pchar, n: int64): void'' | dito für INFO | | ''log_warnf(msg: pchar, n: int64): void'' | dito für WARN | | ''log_errorf(msg: pchar, n: int64): void'' | dito für ERROR | | ''log_info_kv(event: pchar, key: pchar, value: pchar): void'' | Schreibt eine JSON-Zeile ''{"level":"info","event":"…","key":"value"}'' direkt auf das Ziel | ==== Konfiguration ==== ^ Signatur ^ Beschreibung ^ | ''set_log_level(level: int64): void'' | Setzt die Filterschwelle für ''log_emit'' und die ''log_*f''-Funktionen | | ''get_log_level(): int64'' | Liest die aktuelle Filterschwelle | | ''log_level_to_string(level: int64): pchar'' | Level-Name; unbekannte Werte ergeben ''UNKNOWN'' | | ''log_set_file(path: pchar): bool'' | Schaltet auf Datei um (angehängt, ''0644'' bei Neuanlage). Leerer Pfad schaltet zurück auf stdout. ''false'' bei Öffnungsfehler | | ''log_set_timestamp(enabled: bool): void'' | Stellt dem Präfix ''[<Unix-Sekunden>] '' voran | | ''log_set_color(enabled: bool): void'' | Soll ANSI-Farben nach Level einschalten — **derzeit wirkungslos**, siehe [[#Fallstricke]] | ==== Kurzfunktionen (ungefiltert, immer stdout) ==== ^ Signatur ^ Beschreibung ^ | ''log_debug(msg: pchar): void'' | Schreibt ''[DEBUG] msg'' auf stdout und ruft den Callback | | ''log_info(msg: pchar): void'' | dito für INFO | | ''log_warn(msg: pchar): void'' | dito für WARN | | ''log_error(msg: pchar): void'' | dito für ERROR | | ''log_fatal(msg: pchar): void'' | dito für FATAL — ruft anschließend ''exit(1)'' | | ''log_debug_if(cond: bool, msg: pchar): void'' | Ruft ''log_debug'' nur wenn ''cond'' wahr ist | | ''log_info_if(cond: bool, msg: pchar): void'' | dito | | ''log_warn_if(cond: bool, msg: pchar): void'' | dito | | ''log_error_if(cond: bool, msg: pchar): void'' | dito | | ''log_section_enter(name: pchar): void'' | Gibt ''name'' als INFO aus | | ''log_section_exit(name: pchar): void'' | Gibt ''name'' als INFO aus — **von ''enter'' nicht unterscheidbar** | | ''log_app_start(name: pchar): void'' | Drei Zeilen Startbanner — ''name'' wird **nicht** ausgegeben | | ''log_app_end(code: int64): void'' | Abschlussbanner; ''code == 0'' ergibt Erfolgsmeldung, sonst Fehlermeldung | ==== Callback ==== ^ Signatur ^ Beschreibung ^ | ''register_log_callback(fn_ptr: int64): void'' | Registriert einen Handler ''fn(pchar): void'' | | ''unregister_log_callback(): void'' | Entfernt den Handler | | ''has_log_callback(): bool'' | Ob ein Handler gesetzt ist | | ''invoke_log_callback(msg: pchar): void'' | Ruft den Handler direkt; ohne Handler passiert nichts | Der Handler wird von den **Kurzfunktionen** gerufen, **nicht** von ''log_emit''. ---- ===== Beispiele ===== ==== Level-gefilterte Ausgabe ==== import std.log; fn main(): int64 { set_log_level(LOG_LEVEL_INFO); log_emit(LOG_LEVEL_DEBUG, "Verbindungspool initialisiert"); // gefiltert log_emit(LOG_LEVEL_INFO, "Server lauscht auf Port 8080"); log_emit(LOG_LEVEL_WARN, "Zertifikat laeuft in 14 Tagen ab"); log_emit(LOG_LEVEL_ERROR, "Datenbank nicht erreichbar"); PrintLn("aktives Level: ", log_level_to_string(get_log_level())); return 0; } Ausgabe: [INFO] Server lauscht auf Port 8080 [WARN] Zertifikat laeuft in 14 Tagen ab [ERROR] Datenbank nicht erreichbar aktives Level: INFO ==== Datei-Sink, Zeitstempel, Zahlen und JSON-Zeile ==== import std.log; fn main(): int64 { set_log_level(LOG_LEVEL_INFO); if (log_set_file("dienst.log")) { log_set_timestamp(true); log_emit(LOG_LEVEL_INFO, "Dienst gestartet"); log_infof("aktive Verbindungen: ", 17); log_info_kv("request", "pfad", "/api/v1/status"); log_emit(LOG_LEVEL_ERROR, "Upstream-Timeout"); log_set_timestamp(false); log_set_file(""); // zurueck auf stdout PrintLn("geschrieben nach dienst.log"); } else { PrintLn("Log-Datei nicht schreibbar"); } return 0; } Inhalt von ''dienst.log'': [1786640183] [INFO] Dienst gestartet [1786640183] [INFO] aktive Verbindungen: 17 {"level":"info","event":"request","pfad":"/api/v1/status"} [1786640183] [ERROR] Upstream-Timeout Zwei Beobachtungen an dieser echten Ausgabe: Der Zeitstempel sind **rohe Unix-Sekunden**, kein formatiertes Datum — für lesbare Zeiten die Zeile selbst mit ''std.datetime'' bauen. Und die ''log_info_kv''-Zeile trägt **keinen** Zeitstempel, weil sie am Präfix-Aufbau von ''log_emit'' vorbei direkt schreibt. ==== Callback zum Weiterleiten ==== import std.log; var _fehler: int64 := 0; fn zaehleUndSpiegle(msg: pchar): void { _fehler := _fehler + 1; PrintLn(" [audit] ", msg); } fn main(): int64 { PrintLn("Handler registriert? ", IntToStr(has_log_callback() as int64)); register_log_callback(zaehleUndSpiegle as int64); PrintLn("Handler registriert? ", IntToStr(has_log_callback() as int64)); log_warn("Plattenplatz unter 10 %"); log_error("Schreibvorgang fehlgeschlagen"); unregister_log_callback(); log_warn("nach unregister — kein audit mehr"); PrintLn("vom Handler gesehen: ", IntToStr(_fehler)); return 0; } Ausgabe: Handler registriert? 0 Handler registriert? 1 [WARN] Plattenplatz unter 10 % [audit] Plattenplatz unter 10 % [ERROR] Schreibvorgang fehlgeschlagen [audit] Schreibvorgang fehlgeschlagen [WARN] nach unregister — kein audit mehr vom Handler gesehen: 2 Der Handler wird als Funktionszeiger übergeben (''as int64'') und muss die Signatur ''fn(pchar): void'' haben. Er bekommt die **nackte Nachricht** ohne Level-Präfix — das Level ist im Handler also nicht mehr erkennbar. ---- ===== Fallstricke ===== Alle Punkte hier sind mit ''lyxc 1.0.17K'' nachgestellt; jeder hat ein offenes Issue. ==== 1. Kurzfunktionen ignorieren den Level-Filter ==== import std.log; fn main(): int64 { set_log_level(LOG_LEVEL_ERROR); log_info("log_info: erscheint trotz Level ERROR"); log_debug("log_debug: erscheint ebenfalls"); log_emit(LOG_LEVEL_INFO, "log_emit INFO: korrekt unterdrueckt"); log_emit(LOG_LEVEL_ERROR, "log_emit ERROR: erscheint"); return 0; } [INFO] log_info: erscheint trotz Level ERROR [DEBUG] log_debug: erscheint ebenfalls [ERROR] log_emit ERROR: erscheint Ursache: ''log_debug'' & Co. rufen direkt ''PrintLn'' statt ''log_emit''. Damit greift weder der Filter noch der Datei-Sink noch der Zeitstempel. Ein Dienst, der ''log_set_file'' aufruft und dann ''log_info'' benutzt, schreibt seine Logs weiter nach stdout — die Datei bleibt leer. ==== 2. ''log_set_color'' bleibt wirkungslos ==== ''log_set_color(true)'' erzeugt kein einziges ANSI-Byte. Grund ist der Escape in ''_log_color_code'': die Farbcodes sind als ''"\033[36m"'' geschrieben, aber Lyx kennt **keine oktalen Escapes**. ''\0'' wird als NUL gelesen, der String hat damit Länge 0: fn main(): int64 { PrintLn("StrLen(\\033[36m) = ", IntToStr(StrLen("\033[36m"))); PrintLn("StrLen(\\x1b[36m) = ", IntToStr(StrLen("\x1b[36m"))); return 0; } StrLen(\033[36m) = 0 StrLen(\x1b[36m) = 5 Im eigenen Code deshalb ''\x1b'' verwenden, nie ''\033''. ==== 3. ''log_info_kv'' maskiert nichts ==== Anführungszeichen im Wert zerstören das JSON: log_info_kv("cfg", "pfad", "C:\"tmp\"x"); {"level":"info","event":"cfg","pfad":"C:"tmp"x"} Das ist kein gültiges JSON mehr. Vor der Übergabe selbst maskieren (Backslash, Anführungszeichen, Steuerzeichen) oder ''std.json'' verwenden. ''log_info_kv'' ignoriert außerdem den Level-Filter und schreibt auch bei Level FATAL. ==== 4. Sektionen und App-Banner tragen keine Information ==== ''log_section_enter("migration")'' und ''log_section_exit("migration")'' erzeugen **dieselbe** Zeile ''[INFO] migration'' — im Log ist Anfang und Ende nicht zu unterscheiden. ''log_app_start(name)'' ignoriert den übergebenen Namen und druckt ein festes Banner. Wer Abschnitte auswerten will, formuliert die Zeile selbst: log_emit(LOG_LEVEL_INFO, "-> migration"); log_emit(LOG_LEVEL_INFO, "<- migration"); ==== 5. Die ''log_*f''-Wrapper lecken Speicher ==== ''log_debugf''/''log_infof''/''log_warnf''/''log_errorf'' allokieren pro Aufruf einen 22-Byte-Zahlenpuffer und einen Kombinationspuffer, geben aber beides nie frei. In einer Schleife mit vielen Log-Zeilen wächst der Heap monoton. Bis zum Fix in Dauerschleifen ''log_emit'' mit selbst gebauter Nachricht verwenden: log_emit(LOG_LEVEL_INFO, StrConcat("aktive Verbindungen: ", IntToStr(n))); ---- ===== Empfehlung ===== * Ausgabe grundsätzlich über ''log_emit'' — nur dieser Weg respektiert Level und Ziel. * Level einmal beim Programmstart setzen, danach nicht mehr ändern. * Datei-Sink früh setzen, damit keine Zeilen vorher nach stdout entkommen. * Farben derzeit nicht einplanen (Fallstrick 2). * Für maschinell auswertbare Logs eigene JSON-Zeilen mit ''std.json'' bauen statt ''log_info_kv''. ---- ===== Siehe auch ===== * [[lyx_-_programmiersprache:units:datetime|std.datetime]] — lesbare Zeitstempel statt Unix-Sekunden * [[lyx_-_programmiersprache:units:json|std.json]] — korrekt maskierte strukturierte Ausgabe * [[lyx_-_programmiersprache:units:io|std.io]] — direkte Dateiausgabe * [[lyx_-_programmiersprache:units:os|std.os]] — ''open''/''write''/''close'', ''unix_time''