# Utilisation de ksp-logging-lib ## Target d'une crate consommatrice Chaque crate KSP comportementale fournit explicitement son target, égal au nom Cargo de la crate : ```rust const LOGGING_TARGET: &str = "ksp-store-lib"; ksp_logging_lib::trace!( target: LOGGING_TARGET, domain = "store", component = "postgres", operation = "load_transactions", "executing store operation" ); ``` Les champs `domain`, `component`, `operation` et autres champs structurés sont ajoutés par le caller lorsqu'ils sont utiles ; ils ne remplacent pas le target propriétaire. ## Contrat des outputs `LoggingSettings` possède : - le `default_filter` global de takeover KSP ; - les `TargetFilter` globaux ; - la politique `SpanEvents` ; - une console déclarée optionnellement ; - zéro, un ou plusieurs `FileSettings`. Chaque output possède un `OutputFilter` indépendant. Son niveau, ses targets et ses domains s'appliquent **en plus** de la politique globale. `*` signifie « tous » dans la dimension concernée. Les formats publics sont : ```text Human Compact Pretty Json ``` Une sortie fichier possède un `output_id` stable et unique dans `LoggingSettings`. Cet identifiant appartient au runtime Logging et ne doit pas être confondu avec les `file_id` utilisés par `ksp-config-lib` pour identifier ses documents. Les fichiers persistants interdisent `ansi = true`. ## Runtime multi-output et routing `domain` `0.1.3-pre.005` active le multi-sink, les formats et le routing niveau/target ; `0.1.3-pre.006` complète le routing structuré `domain`. Le runtime supporte donc réellement : - plusieurs fichiers simultanés ; - les formats `Human`, `Compact`, `Pretty` et `Json` ; - l'ANSI console ; - le routing par niveau, target et `domain` pour chaque output ; - le comptage cumulatif des lignes abandonnées par `output_id` fichier ; - le hot reload de ces sorties sans réinstaller le subscriber global. Le `domain` n'est jamais transformé en target. Un event portant directement `domain` utilise cette valeur ; sinon il hérite du `domain` effectif de son span. Un span explicite remplace le `domain` de son parent, tandis qu'un span sans `domain` l'hérite. Les lifecycle events d'un span utilisent le même `domain` effectif. Pour `domains[]`, `["*"]` accepte tous les événements, y compris ceux sans `domain`. Un selector nommé correspond par préfixe et ne sélectionne pas une entrée sans `domain`. Les helpers `ConsoleSettings::stdout()` et `ConsoleSettings::stderr()` restent des raccourcis `Human`, sans ANSI et sans restriction supplémentaire par output. ## Initialisation ```rust let file = ksp_logging_lib::FileSettings::new( "file.worker", true, "logs", "worker.log", ksp_logging_lib::FileRotation::Daily, ksp_logging_lib::LogFormat::Human, ksp_logging_lib::OutputFilter::unrestricted(), ); let settings = ksp_logging_lib::LoggingSettings::new( ksp_logging_lib::LogFilterLevel::Info, ksp_logging_lib::SpanEvents::NewAndClose, std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()), std::vec![file], ); let initialize_result = ksp_logging_lib::initialize(&settings); let mut logging_guard = match initialize_result { std::result::Result::Ok(guard) => guard, std::result::Result::Err(error) => return std::result::Result::Err(error), }; ``` Une configuration sans output actif est valide et installe une infrastructure initialement silencieuse qui pourra être activée plus tard par hot reload. ## Identité de lancement et isolation des fichiers persistants Une application qui utilise des sorties fichiers persistantes peut attacher une identité stable au runtime : ```rust let identity = ksp_logging_lib::LoggingRuntimeIdentity::new( "ksp-app-config-desk", "20260816-182519.123Z-p4242", ); let identity = match identity { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let initialize_result = ksp_logging_lib::initialize_with_identity(&settings, &identity); let mut logging_guard = match initialize_result { std::result::Result::Ok(guard) => guard, std::result::Result::Err(error) => return std::result::Result::Err(error), }; ``` Les composants de l'identité n'acceptent que des caractères ASCII alphanumériques et `._-`; les séparateurs de path et espaces sont refusés. Si un `FileSettings` configure `ksp-debug.log`, l'identité ci-dessus produit un prefix runtime : ```text ksp-app-config-desk.20260816-182519.123Z-p4242.ksp-debug.log ``` Le `FileSettings` d'origine reste `ksp-debug.log`. `reinitialize(&mut logging_guard, ...)` réutilise automatiquement l'identité stockée dans le guard : un hot reload ne crée donc pas une fausse nouvelle identité de lancement. Les outputs réellement actifs sont inspectables sans exposer les writers : ```rust for file in logging_guard.active_file_outputs() { let output_id = file.output_id(); let directory = file.directory(); let prefix = file.file_name_prefix(); let rotation = file.rotation(); } ``` `initialize()` reste disponible pour les consommateurs qui n'ont pas besoin d'identité de lancement. `initialize_with_identity()` est la forme attendue pour les applications KSP qui activent des logs persistants et doivent empêcher la fusion de plusieurs lancements dans le même fichier. ## Construction d'un runtime multi-output Le runtime peut activer plusieurs sorties ayant des formats et filtres niveau/target/domain distincts : ```rust let console = ksp_logging_lib::ConsoleSettings::new( true, ksp_logging_lib::ConsoleOutput::Stderr, true, ksp_logging_lib::LogFormat::Compact, ksp_logging_lib::OutputFilter::new( ksp_logging_lib::LogFilterLevel::Debug, std::vec!["*".to_string()], std::vec!["*".to_string()], ), ); let error_file = ksp_logging_lib::FileSettings::new( "file.config.error", true, "logs/config", "error.jsonl", ksp_logging_lib::FileRotation::Daily, ksp_logging_lib::LogFormat::Json, ksp_logging_lib::OutputFilter::new( ksp_logging_lib::LogFilterLevel::Error, std::vec!["ksp-config-lib".to_string()], std::vec!["*".to_string()], ), ); let settings = ksp_logging_lib::LoggingSettings::new( ksp_logging_lib::LogFilterLevel::Warn, ksp_logging_lib::SpanEvents::NewAndClose, std::option::Option::Some(console), std::vec![error_file], ); let validation = settings.validate(); ``` `validate()` vérifie le contrat structurel. `initialize/reinitialize` appliquent ensuite conjointement niveau, target et `domain` par output. Par exemple, un fichier réservé au domaine Store peut utiliser : ```rust ksp_logging_lib::OutputFilter::new( ksp_logging_lib::LogFilterLevel::Debug, std::vec!["ksp-store-lib".to_string()], std::vec!["store".to_string()], ) ``` Un event `domain = "store.postgres"` correspond au selector `store`; un event sans `domain` n'y correspond pas. ## Hot reload Une configuration peut être appliquée sans redémarrer le processus ou le worker : ```rust let debug_settings = ksp_logging_lib::LoggingSettings::new( ksp_logging_lib::LogFilterLevel::Info, ksp_logging_lib::SpanEvents::NewAndClose, std::option::Option::Some(ksp_logging_lib::ConsoleSettings::stderr()), std::vec::Vec::new(), ) .with_target_filter(ksp_logging_lib::TargetFilter::new( "ksp-store-lib", ksp_logging_lib::LogFilterLevel::Debug, )); let reload_result = ksp_logging_lib::reinitialize(&mut logging_guard, &debug_settings); if let std::result::Result::Err(error) = reload_result { return std::result::Result::Err(error); } ``` La nouvelle configuration est préparée avant la bascule. Si sa validation ou la création d'un nouveau sink échoue, l'ancienne configuration reste active. ## Spans synchrones ```rust let span = ksp_logging_lib::trace_span!( target: LOGGING_TARGET, "materialize_transaction", domain = "store" ); let output = span.in_scope(|| { return materialize_transaction(); }); ``` Avec `SpanEvents::NewAndClose`, le formatter produit les événements de création/fermeture et les temps `busy` / `idle` à la fermeture. ## Spans async Une `Future` doit être instrumentée avec `ksp_logging_lib::instrument` ; un guard d'entrée de span ne doit pas être conservé à travers `.await` : ```rust let span = ksp_logging_lib::trace_span!( target: LOGGING_TARGET, "fetch_account", domain = "transport" ); let output = ksp_logging_lib::instrument(span, fetch_account()).await; ``` La future instrumentée entre/sort du span pendant ses polls et lors de son `Drop`, conformément au contrat de la primitive `tracing` sous-jacente. ## Lignes abandonnées Les sorties utilisent des queues lossy afin de ne pas appliquer de backpressure au hot path. La vue agrégée console/fichier reste disponible : ```rust let dropped = logging_guard.dropped_lines(); ksp_logging_lib::warn!( target: LOGGING_TARGET, console = dropped.console(), file = dropped.file(), total = dropped.total(), "logging queues dropped lines" ); ``` Pour un fichier précis : ```rust let dropped_for_output = logging_guard.dropped_file_lines("file.worker"); ``` Le compteur par `output_id` reste cumulatif à travers les hot reloads tant que le même `LoggingGuard` est conservé. ## Instrumentation async et executor `instrument(span, future)` accepte une `Future` standard et ne dépend d'aucun executor particulier. Tokio n'est présent qu'en `dev-dependency` pour valider la surface sur un executor réel, y compris après plusieurs suspensions et sur un runtime multi-thread.