v0.1.2-pre.004-fix.003
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 33
|
||||
# version: 34
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.2-pre.4.fix.2"
|
||||
version = "0.1.2-pre.4.fix.3"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/runtime.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
use tracing_subscriber::Layer; // rust-rules: trait-import
|
||||
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
|
||||
@@ -178,17 +178,19 @@ fn prepare_runtime(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<Pr
|
||||
let prepared_console = settings.console().map(|console| -> PreparedOutput {
|
||||
return build_console_output(console, settings);
|
||||
});
|
||||
let mut layers = RuntimeLayers::new();
|
||||
layers.push(std::boxed::Box::new(build_target_filter(settings)));
|
||||
let mut output_layers = RuntimeLayers::new();
|
||||
let mut outputs = RuntimeOutputs::default();
|
||||
if let std::option::Option::Some(console) = prepared_console {
|
||||
layers.push(console.layer);
|
||||
output_layers.push(console.layer);
|
||||
outputs.console = std::option::Option::Some(console.output);
|
||||
}
|
||||
if let std::option::Option::Some(file) = prepared_file {
|
||||
layers.push(file.layer);
|
||||
output_layers.push(file.layer);
|
||||
outputs.file = std::option::Option::Some(file.output);
|
||||
}
|
||||
let takeover_layer = build_target_filter(settings).and_then(output_layers).boxed();
|
||||
let mut layers = RuntimeLayers::new();
|
||||
layers.push(takeover_layer);
|
||||
return std::result::Result::Ok(PreparedRuntime { layers, outputs });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/unit_tests/runtime.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
#[test]
|
||||
fn level_mapping_covers_all_ksp_levels() {
|
||||
@@ -60,7 +60,7 @@ fn disabled_runtime_has_no_layers_or_outputs() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn console_runtime_keeps_takeover_filter_separate_from_formatter_and_owns_guard() {
|
||||
fn console_runtime_composes_takeover_filter_before_formatter_and_owns_guard() {
|
||||
let settings = crate::LoggingSettings::new(
|
||||
crate::LogFilterLevel::Info,
|
||||
crate::SpanEvents::Off,
|
||||
@@ -73,7 +73,7 @@ fn console_runtime_keeps_takeover_filter_separate_from_formatter_and_owns_guard(
|
||||
std::result::Result::Ok(prepared) => prepared,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(prepared.layers.len(), 2);
|
||||
assert_eq!(prepared.layers.len(), 1);
|
||||
assert!(prepared.outputs.console.is_some());
|
||||
assert!(prepared.outputs.file.is_none());
|
||||
assert_eq!(prepared.outputs.dropped_lines(), crate::DroppedLines::zero());
|
||||
|
||||
131
deltas/0.1.2/pre.004-fix.003.md
Normal file
131
deltas/0.1.2/pre.004-fix.003.md
Normal file
@@ -0,0 +1,131 @@
|
||||
<!-- file: deltas/0.1.2/pre.004-fix.003.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.1.2-pre.004-fix.003
|
||||
|
||||
## Base requise
|
||||
|
||||
Livraison précédente :
|
||||
|
||||
```text
|
||||
0.1.2-pre.004-fix.002
|
||||
```
|
||||
|
||||
La base porte :
|
||||
|
||||
```text
|
||||
workspace.package.version = "0.1.2-pre.4.fix.2"
|
||||
Cargo.toml header version = 33
|
||||
```
|
||||
|
||||
## Validation remontée
|
||||
|
||||
Après application de `pre.004-fix.002` :
|
||||
|
||||
```text
|
||||
cargo fmt --all OK
|
||||
cargo check --workspace OK
|
||||
cargo clippy --workspace --all-targets OK
|
||||
cargo test --workspace ECHEC
|
||||
```
|
||||
|
||||
Le marqueur fichier précédemment absent est désormais correctement persisté et les assertions de présence du target, du callsite et d'absence d'ANSI passent. Le test runtime global échoue plus loin sur :
|
||||
|
||||
```text
|
||||
assertion failed: !file_text.contains("external marker must remain silent")
|
||||
```
|
||||
|
||||
Le défaut restant concerne donc exclusivement le takeover : un événement `tracing` émis directement avec le target externe `sqlx` atteint encore le sink fichier alors que la politique KSP exige son silence total.
|
||||
|
||||
## Cause réelle
|
||||
|
||||
`pre.003-fix.001` avait évité le panic `Filtered`/`FilterId` en plaçant `Targets` comme layer global distinct dans le même :
|
||||
|
||||
```text
|
||||
Vec<Box<dyn Layer<Registry>>>
|
||||
```
|
||||
|
||||
que les formatters.
|
||||
|
||||
Cette composition n'est toutefois pas correcte pour le filtrage global au niveau des callsites. L'implémentation `Layer` de `Vec<L>` agrège `register_callsite` en conservant l'intérêt le plus élevé retourné par ses enfants. Un `fmt::Layer` intéressé par le callsite peut donc produire un intérêt actif alors que `Targets` retourne `Interest::never()` pour un target externe.
|
||||
|
||||
Lorsque le callsite est enregistré comme toujours actif, `enabled()` n'est ensuite pas consulté à chaque émission. Le `Targets` frère du formatter ne peut donc plus bloquer l'événement externe.
|
||||
|
||||
Le test avec `sqlx` expose précisément cette fuite.
|
||||
|
||||
## Correction
|
||||
|
||||
Les layers de sortie sont d'abord construits dans un `Vec` :
|
||||
|
||||
```text
|
||||
outputs
|
||||
├── console fmt layer, si actif
|
||||
└── file fmt layer, si actif
|
||||
```
|
||||
|
||||
Le takeover est ensuite composé **devant tout ce groupe** :
|
||||
|
||||
```text
|
||||
Targets
|
||||
.and_then(outputs)
|
||||
```
|
||||
|
||||
et ce composite unique devient l'élément du `Vec` reloadable :
|
||||
|
||||
```text
|
||||
reload::Layer
|
||||
└── Vec
|
||||
└── Targets -> output layers
|
||||
```
|
||||
|
||||
Cette forme rétablit la sémantique de filtre global : un `Interest::never()` produit par `Targets` court-circuite le groupe de sinks avant leur formatter.
|
||||
|
||||
Elle conserve simultanément les propriétés requises :
|
||||
|
||||
- aucun `Layer::with_filter` n'est utilisé sur un layer remplacé à chaud ;
|
||||
- aucun `Filtered` et donc aucun `FilterId` reloadable n'est introduit ;
|
||||
- console et fichier restent activables/désactivables dynamiquement ;
|
||||
- `Handle::modify` continue de récupérer l'ancien composite avant destruction de ses `WorkerGuard` ;
|
||||
- la sanitization console et le stripping ANSI fichier de `fix.002` restent inchangés ;
|
||||
- l'API publique reste inchangée.
|
||||
|
||||
Le test d'intégration runtime conserve son assertion directe sur un événement `tracing::error!` de target `sqlx`. Il reste donc le test de non-régression du takeover effectif, au-delà du test unitaire de `Targets::would_enable`.
|
||||
|
||||
## Version technique
|
||||
|
||||
Ce correctif modifie du Rust. La version workspace devient :
|
||||
|
||||
```text
|
||||
workspace.package.version = "0.1.2-pre.4.fix.3"
|
||||
```
|
||||
|
||||
et l'en-tête du `Cargo.toml` racine devient :
|
||||
|
||||
```text
|
||||
# version: 34
|
||||
```
|
||||
|
||||
## Fichiers du delta
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
crates/ksp-logging-lib/src/runtime.rs
|
||||
crates/ksp-logging-lib/unit_tests/runtime.rs
|
||||
docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md
|
||||
deltas/0.1.2/pre.004-fix.003.md
|
||||
```
|
||||
|
||||
## Validations à exécuter
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
Aucune dépendance n'est modifiée par ce fix. Après validation propre, la tranche suivante reste :
|
||||
|
||||
```text
|
||||
0.1.2-pre.005 — intégration + concurrence + saturation + audits
|
||||
```
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 10 -->
|
||||
|
||||
# Plan KSP 0.1.2 — Logging foundation
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
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. `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.
|
||||
`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. La validation de `pre.004-fix.002` confirme ensuite que l'écriture fichier et le stripping ANSI fonctionnent, mais révèle que le target externe `sqlx` est encore persisté. La cause est la composition du filtre global comme enfant frère des formatters dans un `Vec<Layer>` : le `Vec` agrège l'intérêt de callsite en conservant l'intérêt le plus élevé de ses enfants, de sorte qu'un formatter intéressé peut masquer le `Interest::never()` du `Targets`. `pre.004-fix.003` compose donc `Targets` devant le `Vec` des sinks avec `Layer::and_then`; le filtre redevient global pour tout le groupe de sorties sans utiliser `with_filter`/`Filtered`.
|
||||
|
||||
## Base auditée
|
||||
|
||||
@@ -960,7 +960,7 @@ Réalisé :
|
||||
|
||||
La console de `pre.003` utilise encore directement `stdout`/`stderr` comme writer synchrone. C'est un état transitoire volontaire : `pre.004` remplace ces writers par `tracing-appender::non_blocking`, introduit les `WorkerGuard`/`ErrorCounter`, puis ajoute le fichier et le stripping ANSI. La release stable `0.1.2` ne sera pas déclarée conforme tant que ce remplacement n'est pas terminé.
|
||||
|
||||
Le runtime reloadable reste un `Vec<Box<dyn Layer<Registry>>>` placé derrière une unique `reload::Layer`. En revanche, les sinks reloadables ne doivent pas encapsuler leur `Targets` via `Layer::with_filter`, car cela crée un `Filtered` dont le `FilterId` est enregistré lors de son attachement initial au subscriber et ne peut pas être remplacé directement avec `Handle::reload`. `pre.003-fix.001` place donc `Targets` comme layer de filtrage global distinct dans le même `Vec`, à côté du layer `fmt` non filtré. Le `Vec` entier reste remplaçable à chaud, mais aucun `Filtered` nouvellement construit n'a besoin d'être enregistré après l'installation globale du subscriber. Cette composition conserve le takeover externe, permet de changer à chaud le nombre et le type des sinks et prépare l'ajout du layer fichier de `pre.004`.
|
||||
Le runtime reloadable reste fondé sur un `Vec<Box<dyn Layer<Registry>>>` placé derrière une unique `reload::Layer`, mais `pre.004-fix.003` corrige la composition du takeover. Les sinks reloadables ne doivent pas encapsuler leur `Targets` via `Layer::with_filter`, car cela crée un `Filtered` dont le `FilterId` est enregistré lors de son attachement initial au subscriber et ne peut pas être remplacé directement avec `Handle::reload`. Cependant, `Targets` ne doit pas non plus être un enfant frère des `fmt` layers dans le même `Vec` : `Vec<Layer>::register_callsite` conserve l'intérêt le plus élevé de ses enfants, et un formatter intéressé peut alors masquer le `Interest::never()` du filtre global. Le runtime construit désormais le `Vec` des sinks puis le compose derrière `Targets` avec `Layer::and_then`; ce composite unique est placé dans le `Vec` reloadable. Le filtre gouverne ainsi tout le groupe de sinks, aucun `Filtered` n'est créé, et le nombre/type de sorties reste modifiable à chaud.
|
||||
|
||||
### `0.1.2-pre.004` — non-blocking console/fichier + guards + ANSI + reload sinks
|
||||
|
||||
@@ -1064,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 ;
|
||||
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. `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.
|
||||
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 composé avec `Layer::and_then` devant le `Vec` des sinks afin que son `Interest::never()` gouverne réellement tout le groupe de sorties ; les layers de sortie reloadables ne sont pas 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. `pre.004-fix.003` ferme enfin la fuite des targets externes révélée par le test d'intégration fichier.
|
||||
|
||||
Restent à confirmer par les prereleases suivantes sans remettre en cause ce contrat :
|
||||
|
||||
|
||||
Reference in New Issue
Block a user