55 KiB
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 :
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-libn'existe pas encore ;ksp-core-libexpose déjàErrorCode,ErrorContext,Error,Result<T>etPubkeyainsi que les Program IDs fondamentaux et leur registre ;ksp-core-libdépend uniquement desolana-pubkeyau runtime ;- le delta final
deltas/0.1.1/rel.001.mdenregistre la validation finale de0.1.1; - le prompt
prompts/002-V0_1_2_START_PROMPT.mdpré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 :
- une surface d'émission KSP pour
error,warn,info,debugettrace; - une surface KSP de spans pour instrumenter des scopes synchrones et des
Futureasync sans dépendancetracingdirecte chez les consumers ; - des settings runtime propres à Logging, indépendants de Config, permettant de choisir niveau, console, fichier et paramètres associés ;
- une installation globale unique du subscriber KSP ;
- une reconfiguration à chaud après initialisation, sans redémarrer le processus, worker ou service appelant ;
- une sortie console non bloquante ;
- une sortie fichier optionnelle non bloquante avec rotation bornée ;
- un filtrage KSP global et par target, avec silence par défaut des targets externes ;
- un lifecycle explicite conservant les guards nécessaires aux writers non bloquants et au reload ;
- 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 ;
- un contrat d'erreurs basé sur
ksp-core-lib; - 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 :
ksp-logging-lib
-> ksp-core-lib
-> tracing
-> tracing-subscriber
-> tracing-appender
Règles :
ksp-logging-libest le seul propriétaire KSP direct detracing,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-subscriberoutracing-appenderpour produire ses propres logs ; ksp-core-libne dépend pas de Logging ;ksp-logging-libne dépend pas deksp-config-lib;ksp-logging-libne dépend pas de Wallet, Transport, Program, Store, workers, jobs, pipelines ou applications ;- les crates
*-apipurement 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
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
- un niveau KSP default ;
- zéro ou plusieurs overrides par target/préfixe KSP ;
- un niveau global externe fixé à
Offpar 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_LOGpar Logging ; - pas de directive textuelle
EnvFilterdans les settings ; - pas de filtre sur
domain/componenten 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 :
LoggingSettings
default_filter: LogFilterLevel
target_filters: Vec<TargetFilter>
span_events: SpanEvents
console: Option<ConsoleSettings>
file: Option<FileSettings>
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.
Stdout | Stderr
-> NonBlocking
-> WorkerGuard
Objectifs :
- l'émission ordinaire d'un log KSP ne doit pas attendre l'I/O terminal ;
- le
WorkerGuardconsole 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
fmtretenue 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 :
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 :
LoggingGuard
Contrat :
initialize(&LoggingSettings) -> ksp_core_lib::Result<LoggingGuard>est appelé au plus une fois par processus ;- console et fichier peuvent chacun posséder leur
WorkerGuard; LoggingGuardconserve les guards actifs, les compteurs de dropped lines et l'état nécessaire au hot reload ;- l'appelant conserve
LoggingGuardpendant 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 clonesNonBlockingqu'ils possèdent soient libérés avant l'envoi du shutdown/drain au worker ; - aucun
WorkerGuardn'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 :
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 :
pub fn initialize(settings: &LoggingSettings) -> ksp_core_lib::Result<LoggingGuard>
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 :
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 :
success -> nouvelle configuration active
error -> ancienne configuration reste active
Ordre conceptuel :
- valider les nouveaux
LoggingSettings; - construire les nouveaux writers/non-blocking/guards nécessaires ;
- préparer les nouveaux filters/layers ;
- basculer la configuration active ;
- 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 :
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 :
ksp_core_lib::Error
ksp_core_lib::ErrorCode
ksp_core_lib::Result<T>
Le domaine stable appartient à Logging :
logging
Premiers codes conceptuels :
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
ErrorCodesont définies dansksp-logging-lib; - Core n'ajoute aucune variante/connaissance Logging ;
- les causes externes utiles sont conservées avec
Error::with_source(...)lorsqu'elles satisfontError + Send + Sync + 'static; - aucun
unwrap,expect,panicproduction 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,componentet autres fields ; - exécution synchrone dans un span ;
- instrumentation correcte des
Futureasync ; - événements de lifecycle optionnels, au minimum
NEW | CLOSE, permettant d'obtenir rapidement timestamp de début, timestamp de fermeture et tempsbusy/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 :
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::<ErrorCode constants owned by Logging>
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 :
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
tracingdans 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
pollpertinent et lors duDropde la future instrumentée ; - aucun enter guard conservé à travers
.awaitdans l'API recommandée ; SpanEvents::NewAndCloseproduit les événements lifecycle attendus ;CLOSEcontient les tempsbusy/idlelorsque les timestamps sont actifs ;SpanEvents::Offne 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
initializerefusé ; - 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::Erroravec code Logging ; - guards console/fichier conservés jusqu'au flush final ;
- dropped-line counters récupérables/observables selon l'API finale.
Hot reload
reinitializenécessite unLoggingGuardvalide ;- 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 :
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 de0.1.2n'exigent pas#[instrument]; - pas de
nu-ansi-termpar activation KSP deansi; - pas de
tracing-logpar 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-appenderpeut tirertimepour son fonctionnement propre sans justifier d'activer la featuretimedetracing-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
WorkerGuardpar un objetLoggingGuard; - 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-libet l'ajouter au workspace ; - dépendre de
ksp-core-lib; - revérifier puis ajouter
tracingau 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::Instrumentaux 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-subscriberajouté avec uniquement la featurefmt;- mapping complet
LogFilterLevel -> LevelFilter; Targetsconfiguré avec default externeOFF, préfixe KSPksp-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 erreurlogging.already_initialized; LoggingGuardpublic possédant le handle de reload et les settings actifs ;reinitialize(&mut LoggingGuard, &LoggingSettings)remplaçant leVecde 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/FullversFmtSpan::NONE,NEW | CLOSEetFULL; - 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<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
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.5ajouté via^0.2, sans feature optionnelle ;- console stdout/stderr remplacée par
NonBlockingBuilderen mode lossy ; - fichier optionnel réellement activé avec
RollingFileAppender::builder()fallible et rotationsNever/Hourly/Daily; - console et fichier possèdent chacun leur
WorkerGuardet leurErrorCounter; LoggingGuard::dropped_lines()expose unDroppedLinescumulatif 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_failedet 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
NonBlockingBuilderde production est centralisée afin que le test unitaire puisse réutiliser exactementlossy(true)sans exposer la taille de queue dansLoggingSettings; - 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 | Closeet vérifie la présence detime.busyettime.idle; - un test d'ownership parcourt les crates workspace autres que Logging et refuse les dépendances directes
tracing,tracing-subscriber,tracing-appenderainsi que leurs usages Rust directs ; README.md,USAGE.mdetTODO.mdsont 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 aveccargo 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.53avec uniquementrt,rt-multi-threadetmacros; ksp-logging-lible 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 plusieursyield_now().awaitet vérifie plusieurs couplesenter/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 viatokio::spawn, avec suspensions répétées, et vérifie leur terminaison ainsi que l'équilibreenter/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 livraisonrel.001; - le prompt
prompts/003-V0_1_3_START_PROMPT.mdest préparé pour ouvrir Config après la stablev0.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 :
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 :
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 sousconfig/schemas/; - les namespaces d'environnement deviennent
KSP_*pour KSP etKSPB_*pour la branche bot ; - Config doit reprendre le modèle documents unitaires + fichiers composites déjà utilisé dans khadhroony-bot3 ;
ksp-config-libest 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-libet 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-libest 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
tracingdirecte 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 | CLOSEpermet 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 ;
initializeinstalle le subscriber une seule fois ;reinitializepermet 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.3sont prêts.
Questions ouvertes après pre.006
Les deux questions d'API propres à pre.002 sont résolues :
- les macros KSP délèguent aux macros
tracingau point d'appel via un bridge interne caché et exigent untarget:explicite ; - la surface span publique est
Span::in_scope(...)pour le synchrone etinstrument(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.