Author SHA1 Message Date
DragonSlayer_14 2bd85e61bf Merge pull request 'Merge dev in testing: Feat: Fügt Workflows für Security-Scans, Code-Qualität, Unit-Tests und Dependency-Management hinzu sowie Python-Skript zur Erstellung von Security-Berichten. Hebt Version auf 1.0.4 an.' (#9) from dev into testing
Unit-Tests / Unit-Tests (pull_request) Skipped
Security Scans / Trivy & OSV-Scanner (push) Successful in 28s
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 40s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 18s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 33s
TruffleHog Secret Scan / TruffleHog (push) Successful in 20s
Reviewed-on: #9
2026-09-12 22:56:33 +00:00
Gitea-Bot 95d2ea6d9c Style: Automatische Formatierung & Clippy-Fixes
TruffleHog Secret Scan / TruffleHog (push) Successful in 16s
Security Scans / Trivy & OSV-Scanner (push) Successful in 26s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 27s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 16s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 26s
Unit-Tests / Unit-Tests (pull_request) Successful in 30s
2026-09-12 22:36:36 +00:00
Gitea-Bot 3abd8d9c24 Style: Automatische Formatierung & Clippy-Fixes
TruffleHog Secret Scan / TruffleHog (push) Successful in 16s
Security Scans / Trivy & OSV-Scanner (push) Successful in 26s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 28s
2026-09-12 22:36:04 +00:00
DragonSlayer_14 01f6948c2b Feat: Fügt Workflows für Security-Scans, Code-Qualität, Unit-Tests und Dependency-Management hinzu sowie Python-Skript zur Erstellung von Security-Berichten. Hebt Version auf 1.0.4 an.
TruffleHog Secret Scan / TruffleHog (push) Successful in 18s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 35s
Security Scans / Trivy & OSV-Scanner (push) Successful in 37s
2026-09-13 00:35:25 +02:00
DragonSlayer_14 84e6796455 Merge pull request 'Merge dev in testing: Ersetzt die Ermittlung des Programmnamen durch die Crate program-ctdra' (#7) from dev into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 23s
Reviewed-on: #7
2026-08-26 18:11:10 +00:00
DragonSlayer_14 1c7c4845fb Feat: Passt Version auf 1.0.3 an. 2026-08-26 20:10:34 +02:00
DragonSlayer_14 258ee26496 Merge remote-tracking branch 'origin/main' into dev 2026-08-26 20:09:55 +02:00
DragonSlayer_14 ea4fca3f53 Feat: Fügt Integrationstests für Logging-Funktionen und verwendet für die Ermittlung des Programmnamen die Crate program-ctdra 2026-08-26 20:09:15 +02:00
DragonSlayer_14 2d181a4361 Fix: Passt LICENSE an 2026-08-26 18:54:02 +02:00
DragonSlayer_14 f5b14f635e Merge pull request 'Merge testing in dev: Release 1.0.2' (#6) from testing into main
Main Release & Publish / Build, Publish Crate to Gitea Registry & Create Release (push) Successful in 22s
Reviewed-on: #6
2026-08-26 16:39:56 +00:00
DragonSlayer_14 abbe281549 Merge branch 'main' into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 22s
2026-08-26 16:39:21 +00:00
DragonSlayer_14 fa1c522090 Merge pull request 'Merge dev in testing: Passt Versionsnummer auf 1.0.2 an' (#5) from dev into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 22s
Reviewed-on: #5
2026-08-26 16:38:18 +00:00
DragonSlayer_14 0c2d898ea0 Merge branch 'testing' into dev 2026-08-26 16:38:07 +00:00
DragonSlayer_14 e721879b9a Merge remote-tracking branch 'origin/dev' into dev 2026-08-26 18:37:39 +02:00
DragonSlayer_14 477b27e9b9 Feat: Ändert Versionsnummer auf 1.0.2 2026-08-26 18:37:35 +02:00
DragonSlayer_14 8c58475171 Merge pull request 'Merge dev in testing: Fix: Benennt auch die anderen Vorkomnisse des Crate-Namens um' (#4) from dev into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 23s
Reviewed-on: #4
2026-08-26 16:36:02 +00:00
DragonSlayer_14 e28bd22e4e Merge branch 'testing' into dev 2026-08-26 16:35:49 +00:00
DragonSlayer_14 d66c8e23cb Merge remote-tracking branch 'origin/dev' into dev 2026-08-26 18:34:58 +02:00
DragonSlayer_14 09fc0c9153 Fix: Benennt auch die anderen Vorkomnisse des Crate-Namens um 2026-08-26 18:34:54 +02:00
DragonSlayer_14 7eedc84fe5 Merge pull request 'Merge dev in testing: Feat: Benennt Crate um, um Verwechslungen zu vermeiden.' (#3) from dev into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Failing after 19s
Reviewed-on: #3
2026-08-26 16:23:46 +00:00
DragonSlayer_14 0815b6a8ba Merge branch 'testing' into dev 2026-08-26 16:23:37 +00:00
DragonSlayer_14 b1689dc255 Feat: Benennt Crate um, um Verwechslungen zu vermeiden. 2026-08-26 18:23:05 +02:00
DragonSlayer_14 c99b3cef43 Merge pull request 'Merge testing in main: Release 1.0.1' (#2) from testing into main
Main Release & Publish / Build, Publish Crate to Gitea Registry & Create Release (push) Successful in 24s
Reviewed-on: #2
2026-08-24 20:37:24 +00:00
20 changed files with 1025 additions and 163 deletions
+9
View File
@@ -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
+54
View File
@@ -0,0 +1,54 @@
name: Code Quality (Auto-Format & Clippy-Fix)
on:
push:
branches:
- dev
workflow_dispatch:
jobs:
fix:
name: Formatierung & Clippy automatisch beheben
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
ref: ${{ gitea.ref_name || github.ref_name }}
token: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
components: clippy, rustfmt
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Formatierung automatisch beheben
run: cargo fmt
- name: Clippy-Fixes automatisch anwenden
run: cargo clippy --fix --allow-dirty --allow-staged --all-targets
- name: Änderungen committen & pushen
run: |
if [ -n "$(git status --porcelain)" ]; then
git config user.name "Gitea-Bot"
git config user.email "no-reply@creativedragonslayer.de"
git add -A
git commit -m "Style: Automatische Formatierung & Clippy-Fixes"
git push origin HEAD:${{ gitea.ref_name || github.ref_name }}
else
echo "Keine Formatierungs- oder Clippy-Änderungen."
fi
+14 -2
View File
@@ -14,11 +14,22 @@ jobs:
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: |
cargo test
@@ -58,12 +69,13 @@ jobs:
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }}
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
run: |
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
TAG_NAME="v${VERSION}"
RELEASE_TITLE="Release ${TAG_NAME}"
RELEASE_NOTES="Automatisches Release für logger Crate ${VERSION}."
RELEASE_NOTES="Automatisches Release für ${REPO_NAME} Crate ${VERSION}."
echo "Erstelle oder hole Release für Tag ${TAG_NAME} in ${REPO}..."
+26
View File
@@ -0,0 +1,26 @@
name: Renovate
on:
schedule:
- cron: "0 * * * *"
workflow_dispatch:
jobs:
renovate:
name: Dependency-Updates prüfen & Pull Requests erstellen
runs-on: ubuntu-latest
container: ghcr.io/renovatebot/renovate:44.82.0
steps:
- name: Renovate ausführen
run: renovate
env:
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: ${{ gitea.server_url || github.server_url }}/api/v1/
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_REPOSITORIES: ${{ gitea.repository || github.repository }}
RENOVATE_AUTODISCOVER: "false"
RENOVATE_GIT_AUTHOR: "Renovate Bot <renovate-bot@creativedragonslayer.de>"
RENOVATE_HOST_RULES: >-
[{"hostType":"cargo","matchHost":"${{ gitea.server_url || github.server_url }}","token":"${{ secrets.RENOVATE_TOKEN }}"}]
GITHUB_COM_TOKEN: ${{ secrets.GH_RENOVATE_TOKEN }}
LOG_LEVEL: info
+95
View File
@@ -0,0 +1,95 @@
name: Security Scans
on:
push:
branches:
- main
- testing
- dev
pull_request:
schedule:
- cron: "0 5 * * 1"
workflow_dispatch:
jobs:
security-scan:
name: Trivy & OSV-Scanner
runs-on: ubuntu-latest
env:
TRIVY_VERSION: "0.74.0"
OSV_SCANNER_VERSION: "2.5.1"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Lokales bin-Verzeichnis zum PATH hinzufügen
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Cache Trivy-Binary
id: cache-trivy
uses: actions/cache@v6
with:
path: ~/.local/bin/trivy
key: trivy-bin-${{ runner.os }}-${{ env.TRIVY_VERSION }}
- name: Install Trivy
if: steps.cache-trivy.outputs.cache-hit != 'true'
run: |
curl -fsSL -o trivy.tar.gz \
"https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
tar -xzf trivy.tar.gz trivy
chmod +x trivy
mv trivy "$HOME/.local/bin/trivy"
rm -f trivy.tar.gz
- name: Ermittle Cache-Datum für Trivy-DB
run: echo "CACHE_DATE=$(date -u +%Y-%m-%d)" >> "$GITHUB_ENV"
- name: Cache Trivy-Schwachstellen-Datenbank
uses: actions/cache@v6
with:
path: ~/.cache/trivy
key: trivy-db-${{ runner.os }}-${{ env.CACHE_DATE }}
restore-keys: |
trivy-db-${{ runner.os }}-
- name: Run Trivy Scanner
run: |
trivy fs \
--scanners vuln,secret,misconfig \
--severity CRITICAL,HIGH \
--format json \
--output trivy-results.json \
--exit-code 0 \
.
- name: Cache OSV-Scanner-Binary
id: cache-osv-scanner
uses: actions/cache@v6
with:
path: ~/.local/bin/osv-scanner
key: osv-scanner-bin-${{ runner.os }}-${{ env.OSV_SCANNER_VERSION }}
- name: Install OSV-Scanner
if: steps.cache-osv-scanner.outputs.cache-hit != 'true'
run: |
curl -fsSL -o "$HOME/.local/bin/osv-scanner" \
"https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}/osv-scanner_linux_amd64"
chmod +x "$HOME/.local/bin/osv-scanner"
- name: Run OSV-Scanner
run: |
set +e
osv-scanner scan source --recursive --format json --output-file osv-results.json .
echo "OSV_EXIT=$?" >> "$GITHUB_ENV"
- name: Ergebnisse & Gitea-Issue erstellen/aktualisieren
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
TOKEN: ${{ secrets.SECURITY_TOKEN }}
RUN_URL: ${{ gitea.server_url || github.server_url }}/${{ gitea.repository || github.repository }}/actions/runs/${{ gitea.run_id || github.run_id }}
run: |
python3 scripts/report-security-issue.py trivy-results.json osv-results.json
+14 -2
View File
@@ -14,11 +14,22 @@ jobs:
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: |
cargo test
@@ -35,12 +46,13 @@ jobs:
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }}
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
run: |
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
TAG_NAME="v${VERSION}-preview"
RELEASE_TITLE="Preview Release ${TAG_NAME}"
RELEASE_NOTES="Automatisches Preview-Release für logger Crate ${VERSION} (Branch: Testing)."
RELEASE_NOTES="Automatisches Preview-Release für ${REPO_NAME} Crate ${VERSION} (Branch: Testing)."
echo "Erstelle oder hole Preview-Release für Tag ${TAG_NAME} in ${REPO}..."
+59
View File
@@ -0,0 +1,59 @@
name: TruffleHog Secret Scan
on:
push:
pull_request:
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
jobs:
trufflehog-scan:
name: TruffleHog
runs-on: ubuntu-latest
env:
TRUFFLEHOG_VERSION: "3.97.4"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Lokales bin-Verzeichnis zum PATH hinzufügen
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Cache TruffleHog-Binary
id: cache-trufflehog
uses: actions/cache@v6
with:
path: ~/.local/bin/trufflehog
key: trufflehog-bin-${{ runner.os }}-${{ env.TRUFFLEHOG_VERSION }}
- name: Install TruffleHog
if: steps.cache-trufflehog.outputs.cache-hit != 'true'
run: |
curl -fsSL -o trufflehog.tar.gz \
"https://github.com/trufflesecurity/trufflehog/releases/download/v${TRUFFLEHOG_VERSION}/trufflehog_${TRUFFLEHOG_VERSION}_linux_amd64.tar.gz"
tar -xzf trufflehog.tar.gz trufflehog
chmod +x trufflehog
mv trufflehog "$HOME/.local/bin/trufflehog"
rm trufflehog.tar.gz
- name: Run TruffleHog Scanner
run: |
set +e
trufflehog git file://. --results=verified,unknown --fail --json > trufflehog-results.json
echo "TRUFFLEHOG_EXIT=$?" >> "$GITHUB_ENV"
- name: Ergebnisse & Gitea-Issue erstellen/aktualisieren
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
TOKEN: ${{ secrets.SECURITY_TOKEN }}
RUN_URL: ${{ gitea.server_url || github.server_url }}/${{ gitea.repository || github.repository }}/actions/runs/${{ gitea.run_id || github.run_id }}
ISSUE_TITLE: "Security-Scan: TruffleHog Secrets"
ISSUE_LABEL: "security-scan-trufflehog"
run: |
python3 scripts/report-security-issue.py "" "" trufflehog-results.json
+38
View File
@@ -0,0 +1,38 @@
name: Unit-Tests
on:
pull_request:
types:
- opened
- synchronize
- reopened
branches:
- testing
jobs:
test:
name: Unit-Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: cargo test
+2
View File
@@ -108,3 +108,5 @@ fabric.properties
# Built Visual Studio Code Extensions
*.vsix
.junie/plans
+1
View File
@@ -3,6 +3,7 @@
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
+7 -5
View File
@@ -1,18 +1,18 @@
# AGENTS.md — Entwickler- und Agenten-Dokumentation für Logger
Diese Datei dient als Leitfaden und Kontextdokumentation für autonome Agenten und Entwickler, die an der Codebasis der Crate `logger` arbeiten.
Diese Datei dient als Leitfaden und Kontextdokumentation für autonome Agenten und Entwickler, die an der Codebasis der Crate `logger-ctdra` arbeiten.
---
## 1. Projektübersicht
`logger` ist eine leichtgewichtige, threadsichere Rust-Crate (Edition 2024), die synchron auf Standard-Streams (`stdout`/`stderr`) und in tagesbasierte Logdateien protokolliert.
`logger-ctdra` ist eine leichtgewichtige, threadsichere Rust-Crate (Edition 2024), die synchron auf Standard-Streams (`stdout`/`stderr`) und in tagesbasierte Logdateien protokolliert.
### Wichtige Rahmenbedingungen & Prinzipien
- **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
```
---
Generated
+9 -2
View File
@@ -15,9 +15,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "logger"
version = "1.0.1"
name = "logger-ctdra"
version = "1.0.4"
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"
+3 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "logger"
version = "1.0.1"
name = "logger-ctdra"
version = "1.0.4"
edition = "2024"
authors = ['DragonSlayer_14']
readme = "README.md"
@@ -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]
+3 -3
View File
@@ -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.
+11 -14
View File
@@ -1,6 +1,6 @@
# Logger (`logger`)
# Logger (`logger-ctdra`)
`logger` ist eine leichtgewichtige, threadsichere Logging-Crate für Rust mit Schweregrad-Filterung, formatierten Terminalausgaben und automatischer Dateiprotokollierung.
`logger-ctdra` ist eine leichtgewichtige, threadsichere Logging-Crate für Rust mit Schweregrad-Filterung, formatierten Terminalausgaben und automatischer Dateiprotokollierung.
## Merkmale
@@ -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()`).
---
@@ -34,16 +34,16 @@ Füge anschließend die Abhängigkeit in deiner `Cargo.toml` ein:
```toml
[dependencies]
logger = { version = "1.0.0", registry = "gitea" }
logger-ctdra = { version = "1.0.1", registry = "gitea" }
```
Alternativ als Git- oder Pfad-Abhängigkeit:
```toml
[dependencies]
logger = { git = "https://gitea.creative-dragonslayer.de/Rust-Crates/logger.git", branch = "main" }
logger-ctdra = { git = "https://gitea.creative-dragonslayer.de/Rust-Crates/logger.git", branch = "main" }
# oder lokal:
# logger = { path = "../Logger" }
# logger-ctdra = { path = "../Logger" }
```
---
@@ -53,7 +53,7 @@ logger = { git = "https://gitea.creative-dragonslayer.de/Rust-Crates/logger.git"
### Grundlegendes Beispiel
```rust
use logger::{log, set_log_level, LogLevel};
use logger_ctdra::{log, set_log_level, LogLevel};
fn main() {
// Optional: Log-Level setzen (Standard: Info)
@@ -71,7 +71,7 @@ fn main() {
Für häufige Aufrufe stehen direkte Hilfsfunktionen zur Verfügung:
```rust
use logger::{debug, error, info, warn};
use logger_ctdra::{debug, error, info, warn};
fn run() {
info("service", "Dienst bereit");
@@ -81,17 +81,14 @@ fn run() {
}
```
### Konfiguration des Programmnamens und Logpfads
### Konfiguration des Logpfads
```rust
use logger::{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/<app-name>-<uuid>/)
set_program_name("my-service");
// Oder ein individuelles Logverzeichnis vorgeben:
// Individuelles Logverzeichnis vorgeben:
set_log_dir(PathBuf::from("/var/log/my-service"));
}
```
+50
View File
@@ -0,0 +1,50 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
#################################################################################
# WARNING: Do not store sensitive information in this file, #
# as its contents will be included in the Qodana report. #
#################################################################################
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
# severityThresholds - configures maximum thresholds for different problem severities
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
# dependencyLicenses - fails the run on prohibited or unknown dependency licenses
# Code Coverage is available in Ultimate and Ultimate Plus plans
#failureConditions:
# severityThresholds:
# any: 15
# critical: 5
# testCoverageThresholds:
# fresh: 70
# total: 50
# dependencyLicenses:
# failOnProhibited: true
# failOnUnknown: false
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-<linter>:2026.2
+102
View File
@@ -0,0 +1,102 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"timezone": "Europe/Berlin",
"schedule": ["before 6am on monday"],
"allowCustomCrateRegistries": true,
"baseBranchPatterns": [
"dev"
],
"packageRules": [
{
"matchFileNames": [".gitea/workflows/**"],
"groupName": "Gitea Actions",
"separateMajorMinor": false,
"separateMinorPatch": false
},
{
"matchManagers": ["cargo"],
"groupName": "Cargo Dependencies",
"separateMajorMinor": false,
"separateMinorPatch": false
},
{
"matchManagers": ["dockerfile", "docker-compose"],
"groupName": "Docker-Images",
"separateMajorMinor": false,
"separateMinorPatch": false
}
],
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"TRIVY_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "aquasecurity/trivy",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"OSV_SCANNER_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "google/osv-scanner",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"TRUFFLEHOG_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "trufflesecurity/trufflehog",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_BINSTALL_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-bins/cargo-binstall",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_DEB_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-deb",
"datasourceTemplate": "crate"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_GENERATE_RPM_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-generate-rpm",
"datasourceTemplate": "crate"
}
]
}
+312
View File
@@ -0,0 +1,312 @@
#!/usr/bin/env python3
"""Erstellt oder kommentiert ein Gitea-Issue mit den Ergebnissen der Security-Scans.
Sucht ein offenes Issue mit dem Label ISSUE_LABEL (Standard: "security-scan").
Existiert eines, wird der aktuelle Scan-Stand als neuer Kommentar angehängt
(die Historie bleibt erhalten). Existiert keines (z.B. weil das letzte
geschlossen wurde), wird ein neues Issue erstellt. Gibt es keine Funde mehr,
wird ein offenes Issue nur kommentiert, nicht geschlossen.
Titel und Label lassen sich per Umgebungsvariable ISSUE_TITLE / ISSUE_LABEL
überschreiben, damit z.B. TruffleHog-Funde in ein eigenes Issue laufen statt
in das gemeinsame Trivy/OSV-Issue.
"""
import json
import os
import sys
import urllib.error
import urllib.request
LABEL_NAME = os.environ.get("ISSUE_LABEL", "security-scan")
LABEL_COLOR = "#b60205"
ISSUE_TITLE = os.environ.get("ISSUE_TITLE", "Security-Scan: Offene Schwachstellen")
SEVERITY_ORDER = {
"VERIFIED": -1,
"CRITICAL": 0,
"HIGH": 1,
"MEDIUM": 2,
"LOW": 3,
"UNKNOWN": 4,
"UNVERIFIED": 6,
}
def api(method, path, token, gitea_url, data=None):
url = f"{gitea_url}/api/v1{path}"
body = json.dumps(data).encode() if data is not None else None
req = urllib.request.Request(url, data=body, method=method)
req.add_header("Authorization", f"token {token}")
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req, timeout=10) as resp:
raw = resp.read()
return json.loads(raw) if raw else None
except urllib.error.HTTPError as e:
print(f"Gitea API Fehler ({method} {path}): {e.code} {e.read().decode()}", file=sys.stderr)
raise
def make_finding(source, id, severity, package, installed="-", fixed="-", target="-"):
return {
"source": source,
"id": id,
"severity": severity,
"package": package,
"installed": installed,
"fixed": fixed,
"target": target,
}
def cvss_score_to_severity(score):
try:
score = float(score)
except (TypeError, ValueError):
return "UNKNOWN"
if score >= 9.0:
return "CRITICAL"
if score >= 7.0:
return "HIGH"
if score >= 4.0:
return "MEDIUM"
if score > 0.0:
return "LOW"
return "UNKNOWN"
def load_trivy(path):
findings = []
if not path or not os.path.isfile(path):
return findings
with open(path) as f:
data = json.load(f)
for result in data.get("Results", []) or []:
target = result.get("Target", "?")
for vuln in result.get("Vulnerabilities", []) or []:
findings.append(make_finding(
"Trivy",
vuln.get("VulnerabilityID", "?"),
vuln.get("Severity", "UNKNOWN"),
vuln.get("PkgName", "?"),
installed=vuln.get("InstalledVersion", "?"),
fixed=vuln.get("FixedVersion") or "-",
target=target,
))
for misc in result.get("Misconfigurations", []) or []:
findings.append(make_finding(
"Trivy (Misconfig)",
misc.get("ID", "?"),
misc.get("Severity", "UNKNOWN"),
misc.get("Title", "?"),
target=target,
))
for secret in result.get("Secrets", []) or []:
findings.append(make_finding(
"Trivy (Secret)",
secret.get("RuleID", "?"),
secret.get("Severity", "UNKNOWN"),
secret.get("Title", "?"),
target=target,
))
return findings
def load_osv(path):
findings = []
if not path or not os.path.isfile(path):
return findings
with open(path) as f:
data = json.load(f)
for result in data.get("results", []) or []:
source = (result.get("source") or {}).get("path", "?")
for pkg in result.get("packages", []) or []:
info = pkg.get("package", {})
pkg_name = f"{info.get('name', '?')} ({info.get('ecosystem', '?')})"
severity_by_id = {}
for group in pkg.get("groups", []) or []:
label = cvss_score_to_severity(group.get("max_severity"))
for vuln_id in group.get("ids", []) or []:
severity_by_id[vuln_id] = label
for vuln in pkg.get("vulnerabilities", []) or []:
vuln_id = vuln.get("id", "?")
findings.append(make_finding(
"OSV-Scanner",
vuln_id,
severity_by_id.get(vuln_id, "UNKNOWN"),
pkg_name,
installed=info.get("version", "?"),
target=source,
))
return findings
def load_trufflehog(path):
findings = []
if not path or not os.path.isfile(path):
return findings
with open(path) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
except json.JSONDecodeError:
continue
git_meta = ((entry.get("SourceMetadata") or {}).get("Data") or {}).get("Git") or {}
findings.append(make_finding(
"TruffleHog",
entry.get("DetectorName", "?"),
"VERIFIED" if entry.get("Verified") else "UNVERIFIED",
git_meta.get("file", "?"),
target=git_meta.get("commit", "-"),
))
return findings
def sort_findings(findings):
return sorted(findings, key=lambda f: (SEVERITY_ORDER.get(f["severity"], 9), f["id"]))
def print_summary(findings):
if not findings:
print("Keine Funde.")
return
widths = {
key: max(len(key), *(len(str(f[key])) for f in findings))
for key in ("source", "id", "severity", "package", "installed", "fixed", "target")
}
header = ("source", "id", "severity", "package", "installed", "fixed", "target")
row_fmt = " ".join(f"{{:{widths[k]}}}" for k in header)
print(row_fmt.format(*header))
print(row_fmt.format(*("-" * widths[k] for k in header)))
for f in findings:
print(row_fmt.format(*(str(f[k]) for k in header)))
TRUFFLEHOG_GUIDANCE = """### Vorgehen bei gefundenen Secrets
1. **Sofort rotieren/widerrufen**: Das betroffene Secret (Token, Passwort, Schlüssel) beim jeweiligen Dienst ungültig machen und durch ein neues ersetzen. Ein einmal committetes Secret gilt als kompromittiert, auch wenn es später aus der Historie entfernt wird.
2. **Ursache beheben**: Neues Secret nur noch über Umgebungsvariablen/Secrets-Store einbinden, nicht erneut hart codieren.
3. **Historie bereinigen (optional, manuell, erst nach Schritt 1)**: Mit `git filter-repo` oder BFG Repo-Cleaner den Commit-Inhalt entfernen, danach `git push --force` in Absprache mit allen Mitwirkenden bestehende Clones/Forks werden dadurch ungültig.
4. **Issue schließen**, sobald rotiert wurde. TruffleHog findet das alte Secret ggf. weiterhin in der Historie nach der Rotation ist das unkritisch.
> Der Wert des Secrets selbst wird hier bewusst nicht ausgegeben, auch nicht gekürzt nur Detector, Datei und Commit. Fund lässt sich über "Ziel" (Commit-Hash) und "Paket" (Dateipfad) lokalisieren."""
def escape_md_cell(value):
return str(value).replace("|", "\\|").replace("\r", " ").replace("\n", " ")
def build_report(findings, run_url):
lines = [
"Automatisch erstellt vom Security-Scan-Workflow.",
f"Lauf: {run_url}" if run_url else "",
"",
"| Quelle | ID | Schweregrad | Paket | Installiert | Fix | Ziel |",
"|---|---|---|---|---|---|---|",
]
for f in findings:
cells = (f["source"], f["id"], f["severity"], f["package"], f["installed"], f["fixed"], f["target"])
lines.append("| " + " | ".join(escape_md_cell(c) for c in cells) + " |")
if any(f["source"] == "TruffleHog" for f in findings):
lines.append("")
lines.append(TRUFFLEHOG_GUIDANCE)
return "\n".join(lines)
def ensure_label(token, gitea_url, repo):
page = 1
while True:
labels = api("GET", f"/repos/{repo}/labels?limit=50&page={page}", token, gitea_url) or []
for label in labels:
if label.get("name") == LABEL_NAME:
return label["id"]
if len(labels) < 50:
break
page += 1
created = api("POST", f"/repos/{repo}/labels", token, gitea_url, {
"name": LABEL_NAME,
"color": LABEL_COLOR,
"description": "Automatisch verwaltet vom Security-Scan-Workflow",
})
return created["id"]
def find_open_issue(token, gitea_url, repo):
issues = api(
"GET",
f"/repos/{repo}/issues?state=open&type=issues&labels={LABEL_NAME}",
token,
gitea_url,
) or []
for issue in issues:
if issue.get("title") == ISSUE_TITLE:
return issue
return None
def main():
trivy_path = sys.argv[1] if len(sys.argv) > 1 else None
osv_path = sys.argv[2] if len(sys.argv) > 2 else None
trufflehog_path = sys.argv[3] if len(sys.argv) > 3 else None
gitea_url = os.environ.get("GITEA_URL", "").strip().rstrip("/")
repo = os.environ.get("REPO", "").strip()
token = os.environ.get("TOKEN", "").strip()
run_url = os.environ.get("RUN_URL", "")
osv_exit = int(os.environ.get("OSV_EXIT", "0"))
trufflehog_exit = int(os.environ.get("TRUFFLEHOG_EXIT", "0"))
findings = sort_findings(load_trivy(trivy_path) + load_osv(osv_path) + load_trufflehog(trufflehog_path))
print_summary(findings)
if not token or not gitea_url or not repo:
missing = [name for name, val in [("TOKEN", token), ("GITEA_URL", gitea_url), ("REPO", repo)] if not val]
print(f"{', '.join(missing)} nicht gesetzt oder leer überspringe Gitea-Issue-Synchronisation.")
else:
open_issue = find_open_issue(token, gitea_url, repo)
if findings:
report = build_report(findings, run_url)
if open_issue:
print(f"Kommentiere bestehendes Issue #{open_issue['number']} mit {len(findings)} Fund(en).")
api("POST", f"/repos/{repo}/issues/{open_issue['number']}/comments", token, gitea_url, {"body": report})
else:
label_id = ensure_label(token, gitea_url, repo)
print(f"Erstelle neues Issue mit {len(findings)} Fund(en).")
api("POST", f"/repos/{repo}/issues", token, gitea_url, {
"title": ISSUE_TITLE,
"body": report,
"labels": [label_id],
})
elif open_issue:
print(f"Keine aktuellen Funde mehr. Kommentiere Issue #{open_issue['number']}.")
api("POST", f"/repos/{repo}/issues/{open_issue['number']}/comments", token, gitea_url, {
"body": f"Aktueller Scan hat keine offenen Schwachstellen mehr gefunden.\n\n{run_url}".strip(),
})
else:
print("Keine Funde und kein offenes Issue vorhanden.")
osv_ok_exits = {0, 1}
trufflehog_ok_exits = {0, 183}
if osv_exit not in osv_ok_exits:
print(f"WARNUNG: osv-scanner beendete sich mit unerwartetem Exit-Code {osv_exit} - Scan evtl. unvollständig.", file=sys.stderr)
if trufflehog_exit not in trufflehog_ok_exits:
print(f"WARNUNG: trufflehog beendete sich mit unerwartetem Exit-Code {trufflehog_exit} - Scan evtl. unvollständig.", file=sys.stderr)
has_trivy_findings = any(f["source"].startswith("Trivy") for f in findings)
if (
has_trivy_findings
or osv_exit == 1
or trufflehog_exit == 183
or osv_exit not in osv_ok_exits
or trufflehog_exit not in trufflehog_ok_exits
):
sys.exit(1)
if __name__ == "__main__":
main()
+27 -133
View File
@@ -13,7 +13,7 @@
//!
//! Beispiel:
//! ```rust
//! use logger::{log, LogLevel, set_log_level};
//! use logger_ctdra::{log, LogLevel, set_log_level};
//!
//! set_log_level(LogLevel::Debug);
//! log("startup", "Dienst wird initialisiert...", LogLevel::Info);
@@ -23,13 +23,13 @@
use std::env;
use std::fmt::{Display, Formatter};
use std::fs::{create_dir_all, File, OpenOptions};
use std::fs::{File, OpenOptions, create_dir_all};
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::{Mutex, OnceLock, RwLock};
use time::{macros::format_description, OffsetDateTime};
use time::{OffsetDateTime, macros::format_description};
/// Schweregrade für Logeinträge in aufsteigender Detailtiefe.
///
@@ -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<Mutex<Option<File>>> = OnceLock::new();
/// Optionaler benutzerdefinierter Programmname für die Verzeichnisstruktur im Temp-Ordner.
static PROGRAM_NAME: OnceLock<RwLock<Option<String>>> = OnceLock::new();
/// Optionaler benutzerdefinierter Pfad für Logdateien.
static CUSTOM_LOG_DIR: OnceLock<RwLock<Option<PathBuf>>> = OnceLock::new();
@@ -112,7 +109,7 @@ const DEFAULT_LOG_UUID: &str = "13692bbf-a93b-43e9-9cc6-f05f94a8cfb6";
///
/// # Beispiel
/// ```rust
/// use logger::{set_log_level, LogLevel};
/// use logger_ctdra::{set_log_level, LogLevel};
/// set_log_level(LogLevel::Debug);
/// ```
pub fn set_log_level(level: LogLevel) {
@@ -142,39 +139,15 @@ pub fn get_log_level() -> LogLevel {
};
}
if let Ok(val) = env::var("LOG_LEVEL").or_else(|_| env::var("RUST_LOG")) {
if let Ok(lvl) = LogLevel::try_from(val.as_str()) {
return lvl;
}
if let Ok(val) = env::var("LOG_LEVEL").or_else(|_| env::var("RUST_LOG"))
&& let Ok(lvl) = LogLevel::try_from(val.as_str())
{
return lvl;
}
LogLevel::Info
}
/// Setzt den Programmnamen für die Logdateibenennung explizit.
pub fn set_program_name(name: impl Into<String>) {
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<PathBuf>) {
let lock = CUSTOM_LOG_DIR.get_or_init(|| RwLock::new(None));
@@ -210,7 +183,9 @@ pub fn format_message(tag: &str, message: &str, log_level: &LogLevel) -> String
let message = format!(
"[{}][{}]{}: {}",
now_local.format(fmt).unwrap_or_else(|_| "00.00.0000 00:00:00.000".to_string()),
now_local
.format(fmt)
.unwrap_or_else(|_| "00.00.0000 00:00:00.000".to_string()),
log_level,
prefix,
message
@@ -241,7 +216,7 @@ pub fn format_message(tag: &str, message: &str, log_level: &LogLevel) -> String
///
/// Beispiel:
/// ```rust
/// use logger::{log, LogLevel};
/// use logger_ctdra::{log, LogLevel};
/// log("http", "Server gestartet auf Port 8080", LogLevel::Info);
/// ```
pub fn log(tag: &str, message: &str, log_level: LogLevel) {
@@ -255,11 +230,11 @@ pub fn log(tag: &str, message: &str, log_level: LogLevel) {
}
let file_lock = get_or_init_log_file();
if let Ok(mut guard) = file_lock.lock() {
if let Some(f) = guard.as_mut() {
let _ = writeln!(f, "{}", formatted);
let _ = f.flush();
}
if let Ok(mut guard) = file_lock.lock()
&& let Some(f) = guard.as_mut()
{
let _ = writeln!(f, "{}", formatted);
let _ = f.flush();
}
}
}
@@ -292,6 +267,15 @@ fn get_or_init_log_file() -> &'static Mutex<Option<File>> {
})
}
/// 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<File> {
let custom_dir = if let Some(lock) = CUSTOM_LOG_DIR.get() {
@@ -305,7 +289,7 @@ fn open_log_file() -> std::io::Result<File> {
};
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 +308,3 @@ fn open_log_file() -> std::io::Result<File> {
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());
}
}
+189
View File
@@ -0,0 +1,189 @@
use logger_ctdra::{
LogLevel, debug, error, format_message, get_log_level, info, init, log, set_log_dir,
set_log_level, warn,
};
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!(&timestamp[2..3], ".");
assert_eq!(&timestamp[5..6], ".");
assert_eq!(&timestamp[10..11], " ");
assert_eq!(&timestamp[13..14], ":");
assert_eq!(&timestamp[16..17], ":");
assert_eq!(&timestamp[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");
}