std.log — Logging
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
<WRAP alert>
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.
</WRAP>
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.jsonbauen stattlog_info_kv.
Siehe auch
- std.datetime — lesbare Zeitstempel statt Unix-Sekunden
- std.json — korrekt maskierte strukturierte Ausgabe
- std.io — direkte Dateiausgabe
