v0.1.2-pre.004-fix.002

This commit is contained in:
2026-08-14 19:33:59 +02:00
parent 9f23eb950c
commit 37f53f3080
4 changed files with 152 additions and 14 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 32 # version: 33
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-core-lib", "crates/ksp-logging-lib"] members = ["crates/ksp-core-lib", "crates/ksp-logging-lib"]
[workspace.package] [workspace.package]
version = "0.1.2-pre.4.fix.1" version = "0.1.2-pre.4.fix.2"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/runtime.rs // file: crates/ksp-logging-lib/src/runtime.rs
// version: 4 // version: 5
use tracing_subscriber::Layer; // rust-rules: trait-import use tracing_subscriber::Layer; // rust-rules: trait-import
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
@@ -194,8 +194,8 @@ fn prepare_runtime(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<Pr
fn build_console_output(console: &crate::ConsoleSettings, settings: &crate::LoggingSettings) -> PreparedOutput { fn build_console_output(console: &crate::ConsoleSettings, settings: &crate::LoggingSettings) -> PreparedOutput {
return match console.output() { return match console.output() {
crate::ConsoleOutput::Stdout => build_non_blocking_output(std::io::stdout(), "ksp-logging-console", settings), crate::ConsoleOutput::Stdout => build_non_blocking_output(std::io::stdout(), "ksp-logging-console", settings, true),
crate::ConsoleOutput::Stderr => build_non_blocking_output(std::io::stderr(), "ksp-logging-console", settings), crate::ConsoleOutput::Stderr => build_non_blocking_output(std::io::stderr(), "ksp-logging-console", settings, true),
}; };
} }
@@ -216,23 +216,24 @@ fn build_file_output(file: &crate::FileSettings, settings: &crate::LoggingSettin
}, },
}; };
let stripped_writer = crate::writer::StripAnsiWriter::new(appender); let stripped_writer = crate::writer::StripAnsiWriter::new(appender);
return std::result::Result::Ok(build_non_blocking_output(stripped_writer, "ksp-logging-file", settings)); return std::result::Result::Ok(build_non_blocking_output(stripped_writer, "ksp-logging-file", settings, false));
} }
fn build_non_blocking_output<W>(writer: W, thread_name: &str, settings: &crate::LoggingSettings) -> PreparedOutput fn build_non_blocking_output<W>(writer: W, thread_name: &str, settings: &crate::LoggingSettings, ansi_sanitization: bool) -> PreparedOutput
where where
W: std::io::Write + std::marker::Send + 'static, W: std::io::Write + std::marker::Send + 'static,
{ {
let (non_blocking, worker_guard) = tracing_appender::non_blocking::NonBlockingBuilder::default().lossy(true).thread_name(thread_name).finish(writer); let (non_blocking, worker_guard) = tracing_appender::non_blocking::NonBlockingBuilder::default().lossy(true).thread_name(thread_name).finish(writer);
let error_counter = non_blocking.error_counter(); let error_counter = non_blocking.error_counter();
let layer = build_format_layer(non_blocking, settings); let layer = build_format_layer(non_blocking, settings, ansi_sanitization);
return PreparedOutput { layer, output: RuntimeOutput { _worker_guard: worker_guard, error_counter } }; return PreparedOutput { layer, output: RuntimeOutput { _worker_guard: worker_guard, error_counter } };
} }
fn build_format_layer(writer: tracing_appender::non_blocking::NonBlocking, settings: &crate::LoggingSettings) -> BoxedRuntimeLayer { fn build_format_layer(writer: tracing_appender::non_blocking::NonBlocking, settings: &crate::LoggingSettings, ansi_sanitization: bool) -> BoxedRuntimeLayer {
return tracing_subscriber::fmt::layer() return tracing_subscriber::fmt::layer()
.with_writer(writer) .with_writer(writer)
.with_ansi(false) .with_ansi(false)
.with_ansi_sanitization(ansi_sanitization)
.with_target(true) .with_target(true)
.with_file(true) .with_file(true)
.with_line_number(true) .with_line_number(true)

View File

@@ -0,0 +1,135 @@
<!-- file: deltas/0.1.2/pre.004-fix.002.md -->
<!-- version: 1 -->
# Delta 0.1.2-pre.004-fix.002
## Base requise
Livraison précédente :
```text
0.1.2-pre.004-fix.001
```
La base porte :
```text
workspace.package.version = "0.1.2-pre.4.fix.1"
Cargo.toml header version = 32
```
## Validation remontée
Après application de `pre.004-fix.001`, un rebuild propre a donné :
```text
cargo clean OK
cargo fmt --all OK
cargo check --workspace OK
cargo clippy --workspace --all-targets OK
cargo test --workspace ECHEC
```
Tous les tests sauf le test runtime global passent encore. L'échec reste strictement identique :
```text
assertion failed: file_text.contains("file output marker")
```
La reproduction après `cargo clean` invalide donc l'hypothèse selon laquelle cet échec précis provenait de l'ordre de destruction corrigé par `pre.004-fix.001`. Ce lifecycle explicite est néanmoins conservé.
## Cause réelle
`tracing-subscriber 0.3.23` active par défaut la sanitization ANSI des valeurs dans `fmt::Layer`. Cette protection intervient pendant le formatage, donc avant l'appel au `MakeWriter`.
Le sink fichier KSP était composé comme suit :
```text
value containing ESC
-> fmt::Layer ANSI sanitization
-> NonBlocking
-> StripAnsiWriter
-> RollingFileAppender
```
Le `StripAnsiWriter` KSP ne recevait donc plus les octets ESC originaux à supprimer. Le test attend volontairement que :
```text
file ESC[31moutput ESC[0m marker
```
devienne dans le fichier :
```text
file output marker
```
La sanitization native et le stripping KSP sont deux politiques différentes : KSP veut supprimer les contrôles du fichier, pas les transformer avant son propre writer.
## Correction
Le runtime distingue désormais la politique du formatter selon le sink :
```text
console
fmt::Layer.with_ansi(false)
fmt::Layer.with_ansi_sanitization(true)
-> NonBlocking console
file
fmt::Layer.with_ansi(false)
fmt::Layer.with_ansi_sanitization(false)
-> NonBlocking
-> StripAnsiWriter
-> RollingFileAppender
```
La console conserve donc la protection native de `tracing-subscriber`. Le fichier laisse passer jusqu'au worker les séquences présentes dans les valeurs afin que `StripAnsiWriter` les supprime avant persistence.
Le stripping reste hors du hot path : il est toujours exécuté derrière la queue non bloquante.
Le test d'intégration runtime reste inchangé. Il continue à vérifier :
- l'émission fichier après hot reload ;
- le retrait immédiat du sink et son drain ;
- la présence du target et du callsite ;
- l'absence de séquences ANSI ;
- le silence des targets externes.
## Version technique
Ce correctif modifie du Rust. La version workspace devient :
```text
workspace.package.version = "0.1.2-pre.4.fix.2"
```
et l'en-tête du `Cargo.toml` racine devient :
```text
# version: 33
```
## Fichiers du delta
```text
Cargo.toml
crates/ksp-logging-lib/src/runtime.rs
docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md
deltas/0.1.2/pre.004-fix.002.md
```
## Validations à exécuter
```bash
cargo fmt --all
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test --workspace
```
Si ces validations sont propres, la tranche suivante reste :
```text
0.1.2-pre.005 — intégration + concurrence + saturation + audits
```

View File

@@ -1,13 +1,13 @@
<!-- file: docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md --> <!-- file: docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md -->
<!-- version: 8 --> <!-- version: 9 -->
# Plan KSP 0.1.2 — Logging foundation # Plan KSP 0.1.2 — Logging foundation
## Statut ## Statut
Plan actif de `0.1.2`, établi par `0.1.2-pre.001`, corrigé par `0.1.2-pre.001-fix.001`, concrétisé par la façade de `0.1.2-pre.002`, étendu au runtime subscriber par `0.1.2-pre.003`/`pre.003-fix.001`, puis complété par les sorties non bloquantes de `0.1.2-pre.004` et leur lifecycle de retrait corrigé par `pre.004-fix.001`. Plan actif de `0.1.2`, établi par `0.1.2-pre.001`, corrigé par `0.1.2-pre.001-fix.001`, concrétisé par la façade de `0.1.2-pre.002`, étendu au runtime subscriber par `0.1.2-pre.003`/`pre.003-fix.001`, puis complété par les sorties non bloquantes de `0.1.2-pre.004`. `pre.004-fix.001` a rendu explicite l'ordre de retrait des layers et des `WorkerGuard`; `pre.004-fix.002` corrige ensuite l'interaction entre la sanitization ANSI native de `tracing-subscriber 0.3.23` et le stripping fichier KSP.
`pre.003-fix.001` a été validé dans l'environnement de développement avec `cargo fmt --all`, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test --workspace` propres sur la version Cargo `0.1.2-pre.3.fix.1`. `pre.004` ajoute `tracing-appender`, remplace la console synchrone par un writer non bloquant, introduit le fichier `Never/Hourly/Daily`, possède les `WorkerGuard`, expose des compteurs cumulés de dropped lines, déporte le stripping ANSI côté worker fichier et conserve le hot reload transactionnel des sinks. Les validations de `pre.004` ont montré un défaut de lifecycle lors du retrait immédiat du sink fichier : le fichier pouvait être lu avant que le worker n'ait observé un shutdown avec tous les anciens `NonBlocking` senders effectivement libérés. `pre.004-fix.001` retire donc explicitement les anciens layers du runtime, les détruit, puis détruit leurs `WorkerGuard`. `pre.003-fix.001` a été validé dans l'environnement de développement avec `cargo fmt --all`, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test --workspace` propres sur la version Cargo `0.1.2-pre.3.fix.1`. `pre.004` ajoute `tracing-appender`, remplace la console synchrone par un writer non bloquant, introduit le fichier `Never/Hourly/Daily`, possède les `WorkerGuard`, expose des compteurs cumulés de dropped lines, déporte le stripping ANSI côté worker fichier et conserve le hot reload transactionnel des sinks. `pre.004-fix.001` a rendu explicite le retrait des anciens layers avant leurs `WorkerGuard`, mais la validation utilisateur après `cargo clean` a reproduit exactement le même échec du marqueur fichier. Cette seconde validation a invalidé l'hypothèse selon laquelle ce défaut précis provenait du drain. La cause réelle est la sanitization ANSI native de `tracing-subscriber 0.3.23`, activée par défaut dans `fmt::Layer` : elle transforme les séquences de contrôle présentes dans les valeurs avant qu'elles n'atteignent le writer. `pre.004-fix.002` la conserve pour la console mais la désactive sur le formatter fichier afin que le `StripAnsiWriter` KSP, placé côté worker, reçoive les séquences ANSI originales et les supprime réellement. Le lifecycle explicite de `fix.001` est conservé car il reste correct pour le retrait des sinks non bloquants.
## Base auditée ## Base auditée
@@ -534,6 +534,8 @@ La sortie fichier passe par un writer KSP de stripping ANSI avant persistence af
Cette responsabilité reste générique : `ksp-logging-lib` ne dépend pas de Tauri. Elle évite simplement que des séquences ANSI injectées par une couche d'application/framework se retrouvent persistées dans les fichiers. Cette responsabilité reste générique : `ksp-logging-lib` ne dépend pas de Tauri. Elle évite simplement que des séquences ANSI injectées par une couche d'application/framework se retrouvent persistées dans les fichiers.
`tracing-subscriber 0.3.23` active également une sanitization ANSI des valeurs dans `fmt::Layer` par défaut. Pour le sink fichier KSP, cette sanitization native est explicitement désactivée afin de ne pas convertir les séquences avant le `StripAnsiWriter`; le formatter continue de ne pas émettre lui-même de couleurs avec `with_ansi(false)`, puis le writer KSP supprime les contrôles côté worker. Pour la console, la sanitization native reste activée.
Le stripping n'est pas présenté comme un mécanisme de redaction de données. Le stripping n'est pas présenté comme un mécanisme de redaction de données.
## Lifecycle et ownership du guard ## Lifecycle et ownership du guard
@@ -978,7 +980,7 @@ Réalisé :
- test d'intégration étendu pour couvrir fichier, erreur transactionnelle, flush lors du retrait du sink, takeover externe et stripping ANSI ; - test d'intégration étendu pour couvrir fichier, erreur transactionnelle, flush lors du retrait du sink, takeover externe et stripping ANSI ;
- tests unitaires ajoutés pour rotations, runtime disabled/non bloquant, statistiques et stripper ANSI. - tests unitaires ajoutés pour rotations, runtime disabled/non bloquant, statistiques et stripper ANSI.
Le premier test d'intégration `pre.004` a révélé que l'ordre de destruction n'était pas assez explicite lorsque le sink fichier était retiré immédiatement après une émission : `pre.004-fix.001` récupère désormais les anciens layers lors du swap et les détruit avant les `WorkerGuard`. Le test d'intégration existant reste le test de non-régression de cette garantie. `pre.004-fix.001` récupère les anciens layers lors du swap et les détruit avant les `WorkerGuard`, ce qui rend le lifecycle de retrait explicite. La validation après ce fix a toutefois reproduit l'absence du marqueur fichier et a montré que ce lifecycle n'était pas la cause de cet échec précis. `pre.004-fix.002` corrige la cause réelle : le formatter fichier désactive la sanitization ANSI native afin que le stripper KSP puisse supprimer les séquences originales. Le test d'intégration existant reste inchangé et vérifie simultanément l'émission fichier, le retrait immédiat du sink, le drain du worker et le stripping attendu.
La saturation déterministe des queues, les reloads concurrents et l'audit complet des dépendances restent à renforcer dans `pre.005`. La saturation déterministe des queues, les reloads concurrents et l'audit complet des dépendances restent à renforcer dans `pre.005`.
@@ -1062,7 +1064,7 @@ Les deux questions d'API propres à `pre.002` sont résolues :
1. les macros KSP délèguent aux macros `tracing` au point d'appel via un bridge interne caché et exigent un `target:` explicite ; 1. les macros KSP délèguent aux macros `tracing` au point d'appel via un bridge interne caché et exigent un `target:` explicite ;
2. la surface span publique est `Span::in_scope(...)` pour le synchrone et `instrument(span, future)` pour l'async, avec type de future retourné opaque. 2. la surface span publique est `Span::in_scope(...)` pour le synchrone et `instrument(span, future)` pour l'async, avec type de future retourné opaque.
La composition de reload est désormais fixée pour cette release à un `Vec` de layers boxed derrière une `reload::Layer`, ce qui autorise l'activation/désactivation des sinks et le remplacement de leurs paramètres sans second subscriber global. Le takeover `Targets` est un layer global distinct dans ce `Vec`; les layers de sortie reloadables ne doivent pas être des `Filtered` remplacés directement. Pour les changements de sinks, `pre.004-fix.001` utilise `Handle::modify` afin de récupérer le `Vec` retiré : les anciens layers sont détruits avant les `WorkerGuard` correspondants, ce qui fixe explicitement le lifecycle de drain des writers non bloquants. La composition de reload est désormais fixée pour cette release à un `Vec` de layers boxed derrière une `reload::Layer`, ce qui autorise l'activation/désactivation des sinks et le remplacement de leurs paramètres sans second subscriber global. Le takeover `Targets` est un layer global distinct dans ce `Vec`; les layers de sortie reloadables ne doivent pas être des `Filtered` remplacés directement. Pour les changements de sinks, `pre.004-fix.001` utilise `Handle::modify` afin de récupérer le `Vec` retiré : les anciens layers sont détruits avant les `WorkerGuard` correspondants, ce qui fixe explicitement le lifecycle de drain des writers non bloquants. `pre.004-fix.002` distingue en outre la sanitization des valeurs : activée côté console, désactivée côté formatter fichier car le `StripAnsiWriter` KSP possède la responsabilité de suppression avant persistence.
Restent à confirmer par les prereleases suivantes sans remettre en cause ce contrat : Restent à confirmer par les prereleases suivantes sans remettre en cause ce contrat :