Docs: Fügt docs hinzu.
This commit is contained in:
103
src/log.rs
103
src/log.rs
@@ -1,3 +1,25 @@
|
|||||||
|
//! Einfaches, threadsicheres Logging-Modul.
|
||||||
|
//!
|
||||||
|
//! Merkmale:
|
||||||
|
//! - Ausgabe in Terminal (stdout/stderr) und zusätzlich in eine Logdatei im temporären Verzeichnis.
|
||||||
|
//! - Nachrichtenvorlage: `[DD.MM.YYYY HH:MM:SS.mmm][LEVEL][TAG]: <Text>` mit Emoji-Präfix (🛑/⚠️/ℹ️/🚧).
|
||||||
|
//! - Der aktuell verwendete Schweregradfilter ist statisch (`LOG_LEVEL`) und wird zur Laufzeit nicht geändert.
|
||||||
|
//! - Terminal-Erkennung und Dateihandle werden lazily initialisiert und zwischengespeichert (`OnceLock`).
|
||||||
|
//!
|
||||||
|
//! Hinweise:
|
||||||
|
//! - Bei einem leeren `tag` wird ein leerer Tag-Abschnitt erzeugt.
|
||||||
|
//! - Die Logdatei wird unterhalb eines prozessspezifischen Ordners im System-Temp-Verzeichnis abgelegt und nach Datum
|
||||||
|
//! benannt (z. B. `log-2025-08-18.log`). Es wird im Append-Modus geschrieben.
|
||||||
|
//!
|
||||||
|
//! Beispiel (ohne Ausführung in Doctests):
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use crate::log::{log, LogLevel};
|
||||||
|
//!
|
||||||
|
//! log("startup", "Dienst wird initialisiert …", LogLevel::Info);
|
||||||
|
//! log("db", "Verbindung unterbrochen!", LogLevel::Warn);
|
||||||
|
//! log("core", "Unerwarteter Fehler", LogLevel::Error);
|
||||||
|
//! ```
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::fs::{create_dir_all, File, OpenOptions};
|
use std::fs::{create_dir_all, File, OpenOptions};
|
||||||
@@ -6,11 +28,24 @@ use std::sync::{Mutex, OnceLock};
|
|||||||
|
|
||||||
use time::{macros::format_description, OffsetDateTime};
|
use time::{macros::format_description, OffsetDateTime};
|
||||||
|
|
||||||
|
/// Schweregrade für Logeinträge in aufsteigender Detailtiefe.
|
||||||
|
///
|
||||||
|
/// Die Reihenfolge bestimmt den Filter: Nur Einträge mit `log_level <= LOG_LEVEL` werden ausgegeben.
|
||||||
|
///
|
||||||
|
/// Anzeige (Display):
|
||||||
|
/// - `Error` -> `ERROR`
|
||||||
|
/// - `Warn` -> `WARN`
|
||||||
|
/// - `Info` -> `INFO`
|
||||||
|
/// - `Debug` -> `DEBUG`
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||||
pub enum LogLevel {
|
pub enum LogLevel {
|
||||||
|
/// Kritische Fehler, gehen zusätzlich auf `stderr`.
|
||||||
Error = 1,
|
Error = 1,
|
||||||
|
/// Wichtige Warnungen über potenzielle Probleme.
|
||||||
Warn = 2,
|
Warn = 2,
|
||||||
|
/// Allgemeine Betriebsinformationen.
|
||||||
Info = 3,
|
Info = 3,
|
||||||
|
/// Ausführliche Diagnoseausgaben für die Entwicklung.
|
||||||
Debug = 4,
|
Debug = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,11 +60,36 @@ impl Display for LogLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Zwischenspeicher für die einmalig ermittelte Terminal-Fähigkeit von `stdout`.
|
||||||
static IS_TERMINAL: OnceLock<bool> = OnceLock::new();
|
static IS_TERMINAL: OnceLock<bool> = OnceLock::new();
|
||||||
static LOG_LEVEL: LogLevel = LogLevel::Debug; // TODO: LogLevel aus Config laden
|
|
||||||
|
|
||||||
|
/// Globaler Schweregradfilter für Ausgabe.
|
||||||
|
/// TODO: In Zukunft aus Konfiguration laden.
|
||||||
|
static LOG_LEVEL: LogLevel = LogLevel::Debug;
|
||||||
|
|
||||||
|
/// Lazy-initialisiertes Handle zur Logdatei; kann `None` sein, falls das Öffnen fehlschlug.
|
||||||
static LOG_FILE: OnceLock<Mutex<Option<File>>> = OnceLock::new();
|
static LOG_FILE: OnceLock<Mutex<Option<File>>> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Protokolliert eine Nachricht abhängig vom angegebenen Schweregrad.
|
||||||
|
///
|
||||||
|
/// Verhalten:
|
||||||
|
/// - Wenn `log_level` größer als der globale Filter ist, wird nichts ausgegeben.
|
||||||
|
/// - Bei Terminalausgabe gehen `Error`-Meldungen nach `stderr`, alle anderen nach `stdout`.
|
||||||
|
/// - Zusätzlich wird in eine tägliche Logdatei im Temp-Verzeichnis geschrieben (wenn erfolgreich geöffnet).
|
||||||
|
///
|
||||||
|
/// Parameter:
|
||||||
|
/// - `tag`: Kurzer Kontext (z. B. Modulname).
|
||||||
|
/// - `message`: Die eigentliche Nachricht (eine Zeile).
|
||||||
|
/// - `log_level`: Schweregrad der Nachricht.
|
||||||
|
///
|
||||||
|
/// Thread-Sicherheit:
|
||||||
|
/// - Dateischreibzugriffe sind über `Mutex` serialisiert.
|
||||||
|
///
|
||||||
|
/// Beispiel:
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use crate::log::{log, LogLevel};
|
||||||
|
/// log("http", "Server gestartet auf Port 8080", LogLevel::Info);
|
||||||
|
/// ```
|
||||||
pub fn log(tag: &str, message: &str, log_level: LogLevel) {
|
pub fn log(tag: &str, message: &str, log_level: LogLevel) {
|
||||||
if log_level <= LOG_LEVEL {
|
if log_level <= LOG_LEVEL {
|
||||||
let message: String = format_message(tag, message, &log_level);
|
let message: String = format_message(tag, message, &log_level);
|
||||||
@@ -52,10 +112,28 @@ pub fn log(tag: &str, message: &str, log_level: LogLevel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ermittelt einmalig, ob `stdout` ein Terminal ist, und cached das Ergebnis.
|
||||||
|
///
|
||||||
|
/// Rückgabe:
|
||||||
|
/// - `true`, wenn `stdout` ein TTY/Terminal ist.
|
||||||
|
/// - `false` andernfalls.
|
||||||
fn is_terminal() -> bool {
|
fn is_terminal() -> bool {
|
||||||
*IS_TERMINAL.get_or_init(|| stdout().is_terminal())
|
*IS_TERMINAL.get_or_init(|| stdout().is_terminal())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Formatiert eine Lognachricht mit Zeitstempel, Level, Tag und Emoji-Präfix.
|
||||||
|
///
|
||||||
|
/// Format:
|
||||||
|
/// - Datum/Zeit lokal (Fallback: UTC) im Format `DD.MM.YYYY HH:MM:SS.mmm`.
|
||||||
|
/// - Level als Text (`ERROR`, `WARN`, `INFO`, `DEBUG`).
|
||||||
|
/// - Tag in eckigen Klammern; bei leerem Tag entsteht ein leeres `[]`.
|
||||||
|
/// - Emoji-Präfix pro Level: 🛑/⚠️/ℹ️/🚧.
|
||||||
|
///
|
||||||
|
/// Beispielausgabe:
|
||||||
|
/// - `ℹ️[18.08.2025 14:23:45.012][INFO][init]: Fertig`
|
||||||
|
///
|
||||||
|
/// Hinweis:
|
||||||
|
/// - Diese Funktion formatiert nur; sie führt keinen I/O aus.
|
||||||
fn format_message(tag: &str, message: &str, log_level: &LogLevel) -> String {
|
fn format_message(tag: &str, message: &str, log_level: &LogLevel) -> String {
|
||||||
let mut prefix: String = String::new();
|
let mut prefix: String = String::new();
|
||||||
|
|
||||||
@@ -84,6 +162,11 @@ fn format_message(tag: &str, message: &str, log_level: &LogLevel) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialisiert das Logdatei-Handle beim ersten Aufruf und liefert eine Referenz darauf.
|
||||||
|
///
|
||||||
|
/// Rückgabe:
|
||||||
|
/// - `&'static Mutex<Option<File>>`: Das Mutex schützt den optionalen Dateihandler.
|
||||||
|
/// `None` bedeutet, dass das Öffnen fehlgeschlagen ist (z. B. fehlende Rechte).
|
||||||
fn get_or_init_log_file() -> &'static Mutex<Option<File>> {
|
fn get_or_init_log_file() -> &'static Mutex<Option<File>> {
|
||||||
LOG_FILE.get_or_init(|| {
|
LOG_FILE.get_or_init(|| {
|
||||||
let file = open_log_file().ok();
|
let file = open_log_file().ok();
|
||||||
@@ -91,6 +174,19 @@ fn get_or_init_log_file() -> &'static Mutex<Option<File>> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Öffnet (und erstellt bei Bedarf) die tagesbasierte Logdatei im Temp-Verzeichnis.
|
||||||
|
///
|
||||||
|
/// Pfadaufbau:
|
||||||
|
/// - Basis: `std::env::temp_dir()`
|
||||||
|
/// - Unterordner: `<programmname>-<konstante-uuid>`
|
||||||
|
/// - Datei: `log-YYYY-MM-DD.log` im Append-Modus
|
||||||
|
///
|
||||||
|
/// Rückgabe:
|
||||||
|
/// - `Ok(File)`, wenn der Ordner erstellt/gefunden und die Datei geöffnet/angelegt werden konnte.
|
||||||
|
/// - `Err(std::io::Error)`, wenn ein I/O-Fehler auftrat.
|
||||||
|
///
|
||||||
|
/// Fehler:
|
||||||
|
/// - Gibt I/O-Fehler unverändert weiter (z. B. beim Erstellen des Ordners oder Öffnen der Datei).
|
||||||
fn open_log_file() -> std::io::Result<File> {
|
fn open_log_file() -> std::io::Result<File> {
|
||||||
let program = program_name();
|
let program = program_name();
|
||||||
let rand = "13692bbf-a93b-43e9-9cc6-f05f94a8cfb6";
|
let rand = "13692bbf-a93b-43e9-9cc6-f05f94a8cfb6";
|
||||||
@@ -114,6 +210,11 @@ fn open_log_file() -> std::io::Result<File> {
|
|||||||
.open(dir)
|
.open(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Liefert den Programmnamen (Dateistamm der aktuellen ausführbaren Datei).
|
||||||
|
///
|
||||||
|
/// Rückgabe:
|
||||||
|
/// - Dateistamm der aktuellen Executable als `String`.
|
||||||
|
/// - Fallback `"app"`, wenn der Name nicht ermittelt werden kann.
|
||||||
fn program_name() -> String {
|
fn program_name() -> String {
|
||||||
env::current_exe()
|
env::current_exe()
|
||||||
.ok()
|
.ok()
|
||||||
|
|||||||
Reference in New Issue
Block a user