From 2d181a436112966c9bae631166bab2efc96122e7 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Wed, 26 Aug 2026 18:54:02 +0200 Subject: [PATCH 1/3] Fix: Passt LICENSE an --- LICENSE | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 866a4a7..e538522 100644 --- a/LICENSE +++ b/LICENSE @@ -208,8 +208,8 @@ If you develop a new program, and you want it to be of the greatest possible use To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. - DockerUpdater - Copyright (C) 2026 Linuxapps + logger + Copyright (C) 2026 Rust-Crates This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. @@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - DockerUpdater Copyright (C) 2026 Linuxapps + logger Copyright (C) 2026 Rust-Crates This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. From ea4fca3f53f61865b5908662a69353e8d209b528 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Wed, 26 Aug 2026 20:09:15 +0200 Subject: [PATCH 2/3] =?UTF-8?q?Feat:=20F=C3=BCgt=20Integrationstests=20f?= =?UTF-8?q?=C3=BCr=20Logging-Funktionen=20und=20verwendet=20f=C3=BCr=20die?= =?UTF-8?q?=20Ermittlung=20des=20Programmnamen=20die=20Crate=20program-ctd?= =?UTF-8?q?ra?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cargo/config.toml | 9 ++ .idea/Logger.iml | 1 + AGENTS.md | 8 +- Cargo.lock | 7 ++ Cargo.toml | 1 + README.md | 11 +-- src/lib.rs | 128 +++------------------------ tests/integration_tests.rs | 173 +++++++++++++++++++++++++++++++++++++ 8 files changed, 210 insertions(+), 128 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 tests/integration_tests.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..8455547 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,9 @@ +[registry] +default = "gitea" + +[registries.gitea] +index = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/" # Sparse index +# index = "https://gitea.creative-dragonslayer.de/Rust-Crates/_cargo-index.git" # Git + +[net] +git-fetch-with-cli = true diff --git a/.idea/Logger.iml b/.idea/Logger.iml index cf84ae4..bbe0a70 100644 --- a/.idea/Logger.iml +++ b/.idea/Logger.iml @@ -3,6 +3,7 @@ + diff --git a/AGENTS.md b/AGENTS.md index 1ed5375..031577f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ Diese Datei dient als Leitfaden und Kontextdokumentation für autonome Agenten u - **Keine Unicode-Emojis im Logging**: Das Logging verwendet einheitliche ASCII-Präfixe (`[!]` Error, `[?]` Warn, `[i]` Info, `[d]` Debug). - **Threadsicherheit**: Sämtliche Zustände (Log-Level, Dateizugriffe, Pfade) sind threadsicher gekapselt. -- **Minimaler Overhead**: Geringe Abhängigkeiten (ausschließlich `time` für formatierte Zeitstempel). +- **Minimaler Overhead**: Geringe Abhängigkeiten (`time` für formatierte Zeitstempel, `program-ctdra` zur Programmnamen-Ermittlung). - **Lizenz**: GNU General Public License v3.0 or later (`GPL-3.0-or-later`). - **Veröffentlichung**: Über Gitea Actions CI/CD in die Gitea Cargo Package Registry. @@ -31,8 +31,10 @@ Logger/ ├── LICENSE # GPL-3.0 Lizenztext ├── README.md # Projektdokumentation & Nutzungsbeispiele ├── AGENTS.md # Entwickler- und Agenten-Richtlinien -└── src/ - └── lib.rs # Hauptimplementierung (LogLevel, log, set_log_level, format_message, Tests) +├── src/ +│ └── lib.rs # Hauptimplementierung (LogLevel, log, set_log_level, format_message) +└── tests/ + └── integration_tests.rs # Integrationstests für alle Modulfunktionen und Traits ``` --- diff --git a/Cargo.lock b/Cargo.lock index 453aa54..f386cc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" name = "logger-ctdra" version = "1.0.2" dependencies = [ + "program-ctdra", "time", ] @@ -51,6 +52,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "program-ctdra" +version = "1.0.0" +source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/" +checksum = "54f20af67f90bf6d10697dd77bb4beb41dafad7fe0fb46aacfc81ad45a8e48bd" + [[package]] name = "quote" version = "1.0.47" diff --git a/Cargo.toml b/Cargo.toml index efcca11..ce15ded 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://gitea.creative-dragonslayer.de/Rust-Crates/logger" description = "Einfaches, threadsicheres Logging-Framework für Rust mit Schweregrad-Filterung, formatierten Ausgaben und automatischer Dateiprotokollierung" [dependencies] +program-ctdra = { version = "1.0.0", registry = "gitea" } time = { version = "0.3.55", features = ["formatting", "macros", "local-offset"] } [profile.release] diff --git a/README.md b/README.md index eab61b4..1c270f3 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - **Einheitliches Format**: `[Präfix][DD.MM.YYYY HH:MM:SS.mmm][LEVEL][Tag]: Nachricht` - **Flexible Konfiguration**: - Log-Level per Code (`set_log_level`), Umgebungsvariable (`LOG_LEVEL` bzw. `RUST_LOG`) oder Fallback (`Info`). - - Einstellbarer Programmname (`set_program_name`) oder automatisches Ermitteln via `current_exe()`. + - Automatisches Ermitteln des Programmnamens via `program-ctdra` (mit zufälligem Fallback-Wert). - Anpassbares Log-Verzeichnis (`set_log_dir`) oder automatische Ablage im Temp-Verzeichnis (`std::env::temp_dir()`). --- @@ -81,17 +81,14 @@ fn run() { } ``` -### Konfiguration des Programmnamens und Logpfads +### Konfiguration des Logpfads ```rust -use logger_ctdra::{set_log_dir, set_program_name}; +use logger_ctdra::set_log_dir; use std::path::PathBuf; fn init() { - // Setzt den App-Namen für das Logverzeichnis (/tmp/-/) - set_program_name("my-service"); - - // Oder ein individuelles Logverzeichnis vorgeben: + // Individuelles Logverzeichnis vorgeben: set_log_dir(PathBuf::from("/var/log/my-service")); } ``` diff --git a/src/lib.rs b/src/lib.rs index 7f7f25e..be4a52d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,9 +99,6 @@ static LOG_LEVEL: AtomicU8 = AtomicU8::new(0); /// Lazy-initialisiertes Handle zur Logdatei; kann `None` sein, falls das Öffnen fehlschlug. static LOG_FILE: OnceLock>> = OnceLock::new(); -/// Optionaler benutzerdefinierter Programmname für die Verzeichnisstruktur im Temp-Ordner. -static PROGRAM_NAME: OnceLock>> = OnceLock::new(); - /// Optionaler benutzerdefinierter Pfad für Logdateien. static CUSTOM_LOG_DIR: OnceLock>> = OnceLock::new(); @@ -151,30 +148,6 @@ pub fn get_log_level() -> LogLevel { LogLevel::Info } -/// Setzt den Programmnamen für die Logdateibenennung explizit. -pub fn set_program_name(name: impl Into) { - let lock = PROGRAM_NAME.get_or_init(|| RwLock::new(None)); - if let Ok(mut guard) = lock.write() { - *guard = Some(name.into()); - } -} - -/// Ermittelt den Programmnamen (aus `set_program_name` oder `std::env::current_exe`). -pub fn get_program_name() -> String { - if let Some(lock) = PROGRAM_NAME.get() { - if let Ok(guard) = lock.read() { - if let Some(name) = guard.as_ref() { - return name.clone(); - } - } - } - env::current_exe() - .ok() - .and_then(|p| p.file_stem().map(|s| s.to_string_lossy().to_string())) - .filter(|s| !s.is_empty()) - .unwrap_or_else(|| "app".to_string()) -} - /// Setzt ein benutzerdefiniertes Verzeichnis für Logdateien. pub fn set_log_dir(path: impl Into) { let lock = CUSTOM_LOG_DIR.get_or_init(|| RwLock::new(None)); @@ -292,6 +265,15 @@ fn get_or_init_log_file() -> &'static Mutex> { }) } +/// Generiert einen zufälligen Fallback-Programmnamen, falls der Name der ausführbaren Datei nicht ermittelt werden kann. +fn random_fallback_program_name() -> String { + use std::hash::{BuildHasher, Hasher, RandomState}; + let mut hasher = RandomState::new().build_hasher(); + hasher.write_i128(OffsetDateTime::now_utc().unix_timestamp_nanos()); + let val = hasher.finish(); + format!("app-{:016x}", val) +} + /// Öffnet (und erstellt bei Bedarf) die tagesbasierte Logdatei. fn open_log_file() -> std::io::Result { let custom_dir = if let Some(lock) = CUSTOM_LOG_DIR.get() { @@ -305,7 +287,7 @@ fn open_log_file() -> std::io::Result { }; let mut dir = custom_dir.unwrap_or_else(|| { - let program = get_program_name(); + let program = program_ctdra::program_name_or(random_fallback_program_name()); let mut d = env::temp_dir(); d.push(format!("{}-{}", program, DEFAULT_LOG_UUID)); d @@ -324,93 +306,3 @@ fn open_log_file() -> std::io::Result { OpenOptions::new().create(true).append(true).open(dir) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_format_message_prefixes() { - let msg_info = format_message("test", "Info message", &LogLevel::Info); - assert!(msg_info.starts_with("[i]")); - assert!(msg_info.contains("[INFO][test]: Info message")); - - let msg_err = format_message("test", "Error message", &LogLevel::Error); - assert!(msg_err.starts_with("[!]")); - assert!(msg_err.contains("[ERROR][test]: Error message")); - - let msg_warn = format_message("test", "Warn message", &LogLevel::Warn); - assert!(msg_warn.starts_with("[?]")); - assert!(msg_warn.contains("[WARN][test]: Warn message")); - - let msg_debug = format_message("test", "Debug message", &LogLevel::Debug); - assert!(msg_debug.starts_with("[d]")); - assert!(msg_debug.contains("[DEBUG][test]: Debug message")); - } - - #[test] - fn test_format_message_empty_tag() { - let msg = format_message("", "No tag message", &LogLevel::Info); - assert!(msg.starts_with("[i]")); - assert!(msg.contains("[INFO]: No tag message")); - } - - #[test] - fn test_log_level_display() { - assert_eq!(LogLevel::Error.to_string(), "ERROR"); - assert_eq!(LogLevel::Warn.to_string(), "WARN"); - assert_eq!(LogLevel::Info.to_string(), "INFO"); - assert_eq!(LogLevel::Debug.to_string(), "DEBUG"); - } - - #[test] - fn test_log_level_parsing() { - assert_eq!(LogLevel::try_from("error").unwrap(), LogLevel::Error); - assert_eq!(LogLevel::try_from("ERROR").unwrap(), LogLevel::Error); - assert_eq!(LogLevel::try_from("warn").unwrap(), LogLevel::Warn); - assert_eq!(LogLevel::try_from("warning").unwrap(), LogLevel::Warn); - assert_eq!(LogLevel::try_from("info").unwrap(), LogLevel::Info); - assert_eq!(LogLevel::try_from("debug").unwrap(), LogLevel::Debug); - assert_eq!(LogLevel::try_from("invalid").unwrap_err(), LogLevel::Info); - assert_eq!(LogLevel::from_str("debug").unwrap(), LogLevel::Debug); - } - - #[test] - fn test_log_level_ordering() { - assert!(LogLevel::Error < LogLevel::Warn); - assert!(LogLevel::Warn < LogLevel::Info); - assert!(LogLevel::Info < LogLevel::Debug); - } - - #[test] - fn test_set_and_get_log_level() { - set_log_level(LogLevel::Debug); - assert_eq!(get_log_level(), LogLevel::Debug); - - set_log_level(LogLevel::Error); - assert_eq!(get_log_level(), LogLevel::Error); - - set_log_level(LogLevel::Info); - assert_eq!(get_log_level(), LogLevel::Info); - } - - #[test] - fn test_convenience_functions() { - set_log_level(LogLevel::Debug); - error("test", "An error occurred"); - warn("test", "A warning occurred"); - info("test", "An info message"); - debug("test", "A debug message"); - } - - #[test] - fn test_program_name_and_custom_dir() { - set_program_name("my-custom-app"); - assert_eq!(get_program_name(), "my-custom-app"); - - let custom_dir = env::temp_dir().join("test-logger-custom-dir"); - set_log_dir(custom_dir.clone()); - let file = open_log_file(); - assert!(file.is_ok()); - } -} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..001bebb --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,173 @@ +use logger_ctdra::{ + debug, error, format_message, get_log_level, info, init, log, set_log_dir, set_log_level, + warn, LogLevel, +}; +use std::env; +use std::str::FromStr; + +#[test] +fn test_format_message_prefixes() { + let msg_info = format_message("test", "Info message", &LogLevel::Info); + assert!(msg_info.starts_with("[i]")); + assert!(msg_info.contains("[INFO][test]: Info message")); + + let msg_err = format_message("test", "Error message", &LogLevel::Error); + assert!(msg_err.starts_with("[!]")); + assert!(msg_err.contains("[ERROR][test]: Error message")); + + let msg_warn = format_message("test", "Warn message", &LogLevel::Warn); + assert!(msg_warn.starts_with("[?]")); + assert!(msg_warn.contains("[WARN][test]: Warn message")); + + let msg_debug = format_message("test", "Debug message", &LogLevel::Debug); + assert!(msg_debug.starts_with("[d]")); + assert!(msg_debug.contains("[DEBUG][test]: Debug message")); +} + +#[test] +fn test_format_message_empty_tag() { + let msg = format_message("", "No tag message", &LogLevel::Info); + assert!(msg.starts_with("[i]")); + assert!(msg.contains("[INFO]: No tag message")); + assert!(!msg.contains("[INFO][]")); +} + +#[test] +fn test_format_message_timestamp_format() { + let msg = format_message("tag", "payload", &LogLevel::Info); + // Format: [i][DD.MM.YYYY HH:MM:SS.mmm][INFO][tag]: payload + assert!(msg.starts_with("[i][")); + let closing_bracket = msg[3..].find(']').expect("Timestamp closing bracket"); + let timestamp = &msg[4..4 + closing_bracket - 1]; + // Timestamp format: DD.MM.YYYY HH:MM:SS.mmm (length: 23) + assert_eq!(timestamp.len(), 23); + assert_eq!(×tamp[2..3], "."); + assert_eq!(×tamp[5..6], "."); + assert_eq!(×tamp[10..11], " "); + assert_eq!(×tamp[13..14], ":"); + assert_eq!(×tamp[16..17], ":"); + assert_eq!(×tamp[19..20], "."); +} + +#[test] +fn test_log_level_display() { + assert_eq!(LogLevel::Error.to_string(), "ERROR"); + assert_eq!(LogLevel::Warn.to_string(), "WARN"); + assert_eq!(LogLevel::Info.to_string(), "INFO"); + assert_eq!(LogLevel::Debug.to_string(), "DEBUG"); +} + +#[test] +fn test_log_level_parsing_str() { + assert_eq!(LogLevel::try_from("error").unwrap(), LogLevel::Error); + assert_eq!(LogLevel::try_from("ERROR").unwrap(), LogLevel::Error); + assert_eq!(LogLevel::try_from(" error ").unwrap(), LogLevel::Error); + assert_eq!(LogLevel::try_from("warn").unwrap(), LogLevel::Warn); + assert_eq!(LogLevel::try_from("warning").unwrap(), LogLevel::Warn); + assert_eq!(LogLevel::try_from("WARN").unwrap(), LogLevel::Warn); + assert_eq!(LogLevel::try_from("WARNING").unwrap(), LogLevel::Warn); + assert_eq!(LogLevel::try_from("info").unwrap(), LogLevel::Info); + assert_eq!(LogLevel::try_from("INFO").unwrap(), LogLevel::Info); + assert_eq!(LogLevel::try_from("debug").unwrap(), LogLevel::Debug); + assert_eq!(LogLevel::try_from("DEBUG").unwrap(), LogLevel::Debug); + assert_eq!(LogLevel::try_from("invalid").unwrap_err(), LogLevel::Info); + assert_eq!(LogLevel::try_from("").unwrap_err(), LogLevel::Info); +} + +#[test] +fn test_log_level_parsing_string_and_from_str() { + assert_eq!( + LogLevel::try_from(String::from("error")).unwrap(), + LogLevel::Error + ); + assert_eq!( + LogLevel::try_from(String::from("WARN")).unwrap(), + LogLevel::Warn + ); + assert_eq!( + LogLevel::try_from(String::from("invalid")).unwrap_err(), + LogLevel::Info + ); + + assert_eq!(LogLevel::from_str("debug").unwrap(), LogLevel::Debug); + assert_eq!(LogLevel::from_str("INFO").unwrap(), LogLevel::Info); + assert_eq!(LogLevel::from_str("unknown").unwrap_err(), LogLevel::Info); +} + +#[test] +fn test_log_level_discriminants_and_traits() { + assert_eq!(LogLevel::Error as u8, 1); + assert_eq!(LogLevel::Warn as u8, 2); + assert_eq!(LogLevel::Info as u8, 3); + assert_eq!(LogLevel::Debug as u8, 4); + + let lvl = LogLevel::Info; + let lvl_cloned = lvl; + assert_eq!(lvl, lvl_cloned); + assert_eq!(format!("{:?}", LogLevel::Debug), "Debug"); +} + +#[test] +fn test_log_level_ordering() { + assert!(LogLevel::Error < LogLevel::Warn); + assert!(LogLevel::Warn < LogLevel::Info); + assert!(LogLevel::Info < LogLevel::Debug); + assert!(LogLevel::Error <= LogLevel::Error); + assert!(LogLevel::Debug >= LogLevel::Info); +} + +#[test] +fn test_set_and_get_log_level() { + set_log_level(LogLevel::Debug); + assert_eq!(get_log_level(), LogLevel::Debug); + + set_log_level(LogLevel::Error); + assert_eq!(get_log_level(), LogLevel::Error); + + set_log_level(LogLevel::Warn); + assert_eq!(get_log_level(), LogLevel::Warn); + + set_log_level(LogLevel::Info); + assert_eq!(get_log_level(), LogLevel::Info); +} + +#[test] +fn test_init_alias() { + init(LogLevel::Debug); + assert_eq!(get_log_level(), LogLevel::Debug); + init(LogLevel::Warn); + assert_eq!(get_log_level(), LogLevel::Warn); +} + +#[test] +fn test_convenience_functions() { + set_log_level(LogLevel::Debug); + error("test", "An error occurred"); + warn("test", "A warning occurred"); + info("test", "An info message"); + debug("test", "A debug message"); +} + +#[test] +fn test_log_filtering_behavior() { + set_log_level(LogLevel::Error); + // Debug and Info should be filtered out, Error should pass + log("filter_test", "This debug log should be ignored", LogLevel::Debug); + log("filter_test", "This info log should be ignored", LogLevel::Info); + log("filter_test", "This warn log should be ignored", LogLevel::Warn); + log("filter_test", "This error log should pass", LogLevel::Error); + + set_log_level(LogLevel::Debug); + log("filter_test", "All logs should pass in Debug level", LogLevel::Debug); +} + +#[test] +fn test_set_log_dir_string_and_pathbuf() { + let dir_str = env::temp_dir().join("test-integration-str-dir"); + set_log_dir(dir_str.to_string_lossy().to_string()); + + let dir_pathbuf = env::temp_dir().join("test-integration-pathbuf-dir"); + set_log_dir(dir_pathbuf); + + set_log_dir("/tmp/test-str-slice-dir"); +} From 1c7c4845fb9c5f601e4c90a20a8d54e0da2995df Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Wed, 26 Aug 2026 20:10:34 +0200 Subject: [PATCH 3/3] Feat: Passt Version auf 1.0.3 an. --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f386cc3..c283804 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "logger-ctdra" -version = "1.0.2" +version = "1.0.3" dependencies = [ "program-ctdra", "time", diff --git a/Cargo.toml b/Cargo.toml index ce15ded..1713808 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "logger-ctdra" -version = "1.0.2" +version = "1.0.3" edition = "2024" authors = ['DragonSlayer_14'] readme = "README.md"