Files
khadhroony-solana-project/docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md
2026-08-14 18:24:28 +02:00

44 KiB

Plan KSP 0.1.2 — Logging foundation

Statut

Plan actif de 0.1.2, établi par 0.1.2-pre.001, corrigé par 0.1.2-pre.001-fix.001, concrétisé par la façade de 0.1.2-pre.002 puis étendu au runtime subscriber par 0.1.2-pre.003.

pre.002-fix.001 a été validé dans l'environnement de développement avec cargo fmt, cargo check, cargo clippy --workspace --all-targets et cargo test --workspace propres sur la version Cargo 0.1.2-pre.2.fix.1. pre.003 ajoute le subscriber global, le takeover/filtering KSP, la console initiale et le hot reload des layers. Les writers non bloquants, le fichier, les guards et le stripping ANSI restent réservés à pre.004.

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. Pour pre.002, tracing reste à 0.1.44, publié dans la génération ^0.1; tracing-subscriber et tracing-appender ne sont pas encore ajoutés.

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 au formatter console. env-filter, ansi, tracing-log, json, time et les autres features optionnelles ne sont pas activées. tracing-appender reste absent jusqu'à pre.004, où il sera réellement consommé.

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 afin que les pertes puissent être observées/inspectées ; l'API publique exacte de cette statistique sera fixée pendant l'implémentation.

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.

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.

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, puis flush/détruit les anciens guards ;
  • aucun WorkerGuard n'est détruit à la fin d'une fonction d'initialisation ou de reconfiguration avant que son sink ne soit effectivement retiré ;
  • 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::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
├── unit_tests/
│   ├── settings.rs
│   ├── span.rs
│   └── runtime.rs
└── tests/
    ├── callsite.rs
    ├── reload.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 la tranche pre.003, sous réserve des validations Cargo à exécuter dans l'environnement de développement.

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 est un Vec<Box<dyn Layer<Registry>>> placé derrière une unique reload::Layer. Cette composition permet de changer à chaud le nombre et le type des sinks tout en conservant un seul subscriber global et prépare directement l'ajout du layer fichier de pre.004.

0.1.2-pre.004 — non-blocking console/fichier + guards + ANSI + reload sinks

Objectifs :

  • revérifier puis ajouter tracing-appender ;
  • passer console et fichier sur writers non bloquants ;
  • implémenter FileSettings et Never/Hourly/Daily ;
  • utiliser le builder fallible du file appender ;
  • conserver les WorkerGuard et ErrorCounter ;
  • implémenter le stripping ANSI fichier ;
  • permettre le remplacement à chaud des sinks/settings concernés ;
  • tester flush/lifetime, saturation et erreurs de fichier.

0.1.2-pre.005 — intégration + concurrence + tests + audits

Objectifs :

  • compléter les tests settings/filtering/takeover/spans/runtime/reload ;
  • tester les reloads concurrents et l'absence de deadlock/panic ;
  • vérifier que les crates de fixture n'utilisent pas directement tracing ;
  • auditer les réexports crate-root ;
  • auditer la documentation de crate (README.md, TODO.md, USAGE.md) ;
  • exécuter les audits Cargo/features/doublons et scripts réellement présents ;
  • mesurer l'overhead grossier de la stratégie reload afin d'éviter une architecture inutilement coûteuse.

0.1.2-pre.006 — validation finale + docs + cleanup + prompt 0.1.3

Objectifs :

  • exécuter les validations workspace finales ;
  • fermer les écarts résiduels ;
  • consolider la documentation durable ;
  • synchroniser roadmap/index/changelog général s'il existe alors ;
  • nettoyer/archive uniquement ce que les règles demandent ;
  • préparer le prompt final 0.1.3 — ksp-config-lib en documentant la conversion Config -> LoggingSettings et le hot reload ;
  • préparer la livraison finale avant rel.001 et tag stable.

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 ;
  • 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.003

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.

Restent à confirmer par les prereleases suivantes sans remettre en cause ce contrat :

  1. l'API publique exacte d'observation des dropped lines ;
  2. le détail visuel exact du formatter humain, sans transformer sa ponctuation en contrat public ;
  3. le comportement de flush/rotation et le swap transactionnel des WorkerGuard lorsque les sinks non bloquants seront introduits.

La prochaine action après validation de pre.003 est 0.1.2-pre.004 : tracing-appender, console/fichier non bloquants, guards, rotation, stripping ANSI et reload des sinks.