Files
khadhroony-solana-project/docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md
2026-08-14 20:41:23 +02:00

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-lib n'existe pas encore ;
  • ksp-core-lib expose déjà ErrorCode, ErrorContext, Error, Result<T> 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 :

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

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 :

  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 :

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 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 :

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 ;
  • 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 :

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 :

  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 :

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 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 :

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 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 :

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<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.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 :

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 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.