# Plan KSP 0.1.2 — Logging foundation ## Statut Plan historique **clôturé** par `0.1.2-rel.001`. La release stable `0.1.2` publie `ksp-logging-lib` comme façade KSP unique de logging/tracing runtime. Toutes les tranches prévues ont été réalisées puis validées jusqu'à `0.1.2-pre.6`; le correctif documentaire `pre.006-fix.001` a finalisé le prompt Config sans modifier le code ni la version Cargo. Aucune question architecturale bloquante ne reste ouverte pour cette release. Les détails ci-dessous sont conservés comme historique de conception et de validation de `0.1.2`; ils ne constituent pas un plan actif pour `0.1.3`. ## Base auditée Base fournie : ```text 0.1.1 stable ``` Constats sur l'archive `khadhroony-solana-project-v0.1.1.zip` reçue : - `workspace.package.version = "0.1.1"` ; - le workspace contient uniquement `crates/ksp-core-lib` ; - `ksp-logging-lib` n'existe pas encore ; - `ksp-core-lib` expose déjà `ErrorCode`, `ErrorContext`, `Error`, `Result` et `Pubkey` ainsi que les Program IDs fondamentaux et leur registre ; - `ksp-core-lib` dépend uniquement de `solana-pubkey` au runtime ; - le delta final `deltas/0.1.1/rel.001.md` enregistre la validation finale de `0.1.1` ; - le prompt `prompts/002-V0_1_2_START_PROMPT.md` présent dans la base ouvre bien la release Logging. Dans le workflow KSP, l'archive fournie provient directement du tag Gitea correspondant et constitue la base stable/taguée `v0.1.1` attendue. L'absence normale de `.git` dans l'archive ne crée pas une vérification Git supplémentaire pour cette session. ## Mission bornée `0.1.2` introduit `ksp-logging-lib` comme façade KSP commune de logging/tracing et propriétaire exclusif de la politique runtime correspondante. La release doit fournir : 1. une surface d'émission KSP pour `error`, `warn`, `info`, `debug` et `trace` ; 2. une surface KSP de spans pour instrumenter des scopes synchrones et des `Future` async sans dépendance `tracing` directe chez les consumers ; 3. des settings runtime propres à Logging, indépendants de Config, permettant de choisir niveau, console, fichier et paramètres associés ; 4. une installation globale unique du subscriber KSP ; 5. une reconfiguration à chaud après initialisation, sans redémarrer le processus, worker ou service appelant ; 6. une sortie console non bloquante ; 7. une sortie fichier optionnelle non bloquante avec rotation bornée ; 8. un filtrage KSP global et par target, avec silence par défaut des targets externes ; 9. un lifecycle explicite conservant les guards nécessaires aux writers non bloquants et au reload ; 10. un stripping ANSI des sorties fichier afin de ne pas persister des séquences de terminal injectées par un framework ou une couche d'adaptation ; 11. un contrat d'erreurs basé sur `ksp-core-lib` ; 12. les tests de callsite, spans sync/async, settings, filtering, takeover, initialisation, hot reload, console, fichiers et façade publique nécessaires à la stabilisation. La release ne lit aucun document JSON/TOML et n'introduit aucune dépendance vers Config ou une couche supérieure. ## Frontière de propriété La frontière retenue reste : ```text ksp-logging-lib -> ksp-core-lib -> tracing -> tracing-subscriber -> tracing-appender ``` Règles : - `ksp-logging-lib` est le seul propriétaire KSP direct de `tracing`, `tracing-subscriber`, `tracing-appender`, de l'installation du subscriber et de la politique de logging ; - les crates KSP comportementales émettent leurs événements et spans exclusivement via la façade de `ksp-logging-lib` ; - une crate KSP runtime ne dépend pas directement de `tracing`, `tracing-subscriber` ou `tracing-appender` pour produire ses propres logs ; - `ksp-core-lib` ne dépend pas de Logging ; - `ksp-logging-lib` ne dépend pas de `ksp-config-lib` ; - `ksp-logging-lib` ne dépend pas de Wallet, Transport, Program, Store, workers, jobs, pipelines ou applications ; - les crates `*-api` purement déclaratives restent sans Logging par défaut ; - les événements produits directement par les dépendances externes sont silencieux par défaut au niveau du subscriber KSP ; - lorsqu'une information provenant d'une dépendance externe est utile, la crate KSP propriétaire de l'opération la réémet explicitement sous son propre target KSP au niveau approprié ; - KSP ne renomme pas ou ne réécrit pas les targets d'événements tiers après émission ; - lorsqu'une dépendance externe offre son propre mécanisme pour désactiver ses logs verbeux, la crate KSP propriétaire doit l'utiliser en plus du filtering central lorsqu'il est pertinent ; - une future intégration Tauri imposée par un plugin reste un adaptateur d'application et ne redéfinit pas la politique KSP ; les logs KSP écrits par l'application restent émis via `ksp-logging-lib`. Exemple de politique future pour Store : les logs SQLx natifs sont désactivés/silencieux ; `ksp-store-lib` réémet uniquement les opérations utiles avec `target = "ksp-store-lib"`, typiquement au niveau `trace` pour les détails de requêtes. ## Audit de la stack `tracing` au 2026-08-14 L'audit a été réalisé à partir des publications/docs officielles Tokio `tracing` et des manifests publiés sur docs.rs/crates.io. ### Versions observées ```text tracing 0.1.44 MSRV annoncé : rustc 1.65+ tracing-subscriber 0.3.23 MSRV annoncé : rustc 1.65+ tracing-appender 0.2.5 MSRV annoncé : rustc 1.63+ ``` Ces exigences restent inférieures au MSRV déjà imposé indirectement par la génération Solana retenue dans `0.1.1` (`solana-pubkey 4.3.0` / workspace Solana SDK auditée à Rust 1.89.0). Logging ne relève donc pas le plancher observé du workspace. Les versions sont revérifiées au moment exact de leur ajout effectif au manifest. `pre.002` a ajouté `tracing` dans la génération `^0.1`, `pre.003` a ajouté `tracing-subscriber` dans la génération `^0.3`, et `pre.004` revérifie puis ajoute `tracing-appender 0.2.5` dans la génération `^0.2`. ### `tracing` `tracing 0.1.44` active par défaut `attributes` et `std` ; la feature `attributes` tire `tracing-attributes`. La première surface KSP n'utilise ni `#[instrument]` ni autre macro attribut procédurale. `pre.002` retient donc : ```toml tracing = { version = "^0.1", default-features = false, features = ["std"] } ``` Aucune feature `attributes`, `log`, `valuable` ou filtre compile-time n'est activée par anticipation. ### `tracing-subscriber` `tracing-subscriber 0.3.23` active par défaut notamment `ansi`, `fmt`, `smallvec`, `std` et `tracing-log`. La première surface KSP a besoin de `fmt`, qui entraîne déjà `registry` et `std`. Elle n'a pas de besoin concret pour : - `ansi` ; - `tracing-log` ; - `env-filter` ; - `json` ; - `chrono` ; - `time` ; - `local-time` ; - `serde` ; - `parking_lot`. La dépendance candidate est donc : ```toml tracing-subscriber = { version = "^0.3", default-features = false, features = ["fmt"] } ``` Le filtrage initial utilisera `tracing_subscriber::filter::Targets`, disponible sans `env-filter` et conçu pour les niveaux par préfixe de target. ### `tracing-appender` `tracing-appender 0.2.5` n'active aucune feature par défaut et ne propose qu'une feature optionnelle `parking_lot`. Son manifest publié dépend déjà de `tracing-subscriber` avec `default-features = false` et `features = ["fmt", "std"]`. Il tire également les dépendances nécessaires à son appender, notamment `crossbeam-channel`, `time`, `symlink` et `thiserror`. Aucun besoin de `parking_lot` n'est identifié. La dépendance candidate est donc : ```toml tracing-appender = { version = "^0.2", default-features = false } ``` ### Politique d'ajout au manifest `pre.002` ajoute `tracing` sous `[workspace.dependencies]`, car la façade événements/spans et l'instrumentation async l'utilisent réellement. `crates/ksp-logging-lib/Cargo.toml` l'hérite avec `tracing.workspace = true`. `pre.003` revérifie `tracing-subscriber 0.3.23` et ajoute : ```toml tracing-subscriber = { version = "^0.3", default-features = false, features = ["fmt"] } ``` La feature `fmt` apporte `registry` et `std`, nécessaires à la composition des layers, `Targets`, `reload` et aux formatters. `env-filter`, `ansi`, `tracing-log`, `json`, `time` et les autres features optionnelles ne sont pas activées. `pre.004` revérifie `tracing-appender 0.2.5` et ajoute réellement : ```toml tracing-appender = { version = "^0.2", default-features = false } ``` Aucune feature optionnelle n'est activée. Le `NonBlockingBuilder` reste explicitement en mode lossy afin que la saturation de sa queue n'applique pas de backpressure au hot path. Après chaque ajout réel : ```bash cargo tree -p ksp-logging-lib cargo tree -p ksp-logging-lib -d cargo tree -p ksp-logging-lib -e features ``` sera utilisé pour vérifier la résolution et l'unification réelles. ## Instrumentation : macros pour préserver le callsite ### Événements Les cinq points d'émission publics seront des macros KSP : ```text ksp_logging_lib::error! ksp_logging_lib::warn! ksp_logging_lib::info! ksp_logging_lib::debug! ksp_logging_lib::trace! ``` Une fonction wrapper qui appellerait elle-même `tracing::info!` ou équivalent déplacerait les métadonnées source vers le wrapper. Les événements ne seront donc pas émis par des fonctions publiques de ce type. Les macros KSP restent minces et délèguent aux primitives `tracing` au point d'appel. Leur implémentation exacte doit préserver le callsite sans obliger le consumer à dépendre directement de `tracing`. ### Target obligatoire Chaque émission KSP comportementale fournit explicitement un `target:` correspondant au nom Cargo de la crate propriétaire, par exemple : ```text ksp-store-lib ksp-wallet-lib ksp-onchain-transport-lib ``` Une constante crate-level peut être utilisée si elle respecte les contraintes compile-time des macros retenues ; le mécanisme exact sera fixé par tests. Exemple conceptuel : ```rust ksp_logging_lib::trace!( target: crate::TRACING_TARGET, domain = "store", component = "postgres", operation = "load_transactions", "executing store query" ); ``` Le target implicite généré depuis le module Rust n'est pas une surface KSP valide pour les crates comportementales. ### Spans `0.1.2` doit également exposer une surface KSP de spans avec les niveaux usuels, conceptuellement : ```text ksp_logging_lib::error_span! ksp_logging_lib::warn_span! ksp_logging_lib::info_span! ksp_logging_lib::debug_span! ksp_logging_lib::trace_span! ``` Les spans suivent la même règle de target explicite que les événements et doivent préserver leur callsite réel. La façade ne doit pas obliger les consumers à importer `tracing::Span` ou `tracing::Instrument`. Une abstraction KSP de span et/ou des helpers KSP d'instrumentation seront retenus par l'implémentation la plus petite qui conserve cette isolation. Pour le code synchrone, la surface doit permettre d'associer un scope d'exécution au span sans déplacer le callsite. Pour le code async, la surface doit instrumenter la `Future` elle-même. Un guard issu de `Span::enter()` ne doit pas être maintenu à travers un `.await`, car cela produit des traces incorrectes lorsque l'exécution change de tâche/thread ou que la future yield. La primitive `tracing::Instrument` entre dans le span à chaque `poll` **et lors du `Drop`** de la future instrumentée ; les tests KSP doivent donc distinguer explicitement ces deux opérations au lieu de supposer une seule paire `enter`/`exit` sur toute la durée de vie de la future. ### Mesure de durée Le formatter doit pouvoir synthétiser au besoin les événements de lifecycle `NEW` et `CLOSE` des spans. Avec les timestamps actifs, `CLOSE` fournit le temps `busy` et `idle` calculé par `tracing-subscriber`; `NEW | CLOSE` donne rapidement un repère de début, de fin et de durée d'activité du span. Cette capacité est destinée au diagnostic rapide de latence et de blocage, y compris pour des futures async. Elle n'est pas présentée comme un benchmark micro/nanoseconde de précision absolue. Le contrôle exact de l'émission des événements de lifecycle appartient aux settings Logging afin de ne pas créer de bruit lorsqu'aucun diagnostic de timing n'est demandé. ### Callsites à tester Les tests d'intégration extérieurs au `src` doivent capturer les `Metadata` d'événements et de spans émis par la façade KSP et vérifier au minimum : - le fichier source est celui du consommateur/test et non un fichier de `ksp-logging-lib` ; - le module path correspond au consommateur ; - le numéro de ligne correspond à l'invocation ; - le `target:` explicite est conservé tel quel ; - les macros refusent ou ne documentent pas une voie KSP sans target explicite ; - la surface async instrumente correctement une future sans conserver un enter guard à travers un `.await`. La façade n'est pas considérée stabilisée avant réussite de ces tests. ## Niveaux KSP Les cinq niveaux d'événements publics sont exactement : ```text error warn info debug trace ``` Ils correspondent aux niveaux homonymes de `tracing`. Le filtrage a en plus besoin d'un état désactivé. Le plan prévoit un type KSP distinct, conceptuellement : ```text LogFilterLevel Off Error Warn Info Debug Trace ``` `Off` n'est pas un sixième niveau d'événement ; il appartient uniquement au contrat de filtering. Aucun type `tracing::Level` ou `tracing_subscriber::filter::LevelFilter` n'est exposé dans l'API publique KSP. ## `target`, `domain`, `component` et champs structurés ### `target` `target` est la métadonnée native de routage/filtering de `tracing`, mais KSP impose une convention plus stricte : - toute crate KSP comportementale émet avec un `target:` explicite ; - ce target correspond au nom Cargo de la crate propriétaire de l'événement ; - il n'est pas utilisé pour représenter chaque sous-module ou opération ; - les sous-domaines et composants sont décrits par des champs structurés ; - les targets externes ne sont pas des targets KSP et restent silencieux par défaut. Exemple : ```text target = "ksp-store-lib" domain = "store" component = "postgres" operation = "load_transactions" ``` ### `domain` `domain` est un **champ structuré KSP optionnel** décrivant le domaine fonctionnel lorsque cette information ajoute de la valeur. Il n'est pas automatiquement injecté et n'est pas une clé de filtrage dans la première surface. ### `component` `component` est un **champ structuré KSP optionnel** décrivant le composant logique à l'intérieur d'un domaine lorsque nécessaire. Il n'est ni obligatoire ni déduit du nom de crate. ### Autres champs Les callers peuvent émettre d'autres champs structurés utiles au contexte : slot, signature publique, Program ID public, opération, état, compteur, durée, etc. Aucune liste fermée de champs n'est introduite. Le contenu demandé à logger appartient à la responsabilité de la crate appelante ; `ksp-logging-lib` ne tente pas d'interpréter ou de classifier arbitrairement les valeurs reçues. ## Filtering retenu ### Takeover KSP La première règle du subscriber est de supprimer le bruit externe : ```text global/external default = Off KSP owned targets = configured KSP default level specific KSP target = optional override ``` La famille KSP est identifiée par la convention de targets correspondant aux noms de crates `ksp-*`. Exemple conceptuel : ```text external default = Off ksp-* = Info ksp-store-lib = Trace ksp-wallet-lib = Debug ``` Ainsi, un événement direct `sqlx`, `hyper`, `rustls` ou autre ne devient pas visible simplement parce que son niveau est `info` ou `debug`. Lorsqu'un détail SQL est utile, Store le réémet explicitement sous `ksp-store-lib`; Logging ne transforme jamais `sqlx` en `ksp-store-lib`. ### Première surface Le filtering initial comprend : 1. un niveau KSP default ; 2. zéro ou plusieurs overrides par target/préfixe KSP ; 3. un niveau global externe fixé à `Off` par la politique de takeover. La sémantique de niveau/target pourra s'appuyer sur `tracing_subscriber::filter::Targets` tant que les tests confirment qu'elle satisfait le hot reload retenu. ### Pourquoi pas `EnvFilter` `EnvFilter` apporte une syntaxe et un filtrage plus dynamiques, notamment par span/champs, mais active des dépendances/features supplémentaires (`matchers`, `regex-automata`, `once_cell`, etc.). Aucun besoin de cette puissance n'est démontré pour la fondation `0.1.2`. Conséquences : - pas de parsing de `RUST_LOG` par Logging ; - pas de directive textuelle `EnvFilter` dans les settings ; - pas de filtre sur `domain`/`component` en tant que champs ; - le hot reload modifie la représentation typée KSP et les couches/filters internes, pas une chaîne `RUST_LOG`. Config pourra ultérieurement convertir sa propre représentation validée vers les settings typés de Logging. ## Settings runtime ### Principes Les settings appartiennent à `ksp-logging-lib` et représentent uniquement ce dont son runtime a besoin. Ils ne : - lisent aucun fichier ; - ne consultent aucune variable d'environnement ; - ne connaissent aucun profil Config ; - n'exposent aucun type `tracing*` public. `ksp-config-lib` pourra plus tard lire/résoudre ses documents puis construire explicitement un `LoggingSettings` et appeler `initialize` ou `reinitialize`. Les structures publiques utilisent des champs privés avec constructeurs/getters/builder methods explicites, conformément à la direction déjà utilisée dans Core. ### Surface conceptuelle La surface doit rester proche de : ```text LoggingSettings default_filter: LogFilterLevel target_filters: Vec span_events: SpanEvents console: Option file: Option TargetFilter target_prefix: String level: LogFilterLevel SpanEvents Off NewAndClose Full ConsoleSettings output: ConsoleOutput ConsoleOutput Stdout Stderr FileSettings directory: PathBuf file_name_prefix: String rotation: FileRotation FileRotation Never Hourly Daily ``` `SpanEvents::NewAndClose` correspond au diagnostic de timing demandé ; `Full` reste disponible si l'observation des transitions enter/exit est utile pour un diagnostic plus détaillé. Les noms exacts peuvent être ajustés pendant l'implémentation sans modifier les responsabilités. ### Defaults Aucun `Default` implicite de `LoggingSettings` n'est requis dans la première surface. Le caller choisit explicitement son niveau KSP global et ses sorties. Une configuration sans console ni fichier est valide et représente un logging KSP désactivé. Le niveau global des targets externes reste `Off` par politique et n'est pas rendu configurable dans cette première surface. `ConsoleSettings` pourra offrir des constructeurs explicites `stdout()` / `stderr()` si cela simplifie l'API sans ambiguïté. ### Validation `initialize()` et `reinitialize()` doivent refuser au minimum : - un préfixe de target vide ; - un override de target qui ne correspond pas à la convention KSP retenue ; - un préfixe de fichier vide si le backend retenu ne peut pas le traiter sans ambiguïté. Les autres invariants seront ajoutés uniquement s'ils correspondent à une erreur réelle du backend ou à une ambiguïté de contrat. ## Console La sortie console est construite avec un writer `tracing-appender` non bloquant, comme la sortie fichier. ```text Stdout | Stderr -> NonBlocking -> WorkerGuard ``` Objectifs : - l'émission ordinaire d'un log KSP ne doit pas attendre l'I/O terminal ; - le `WorkerGuard` console est possédé par le lifecycle KSP jusqu'au shutdown/reload approprié ; - stdout reste disponible pour les applications qui le souhaitent ; - stderr permet aux futurs exécutables de ne pas mélanger diagnostics et sortie de données sur stdout. Le mode de queue doit privilégier le non-blocage du caller : lorsque la queue est saturée, les lignes peuvent être abandonnées plutôt que d'appliquer une backpressure au hot path. Le compteur `ErrorCounter` correspondant est conservé par le runtime. `pre.004` stabilise l'observation publique via `LoggingGuard::dropped_lines() -> DroppedLines`, avec compteurs `console`, `file` et `total` cumulés pendant toute la durée de vie du guard, y compris à travers les hot reloads. Le format initial est un format humain unique. Il doit inclure au minimum : - timestamp fourni par le formatter standard retenu ; - niveau ; - target ; - message/champs structurés ; - source file/line lorsque la configuration `fmt` retenue le permet sans dépendance supplémentaire. Le texte exact d'une ligne n'est pas un protocole de sérialisation stable. Les tests contrôlent les informations nécessaires, pas tous les espaces/ponctuations du formatter externe. ## Fichiers, rotation et writer non bloquant ### Capacité retenue `0.1.2` retient une sortie fichier **optionnelle** afin que les futurs exécutables/workers disposent d'un backend durable sans réinventer cette responsabilité hors de Logging. La première surface reste volontairement étroite : ```text rotation = Never | Hourly | Daily ``` Sont différés : - rotation minutely/weekly ; - rotation par taille ; - compression ; - politique complexe de rétention ; - symlink `latest` ; - multiples routes fichier indépendantes ; - formats fichier distincts par route. ### Construction sans panic L'implémentation doit utiliser la forme builder de `RollingFileAppender` qui retourne un `Result` en cas d'échec d'initialisation, et non une API qui panique. L'erreur externe utile est enveloppée dans `ksp_core_lib::Error` et conservée comme `source` lorsque ses bornes le permettent. ### Non-blocking La sortie fichier utilise `tracing_appender::non_blocking::NonBlockingBuilder` afin de déplacer les écritures ordinaires hors du thread appelant. La politique retenue est explicitement **non bloquante pour le caller**. Lorsque la queue est pleine, les lignes peuvent être abandonnées au lieu d'appliquer une backpressure susceptible de bloquer un worker critique. Le runtime conserve l'`ErrorCounter` fourni par le writer afin que le nombre de lignes abandonnées reste observable. La taille de file reste celle du backend tant qu'un besoin réel ne justifie pas de l'exposer dans `LoggingSettings`. ### Stripping ANSI fichier La sortie fichier passe par un writer KSP de stripping ANSI avant persistence afin de supprimer les séquences de terminal déjà présentes dans les données écrites. `pre.004` place ce writer **derrière** la queue `NonBlocking`, autour du `RollingFileAppender`. Le parsing/stripping et l'I/O disque s'exécutent donc sur le worker logging plutôt que dans le hot path du caller. Le stripper conserve son état entre plusieurs appels `Write` afin de gérer une séquence ANSI coupée entre deux buffers ; les séquences CSI ainsi que les séquences terminal de type OSC/string terminées par BEL/ST sont couvertes. 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. ## Lifecycle et ownership du guard `tracing-appender` retourne des `WorkerGuard` dont la durée de vie contrôle le flush des files non bloquantes. KSP possède ces guards et les handles de reload dans un type public de lifecycle : ```text LoggingGuard ``` Contrat : - `initialize(&LoggingSettings) -> ksp_core_lib::Result` est appelé au plus une fois par processus ; - console et fichier peuvent chacun posséder leur `WorkerGuard` ; - `LoggingGuard` conserve les guards actifs, les compteurs de dropped lines et l'état nécessaire au hot reload ; - l'appelant conserve `LoggingGuard` pendant toute la durée de vie du logging ; - un reload construit d'abord les nouveaux sinks/filters, puis bascule vers eux ; - le swap récupère explicitement les anciens layers avec `reload::Handle::modify` + `mem::replace` ; - les anciens layers sont détruits avant leurs `WorkerGuard`, afin que les clones `NonBlocking` qu'ils possèdent soient libérés avant l'envoi du shutdown/drain au worker ; - aucun `WorkerGuard` n'est détruit à la fin d'une fonction d'initialisation ou de reconfiguration avant que son sink ne soit effectivement retiré et que ses anciens layers aient été détruits ; - aucune fuite volontaire (`mem::forget`) n'est utilisée pour prolonger artificiellement la durée de vie. Le subscriber global reste installé jusqu'à la fin du processus. Le lifecycle KSP devient : ```text uninitialized -> initialized(settings A) -> reinitialized(settings B) -> reinitialized(settings C) -> process shutdown ``` La destruction finale de `LoggingGuard` termine/flushe les backends non bloquants mais ne rend pas possible une seconde installation globale. ## Initialisation globale et hot reload ### `initialize` La fonction publique principale reste conceptuellement : ```rust pub fn initialize(settings: &LoggingSettings) -> ksp_core_lib::Result ``` Elle reste synchrone : installer un subscriber et construire des writers locaux ne justifie pas artificiellement une API `async`. Le subscriber global est installé une seule fois avec une API fallible. Un deuxième appel à `initialize()` échoue avec une erreur KSP déterministe, que le premier guard soit encore détenu ou non. ### `reinitialize` Après un `initialize()` réussi, la configuration peut être remplacée à chaud 0..N fois. La forme publique préférée est liée au guard, conceptuellement : ```rust pub fn reinitialize( guard: &mut LoggingGuard, settings: &LoggingSettings, ) -> ksp_core_lib::Result<()> ``` ou une méthode équivalente sur `LoggingGuard` si l'ergonomie finale est meilleure. Cette forme impose naturellement la précondition : un reload n'est possible que si l'initialisation a déjà réussi. `reinitialize()` ne tente **jamais** d'appeler une seconde fois `set_global_default`/`try_init`. Le subscriber global installé par `initialize()` contient une infrastructure reloadable ; `reinitialize()` modifie ses filters/layers/sinks actifs. Le mécanisme exact pourra utiliser `tracing_subscriber::reload` pour les éléments appropriés ou une couche KSP dynamique plus spécialisée si les mesures montrent un overhead inférieur. Le contrat public de hot reload ne dépend pas de ce choix interne. ### Sémantique transactionnelle Le reload vise la propriété suivante : ```text success -> nouvelle configuration active error -> ancienne configuration reste active ``` Ordre conceptuel : 1. valider les nouveaux `LoggingSettings` ; 2. construire les nouveaux writers/non-blocking/guards nécessaires ; 3. préparer les nouveaux filters/layers ; 4. basculer la configuration active ; 5. flush puis détruire les anciens guards devenus inutiles. Une erreur de création du nouveau fichier, writer ou filter ne doit pas couper le logging déjà opérationnel. `reinitialize()` peut prendre un verrou et attendre un flush ponctuel : cette opération de contrôle n'appartient pas au hot path. En revanche, les appels `error!`/`warn!`/`info!`/`debug!`/`trace!` et l'instrumentation ordinaire restent non bloquants vis-à-vis des I/O. ### Cas d'usage attendu Une future `ksp-config-lib` pourra recharger son document puis effectuer : ```text new config -> new LoggingSettings -> ksp-logging-lib::reinitialize(...) ``` sans redémarrer un worker/service uniquement pour passer `ksp-store-lib` de `Info` à `Debug`/`Trace`, activer/désactiver le fichier ou modifier ses paramètres. ### Tests globaux Les tests globaux doivent éviter de se gêner entre eux, par exemple en isolant l'installation globale dans des processus de test dédiés et en testant les composants internes avec des subscribers locaux lorsque possible. ## Erreurs Logging Logging utilise : ```text ksp_core_lib::Error ksp_core_lib::ErrorCode ksp_core_lib::Result ``` Le domaine stable appartient à Logging : ```text logging ``` Premiers codes conceptuels : ```text logging.invalid_settings logging.already_initialized logging.not_initialized logging.reload_failed logging.file_output_initialization_failed ``` Un code supplémentaire n'est ajouté que si une erreur réellement distincte apparaît pendant l'implémentation. Règles : - les constantes `ErrorCode` sont définies dans `ksp-logging-lib` ; - Core n'ajoute aucune variante/connaissance Logging ; - les causes externes utiles sont conservées avec `Error::with_source(...)` lorsqu'elles satisfont `Error + Send + Sync + 'static` ; - aucun `unwrap`, `expect`, `panic` production ni opérateur `?` n'est introduit. ## Responsabilité du contenu loggé `ksp-logging-lib` transporte et formate les événements que les callers lui demandent d'émettre. Elle n'est pas responsable de déterminer si une valeur arbitraire passée dans un champ `Debug`/`Display` constitue un secret ou une donnée sensible. Conséquences : - aucune détection automatique de secrets ; - aucune redaction automatique ; - aucun scanner de noms de champs ; - aucun helper universel présenté comme barrière de sécurité ; - la crate appelante reste responsable de ne pas transmettre de données qu'elle ne souhaite pas journaliser. `LoggingSettings` ne contient que des paramètres de logging (niveaux, targets, sorties, chemins/rotation, spans) ; cela ne constitue pas une politique de secret mais simplement son contrat fonctionnel. ## Format, spans et compatibilité `log` ### Format `0.1.2` stabilise un format humain exploitable, pas une sérialisation machine publique. Sont hors de la première surface : - JSON ; - pretty/compact/human comme enum publique de formats ; - choix arbitraire de formatter par le caller ; - sérialisation Serde des événements. ### Spans Les spans font désormais partie de la fondation `0.1.2`. La surface doit couvrir : - création de spans par niveau avec target KSP explicite ; - champs structurés `domain`, `component` et autres fields ; - exécution synchrone dans un span ; - instrumentation correcte des `Future` async ; - événements de lifecycle optionnels, au minimum `NEW | CLOSE`, permettant d'obtenir rapidement timestamp de début, timestamp de fermeture et temps `busy`/`idle` ; - callsite réel préservé. `#[instrument]` n'est pas requis par `0.1.2` : la surface macros/helpers KSP suffit et évite d'activer `tracing-attributes` sans besoin. ### Compatibilité `log` La feature `tracing-log` de `tracing-subscriber` n'est pas activée. Cette décision participe au takeover : KSP ne cherche pas à aspirer automatiquement le bruit de dépendances instrumentées avec la crate `log`. Une information utile est réémise explicitement par la crate KSP propriétaire sous son propre target. ## API publique La façade crate-root visée à la fin de la release est : ```text ksp_logging_lib::error! ksp_logging_lib::warn! ksp_logging_lib::info! ksp_logging_lib::debug! ksp_logging_lib::trace! ksp_logging_lib::error_span! ksp_logging_lib::warn_span! ksp_logging_lib::info_span! ksp_logging_lib::debug_span! ksp_logging_lib::trace_span! ksp_logging_lib::LogFilterLevel ksp_logging_lib::TargetFilter ksp_logging_lib::SpanEvents ksp_logging_lib::Span ksp_logging_lib::instrument ksp_logging_lib::ConsoleOutput ksp_logging_lib::ConsoleSettings ksp_logging_lib::FileRotation ksp_logging_lib::FileSettings ksp_logging_lib::LoggingSettings ksp_logging_lib::DroppedLines ksp_logging_lib::LoggingGuard ksp_logging_lib::initialize ksp_logging_lib::reinitialize ksp_logging_lib:: ``` `pre.002` fixe l'abstraction de span publique à `ksp_logging_lib::Span`. `Span::in_scope(...)` couvre les scopes synchrones et `ksp_logging_lib::instrument(span, future)` retourne une `Future` opaque instrumentée sans demander au consumer d'importer `tracing::Span` ou `tracing::Instrument`. Les macros utilisent un bridge `tracing` caché de la documentation uniquement pour permettre leur expansion depuis une crate consommatrice tout en conservant le callsite réel. Ce bridge est un détail d'implémentation réservé aux macros et ne constitue pas une API KSP consommable. Les modules d'implémentation restent privés. Arborescence candidate : ```text crates/ksp-logging-lib/ ├── Cargo.toml ├── README.md ├── TODO.md ├── USAGE.md ├── src/ │ ├── lib.rs │ ├── error.rs │ ├── macros.rs │ ├── settings.rs │ ├── span.rs │ ├── runtime.rs │ └── writer.rs ├── unit_tests/ │ ├── settings.rs │ ├── span.rs │ ├── runtime.rs │ └── writer.rs └── tests/ ├── callsite.rs ├── runtime.rs └── public_api.rs ``` Cette arborescence reste ajustable si une séparation plus petite suffit. Aucun `mod.rs`, `pub mod`, `pub(super)` ou `pub(in ...)` n'est introduit. ## Tests prévus ### Façade/macros événements - les cinq macros sont accessibles depuis crate-root ; - message simple ; - champs structurés ; - target explicite obligatoire selon la surface retenue ; - target égal au nom de crate dans les fixtures KSP ; - callsite file/module/line réel ; - absence de dépendance directe `tracing` dans une crate consommatrice de test. ### Spans sync/async - les cinq macros de span ou la surface finale équivalente sont accessibles depuis crate-root ; - callsite span file/module/line réel ; - target explicite conservé ; - scope synchrone correctement associé au span ; - future async instrumentée par la façade KSP ; - entrée/sortie du span vérifiée pendant chaque `poll` pertinent et lors du `Drop` de la future instrumentée ; - aucun enter guard conservé à travers `.await` dans l'API recommandée ; - `SpanEvents::NewAndClose` produit les événements lifecycle attendus ; - `CLOSE` contient les temps `busy`/`idle` lorsque les timestamps sont actifs ; - `SpanEvents::Off` ne produit pas de bruit lifecycle synthétique. ### Settings - constructeurs/getters ; - niveaux `Off/Error/Warn/Info/Debug/Trace` ; - target filters ; - span events ; - console stdout/stderr ; - fichier Never/Hourly/Daily ; - validation sans sortie ; - validation des chaînes vides retenues ; - rejet des overrides de targets externes si cette validation est conservée. ### Filtering et takeover - targets externes silencieux par défaut ; - target `ksp-*` au niveau KSP default ; - override d'un target KSP exact ; - override par préfixe KSP ; - target KSP explicitement `Off` ; - priorité d'un préfixe plus spécifique selon la sémantique retenue ; - une émission tierce n'est jamais renommée en target KSP ; - une réémission KSP explicite apparaît uniquement sous le target de la crate propriétaire. ### Runtime - initialisation valide ; - deuxième `initialize` refusé ; - subscriber global déjà occupé ; - console non bloquante reçoit les événements attendus ; - file output non bloquant écrit les événements attendus ; - rotation choisie est transmise au backend ; - stripping ANSI du fichier ; - erreurs d'initialisation fichier remontent sous `ksp_core_lib::Error` avec code Logging ; - guards console/fichier conservés jusqu'au flush final ; - dropped-line counters récupérables/observables selon l'API finale. ### Hot reload - `reinitialize` nécessite un `LoggingGuard` valide ; - changement de niveau KSP à chaud ; - changement d'override pour un target précis ; - activation/désactivation console ; - activation/désactivation fichier ; - changement de chemin/rotation fichier lorsque supporté ; - changement de `SpanEvents` à chaud ; - aucun second `set_global_default` ; - une reconfiguration invalide conserve l'ancienne configuration active ; - les anciens guards sont flushés après bascule et pas avant ; - émissions concurrentes pendant le reload sans panic/deadlock ; - coût du mécanisme reload mesuré suffisamment pour détecter une régression grossière du hot path. ## Audits de dépendances À chaque tranche ajoutant une dépendance : ```bash cargo tree -p ksp-logging-lib cargo tree -p ksp-logging-lib -d cargo tree -p ksp-logging-lib -e features ``` Contrôles particuliers : - pas de `tracing-attributes` : les spans KSP de `0.1.2` n'exigent pas `#[instrument]` ; - pas de `nu-ansi-term` par activation KSP de `ansi` ; - pas de `tracing-log` par default feature afin de ne pas aspirer automatiquement les logs tiers ; - pas de stack `env-filter`/regex ; - pas de stack JSON/Serde ; - pas de dépendance Config ; - aucune seconde version évitable de la stack tracing ; - `tracing-appender` peut tirer `time` pour son fonctionnement propre sans justifier d'activer la feature `time` de `tracing-subscriber`. ## Référence historique bot3 L'archive bot3 fournie contient un ancien `ks-logging` plus large. Éléments utiles comme expérience historique : - possession des `WorkerGuard` par un objet `LoggingGuard` ; - composition console + fichier ; - filtering par routes/targets ; - rotation file ; - importance d'un lifecycle explicite pour le non-blocking. Éléments explicitement non migrés tels quels : - dépendance directe Logging -> Config ; - parsing/validation de document Logging dans la crate ; - JSON schema ; - Serde/Serde JSON uniquement pour la configuration ; - formats/routes multiples non requis ; - surface de configuration historique plus large que le besoin KSP `0.1.2`. Cette archive reste une référence historique et non une source normative KSP. ## Prereleases prévues ### `0.1.2-pre.001` — audit + brainstorming + plan Objectifs : - auditer la base `0.1.1` ; - auditer la stack tracing actuelle ; - fixer takeover, targets KSP, settings, filtering, console/fichier non bloquants, stripping ANSI, lifecycle, hot reload et spans sync/async ; - dimensionner la suite ; - ne pas créer encore la crate fonctionnelle ni ajouter de dépendance inutilisée. ### `0.1.2-pre.002` — crate + settings + façade événements/spans Statut : implémenté dans la tranche `pre.002`, sous réserve des validations Cargo à exécuter dans l'environnement de développement. Objectifs : - créer `crates/ksp-logging-lib` et l'ajouter au workspace ; - dépendre de `ksp-core-lib` ; - revérifier puis ajouter `tracing` au workspace ; - implémenter les types de settings indépendants de Config ; - implémenter les cinq macros événements et la surface de spans KSP ; - établir les tests de callsite événements/spans ; - établir l'instrumentation async sans exposer `tracing::Instrument` aux consumers. ### `0.1.2-pre.003` — subscriber + takeover + console + reload foundation Statut : implémenté dans `pre.003`, puis corrigé par `pre.003-fix.001` après détection d'un panic de reload dans le test d'intégration global. Réalisé : - `tracing-subscriber` ajouté avec uniquement la feature `fmt` ; - mapping complet `LogFilterLevel -> LevelFilter` ; - `Targets` configuré avec default externe `OFF`, préfixe KSP `ksp-` au niveau global demandé et overrides par target prefix ; - console stdout/stderr initiale avec ANSI explicitement désactivé ; - subscriber global installé par `initialize()` avec API fallible et erreur `logging.already_initialized` ; - `LoggingGuard` public possédant le handle de reload et les settings actifs ; - `reinitialize(&mut LoggingGuard, &LoggingSettings)` remplaçant le `Vec` de layers sans réinstaller le subscriber global ; - configuration sans sink supportée à l'initialisation afin de permettre une activation ultérieure par hot reload ; - mapping `SpanEvents::Off/NewAndClose/Full` vers `FmtSpan::NONE`, `NEW | CLOSE` et `FULL` ; - tests du takeover, des overrides, du démarrage sans sink, de l'activation à chaud, du refus d'un second `initialize()` et de la persistance du silence des targets externes. 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 fondé sur un `Vec>>` 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::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 Statut : validé après `pre.004-fix.004`. Les validations utilisateur `cargo fmt --all`, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test --workspace` sont propres sur `0.1.2-pre.4.fix.4`. Réalisé : - `tracing-appender 0.2.5` ajouté via `^0.2`, sans feature optionnelle ; - console stdout/stderr remplacée par `NonBlockingBuilder` en mode lossy ; - fichier optionnel réellement activé avec `RollingFileAppender::builder()` fallible et rotations `Never/Hourly/Daily` ; - console et fichier possèdent chacun leur `WorkerGuard` et leur `ErrorCounter` ; - `LoggingGuard::dropped_lines()` expose un `DroppedLines` cumulatif pour console/fichier/total, sans perdre les compteurs des sinks retirés lors d'un reload ; - formatter humain enrichi avec target, source file et line, ANSI du formatter désactivé ; - stripping ANSI fichier effectué côté worker, avant persistence, avec état conservé entre buffers ; - hot reload prépare les nouveaux writers/guards avant le swap, remplace explicitement les layers via `Handle::modify`, mémorise les dropped lines, détruit les anciens layers puis détruit leurs guards pour provoquer le drain/flush ; - une erreur de construction du nouveau file appender retourne `logging.file_output_initialization_failed` et conserve la configuration précédente ; - 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. `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`. ### `0.1.2-pre.005` — intégration + concurrence + tests + audits Statut : validé après `pre.005-fix.001`. Les validations utilisateur `cargo fmt --all`, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test --workspace` sont propres sur `0.1.2-pre.5.fix.1`. Le probe d'overhead ignoré a également été exécuté explicitement sur 200000 itérations et a passé son garde-fou grossier (`baseline=19.658553ms`, `reload=28.807958ms`). Réalisé : - la construction `NonBlockingBuilder` de production est centralisée afin que le test unitaire puisse réutiliser exactement `lossy(true)` sans exposer la taille de queue dans `LoggingSettings` ; - un test de saturation déterministe utilise une queue de capacité 1 et un writer worker volontairement bloqué : le producer doit terminer sous timeout et `ErrorCounter::dropped_lines()` doit devenir non nul, ce qui détecterait une régression vers la backpressure ; - le test runtime global lance quatre producteurs façade KSP pendant 32 reloads alternant runtime silencieux et console présente avec niveau `Off`, afin d'exercer concurrence, création/retrait de workers et reload sans produire de bruit console ; - un test d'intégration de lifecycle span capture le formatter `New | Close` et vérifie la présence de `time.busy` et `time.idle` ; - un test d'ownership parcourt les crates workspace autres que Logging et refuse les dépendances directes `tracing`, `tracing-subscriber`, `tracing-appender` ainsi que leurs usages Rust directs ; - `README.md`, `USAGE.md` et `TODO.md` sont introduits dans la crate avec la responsabilité, les frontières, l'initialisation/reload, les spans sync/async et les capacités différées ; - un test diagnostic ignoré compare un subscriber local fixe à la même couche placée derrière `reload::Layer`; il utilise un garde-fou volontairement très large contre une régression grossière et doit être exécuté explicitement avec `cargo test -p ksp-logging-lib --test overhead -- --ignored --nocapture` ; - aucun nouveau backend, setting public, format ou dépendance n'est introduit dans cette tranche. Les validations de `pre.005-fix.001` sont closes. Aucun répertoire `scripts/` n'était présent dans la base stable `0.1.1`; aucun audit script historique n'est donc déclaré exécuté par anticipation. Si un script existe dans le dépôt de développement au moment de la validation finale, il doit être exécuté selon les règles générales. ### `0.1.2-pre.006` — validation finale + docs + cleanup + prompt 0.1.3 Statut : implémenté dans le delta `pre.006`, validations Cargo finales à exécuter dans l'environnement de développement. Réalisé : - Tokio est centralisé dans `[workspace.dependencies]` sous `^1.53` avec uniquement `rt`, `rt-multi-thread` et `macros` ; - `ksp-logging-lib` le consomme uniquement sous `[dev-dependencies]`, de sorte que son API/runtime de production reste executor-agnostic ; - un test `#[tokio::test(flavor = "current_thread")]` instrumente une vraie future comportant plusieurs `yield_now().await` et vérifie plusieurs couples `enter/exit`, ce qui confirme la ré-entrée du span à chaque reprise réelle de polling ; - un test `#[tokio::test(flavor = "multi_thread", worker_threads = 2)]` exécute deux futures instrumentées via `tokio::spawn`, avec suspensions répétées, et vérifie leur terminaison ainsi que l'équilibre `enter/exit` ; - la documentation de crate précise que Tokio est un outil de test uniquement et n'est pas imposé aux consumers ; - le TODO final est ramené aux validations `pre.006`, au contrôle du graphe normal/dev et à la future livraison `rel.001` ; - le prompt `prompts/003-V0_1_3_START_PROMPT.md` est préparé pour ouvrir Config après la stable `v0.1.2`, avec relation Config -> `LoggingSettings`, profils/default_profile, env namespaces et politique secrets/public/debug ; - l'index des prompts est synchronisé ; - aucun changelog général n'existe actuellement dans le dépôt, donc aucun fichier de ce type n'est créé artificiellement en clôture. Validations finales attendues : ```bash cargo fmt --all cargo check --workspace cargo build -p ksp-logging-lib cargo clippy --workspace --all-targets cargo test --workspace cargo test -p ksp-logging-lib --test overhead -- --ignored --nocapture cargo tree -p ksp-logging-lib cargo tree -p ksp-logging-lib -d cargo tree -p ksp-logging-lib -e features cargo tree -p ksp-logging-lib -e normal cargo tree -p ksp-logging-lib -e dev ``` Le contrôle spécifique de cette tranche est que Tokio soit visible dans le graphe dev des tests mais absent du graphe normal de `ksp-logging-lib`. Validation utilisateur finale de `pre.006` : ```text cargo fmt --all OK cargo check --workspace OK cargo build -p ksp-logging-lib OK cargo clippy --workspace --all-targets OK cargo test --workspace OK cargo test -p ksp-logging-lib --test overhead -- --ignored --nocapture OK cargo tree -p ksp-logging-lib OK cargo tree -p ksp-logging-lib -d OK (aucun doublon) cargo tree -p ksp-logging-lib -e features OK cargo tree -p ksp-logging-lib -e normal OK (Tokio absent) cargo tree -p ksp-logging-lib -e dev OK (Tokio seul dev-dependency) ``` Le probe observé sur cette validation donne `baseline=16.959425ms`, `reload=24.942444ms` sur 200000 itérations et passe son garde-fou grossier. Les tests Tokio `current_thread` et `multi_thread` passent tous deux. `pre.006-fix.001` est documentaire uniquement et ne change pas la version Cargo `0.1.2-pre.6`. Il corrige le prompt `0.1.3` avec les décisions de clôture suivantes : - les fichiers runtime réels appartiennent sous `config/` ; - les exemples appartiennent sous `config/examples/` et les schemas sous `config/schemas/` ; - les namespaces d'environnement deviennent `KSP_*` pour KSP et `KSPB_*` pour la branche bot ; - Config doit reprendre le modèle documents unitaires + fichiers composites déjà utilisé dans khadhroony-bot3 ; - `ksp-config-lib` est la seule frontière qui lit/résout/modifie les fichiers Config et les variables d'environnement applicatives ; - les secrets sont protégés contre l'exposition implicite mais peuvent être consultés/modifiés via des contrats privilégiés explicites, notamment pour une application de management Config. Le découpage reste souple. Une tranche trop large est scindée ; une tranche devenue inutile est supprimée par correction explicite du plan. ## Hors scope confirmé - `ksp-config-lib` et documents/profils Config ; - parsing JSON/TOML de configuration ; - watcher de fichiers de configuration : Logging fournit `reinitialize`, Config décidera plus tard quand l'appeler ; - Tauri comme dépendance de `ksp-logging-lib` ; - wallet/keypair/signer ; - RPC/WS/providers ; - Program decoding/execution ; - Store/PostgreSQL ; - materializers ; - workers/jobs/pipelines ; - scenarios ; - trading/ML ; - OpenTelemetry/export réseau ; - observabilité distribuée ; - `#[instrument]` public dans cette release ; - JSON log format ; - field-based/domain filtering ; - rotation par taille/compression ; - multi-route avancé ; - politique de rétention complexe ; - compatibilité automatique avec la crate `log`/capture des logs tiers. ## Critères de sortie de `0.1.2` La release peut être stabilisée lorsque : - `ksp-logging-lib` est la façade runtime unique KSP et seule propriétaire directe de la stack tracing ; - les crates comportementales de test utilisent exclusivement la façade KSP pour événements et spans ; - les cinq macros événements fonctionnent avec target KSP explicite et callsite réel ; - la surface de spans sync/async fonctionne sans exposer une dépendance `tracing` directe aux consumers ; - l'instrumentation async est validée sur un executor réel current-thread et multi-thread sans ajouter d'executor aux dépendances runtime de Logging ; - `NEW | CLOSE` permet d'obtenir rapidement début/fin et temps busy/idle lorsque demandé ; - les settings sont indépendants de Config ; - les targets externes sont silencieux par défaut et les détails utiles sont réémis explicitement par leur propriétaire KSP ; - console et fichier optionnel sont non bloquants et leurs guards sont possédés explicitement ; - la saturation des queues ne bloque pas le hot path et les lignes abandonnées restent observables ; - le fichier supprime les séquences ANSI avant persistence ; - `initialize` installe le subscriber une seule fois ; - `reinitialize` permet de modifier à chaud niveaux, filters et sorties retenues sans second subscriber global ; - une erreur de reconfiguration conserve l'ancienne configuration active ; - les reloads concurrents sont testés sans panic/deadlock ; - les erreurs utilisent Core sans dépendance inverse ; - le graphe/features respecte le besoin minimal retenu ; - les validations workspace et audits présents sont propres ; - la documentation finale et le prompt `0.1.3` sont prêts. ## Questions ouvertes après `pre.006` 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 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. `pre.005` ferme les deux points de robustesse restants de `pre.004` : la saturation est testée avec le builder de production et une queue bornée injectée uniquement dans le test ; la concurrence/reload est exercée par quatre producteurs pendant des reconfigurations répétées. Le probe d'overhead existe désormais comme test diagnostic explicitement ignoré par défaut. `pre.006` complète cette couverture par deux tests Tokio réels sans faire de Tokio une dépendance runtime de Logging. Aucune question architecturale bloquante ne reste ouverte pour la stable `0.1.2`. Le détail visuel exact du formatter humain reste volontairement non contractuel : sa ponctuation peut évoluer ultérieurement tant que les informations et garanties publiques documentées sont préservées. La release est clôturée par `0.1.2-rel.001` avec `workspace.package.version = "0.1.2"`. Après validation du delta de publication, le commit stable reçoit le tag `v0.1.2`. La session fonctionnelle suivante est `0.1.3 — ksp-config-lib` avec le prompt `prompts/003-V0_1_3_START_PROMPT.md`.