v0.1.2-pre.003
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 28
|
||||
# version: 29
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-core-lib", "crates/ksp-logging-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.2-pre.2.fix.1"
|
||||
version = "0.1.2-pre.3"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
@@ -16,6 +16,7 @@ publish = false
|
||||
[workspace.dependencies]
|
||||
solana-pubkey = { version = "^4.3", default-features = false }
|
||||
tracing = { version = "^0.1", default-features = false, features = ["std"] }
|
||||
tracing-subscriber = { version = "^0.3", default-features = false, features = ["fmt"] }
|
||||
|
||||
[workspace.lints.rust]
|
||||
missing_docs = "warn"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# file: crates/ksp-logging-lib/Cargo.toml
|
||||
# version: 1
|
||||
# version: 2
|
||||
|
||||
[package]
|
||||
name = "ksp-logging-lib"
|
||||
@@ -10,6 +10,7 @@ repository.workspace = true
|
||||
[dependencies]
|
||||
ksp-core-lib = { path = "../ksp-core-lib" }
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// file: crates/ksp-logging-lib/src/error.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
/// Error code used when runtime logging settings are invalid.
|
||||
pub const ERROR_CODE_INVALID_SETTINGS: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "invalid_settings");
|
||||
/// Error code used when a global logging subscriber is already installed.
|
||||
pub const ERROR_CODE_ALREADY_INITIALIZED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "already_initialized");
|
||||
/// Error code used when a hot reload cannot replace the active runtime layers.
|
||||
pub const ERROR_CODE_RELOAD_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("logging", "reload_failed");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/lib.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -7,15 +7,27 @@
|
||||
//! KSP-owned logging and tracing facade.
|
||||
//!
|
||||
//! This crate owns the KSP runtime logging contract. Behavioral KSP crates emit events and spans through this facade rather than depending directly on the
|
||||
//! `tracing` stack. Runtime subscriber initialization, filtering, outputs and hot reload are added by the following `0.1.2` prereleases.
|
||||
//! `tracing` stack. `0.1.2-pre.003` installs the single global subscriber, applies KSP takeover filtering, provides the initial console layer and establishes
|
||||
//! hot reload. Non-blocking writers, file output, ANSI stripping and writer guards are completed by the following prerelease.
|
||||
|
||||
mod error;
|
||||
mod macros;
|
||||
mod runtime;
|
||||
mod settings;
|
||||
mod span;
|
||||
|
||||
/// Error code used when a global logging subscriber is already installed.
|
||||
pub use self::error::ERROR_CODE_ALREADY_INITIALIZED;
|
||||
/// Error code used when runtime logging settings are invalid.
|
||||
pub use self::error::ERROR_CODE_INVALID_SETTINGS;
|
||||
/// Error code used when a hot reload cannot replace the active runtime layers.
|
||||
pub use self::error::ERROR_CODE_RELOAD_FAILED;
|
||||
/// Guard owning the mutable runtime state of the installed KSP logging subscriber.
|
||||
pub use self::runtime::LoggingGuard;
|
||||
/// Installs the global KSP tracing subscriber.
|
||||
pub use self::runtime::initialize;
|
||||
/// Replaces the active KSP logging settings without reinstalling the global subscriber.
|
||||
pub use self::runtime::reinitialize;
|
||||
/// Console stream selected for human-readable logs.
|
||||
pub use self::settings::ConsoleOutput;
|
||||
/// Runtime settings for the optional console output.
|
||||
|
||||
126
crates/ksp-logging-lib/src/runtime.rs
Normal file
126
crates/ksp-logging-lib/src/runtime.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
// file: crates/ksp-logging-lib/src/runtime.rs
|
||||
// version: 1
|
||||
|
||||
use tracing_subscriber::Layer; // rust-rules: trait-import
|
||||
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
|
||||
|
||||
/// Guard owning the mutable runtime state of the installed KSP logging subscriber.
|
||||
pub struct LoggingGuard {
|
||||
reload_handle: RuntimeReloadHandle,
|
||||
settings: crate::LoggingSettings,
|
||||
}
|
||||
|
||||
impl LoggingGuard {
|
||||
/// Returns the settings currently active in the KSP logging runtime.
|
||||
#[must_use]
|
||||
pub fn settings(&self) -> &crate::LoggingSettings {
|
||||
return &self.settings;
|
||||
}
|
||||
}
|
||||
|
||||
type BoxedRuntimeLayer = std::boxed::Box<dyn tracing_subscriber::Layer<tracing_subscriber::Registry> + std::marker::Send + std::marker::Sync + 'static>;
|
||||
type RuntimeLayers = std::vec::Vec<BoxedRuntimeLayer>;
|
||||
type RuntimeReloadHandle = tracing_subscriber::reload::Handle<RuntimeLayers, tracing_subscriber::Registry>;
|
||||
|
||||
/// Installs the global KSP tracing subscriber.
|
||||
///
|
||||
/// This function may succeed only once for the lifetime of the process. The returned guard is then used by [`crate::reinitialize`] to replace the active
|
||||
/// KSP logging configuration without installing a second global subscriber.
|
||||
pub fn initialize(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
return prepare_runtime_layers(settings).and_then(|runtime_layers| -> ksp_core_lib::Result<crate::LoggingGuard> {
|
||||
let (reload_layer, reload_handle) = tracing_subscriber::reload::Layer::new(runtime_layers);
|
||||
let subscriber = tracing_subscriber::registry().with(reload_layer);
|
||||
let install_result = tracing::subscriber::set_global_default(subscriber);
|
||||
return match install_result {
|
||||
std::result::Result::Ok(()) => std::result::Result::Ok(crate::LoggingGuard { reload_handle, settings: settings.clone() }),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_ALREADY_INITIALIZED, "the global KSP tracing subscriber is already installed").with_source(error),
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// Replaces the active KSP logging settings without reinstalling the global subscriber.
|
||||
///
|
||||
/// New runtime layers are fully prepared before the reload is attempted. If validation or preparation fails, the currently active configuration is left
|
||||
/// unchanged.
|
||||
pub fn reinitialize(guard: &mut crate::LoggingGuard, settings: &crate::LoggingSettings) -> ksp_core_lib::Result<()> {
|
||||
return prepare_runtime_layers(settings).and_then(|runtime_layers| -> ksp_core_lib::Result<()> {
|
||||
let reload_result = guard.reload_handle.reload(runtime_layers);
|
||||
return match reload_result {
|
||||
std::result::Result::Ok(()) => {
|
||||
guard.settings = settings.clone();
|
||||
std::result::Result::Ok(())
|
||||
},
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_RELOAD_FAILED, "unable to reload the KSP logging runtime").with_source(error),
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn prepare_runtime_layers(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<RuntimeLayers> {
|
||||
let validation_error = settings.validate().err();
|
||||
if let std::option::Option::Some(error) = validation_error {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if settings.file().is_some() {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "file output is not available until the file backend is introduced")
|
||||
.with_context("field", "file"),
|
||||
);
|
||||
}
|
||||
let mut layers = RuntimeLayers::new();
|
||||
if let std::option::Option::Some(console) = settings.console() {
|
||||
layers.push(build_console_layer(console, settings));
|
||||
}
|
||||
return std::result::Result::Ok(layers);
|
||||
}
|
||||
|
||||
fn build_console_layer(console: &crate::ConsoleSettings, settings: &crate::LoggingSettings) -> BoxedRuntimeLayer {
|
||||
let writer = match console.output() {
|
||||
crate::ConsoleOutput::Stdout => tracing_subscriber::fmt::writer::BoxMakeWriter::new(std::io::stdout),
|
||||
crate::ConsoleOutput::Stderr => tracing_subscriber::fmt::writer::BoxMakeWriter::new(std::io::stderr),
|
||||
};
|
||||
let layer = tracing_subscriber::fmt::layer()
|
||||
.with_writer(writer)
|
||||
.with_ansi(false)
|
||||
.with_target(true)
|
||||
.with_span_events(map_span_events(settings.span_events()))
|
||||
.with_filter(build_target_filter(settings))
|
||||
.boxed();
|
||||
return layer;
|
||||
}
|
||||
|
||||
fn build_target_filter(settings: &crate::LoggingSettings) -> tracing_subscriber::filter::Targets {
|
||||
let mut filter = tracing_subscriber::filter::Targets::new()
|
||||
.with_default(tracing_subscriber::filter::LevelFilter::OFF)
|
||||
.with_target("ksp-", map_filter_level(settings.default_filter()));
|
||||
for target_filter in settings.target_filters() {
|
||||
filter = filter.with_target(target_filter.target_prefix(), map_filter_level(target_filter.level()));
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
const fn map_filter_level(level: crate::LogFilterLevel) -> tracing_subscriber::filter::LevelFilter {
|
||||
return match level {
|
||||
crate::LogFilterLevel::Off => tracing_subscriber::filter::LevelFilter::OFF,
|
||||
crate::LogFilterLevel::Error => tracing_subscriber::filter::LevelFilter::ERROR,
|
||||
crate::LogFilterLevel::Warn => tracing_subscriber::filter::LevelFilter::WARN,
|
||||
crate::LogFilterLevel::Info => tracing_subscriber::filter::LevelFilter::INFO,
|
||||
crate::LogFilterLevel::Debug => tracing_subscriber::filter::LevelFilter::DEBUG,
|
||||
crate::LogFilterLevel::Trace => tracing_subscriber::filter::LevelFilter::TRACE,
|
||||
};
|
||||
}
|
||||
|
||||
fn map_span_events(span_events: crate::SpanEvents) -> tracing_subscriber::fmt::format::FmtSpan {
|
||||
return match span_events {
|
||||
crate::SpanEvents::Off => tracing_subscriber::fmt::format::FmtSpan::NONE,
|
||||
crate::SpanEvents::NewAndClose => tracing_subscriber::fmt::format::FmtSpan::NEW | tracing_subscriber::fmt::format::FmtSpan::CLOSE,
|
||||
crate::SpanEvents::Full => tracing_subscriber::fmt::format::FmtSpan::FULL,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/runtime.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/tests/public_api.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Integration tests for the public crate-root surface of `ksp-logging-lib`.
|
||||
|
||||
@@ -52,3 +52,11 @@ fn all_span_levels_are_usable() {
|
||||
let _debug = ksp_logging_lib::debug_span!(target: TEST_TARGET, "debug_span");
|
||||
let _trace = ksp_logging_lib::trace_span!(target: TEST_TARGET, "trace_span");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_runtime_surface_is_addressable_without_installing_it() {
|
||||
let _initialize = ksp_logging_lib::initialize;
|
||||
let _reinitialize = ksp_logging_lib::reinitialize;
|
||||
let _already_initialized = ksp_logging_lib::ERROR_CODE_ALREADY_INITIALIZED;
|
||||
let _reload_failed = ksp_logging_lib::ERROR_CODE_RELOAD_FAILED;
|
||||
}
|
||||
|
||||
75
crates/ksp-logging-lib/tests/runtime.rs
Normal file
75
crates/ksp-logging-lib/tests/runtime.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
// file: crates/ksp-logging-lib/tests/runtime.rs
|
||||
// version: 1
|
||||
|
||||
//! Integration tests for global initialization, takeover filtering and hot reload.
|
||||
|
||||
const LOGGING_TARGET: &str = "ksp-logging-lib";
|
||||
const OTHER_KSP_TARGET: &str = "ksp-store-lib";
|
||||
const EXTERNAL_TARGET: &str = "sqlx";
|
||||
|
||||
fn logging_trace_enabled() -> bool {
|
||||
return tracing::enabled!(target: LOGGING_TARGET, tracing::Level::TRACE);
|
||||
}
|
||||
|
||||
fn other_ksp_info_enabled() -> bool {
|
||||
return tracing::enabled!(target: OTHER_KSP_TARGET, tracing::Level::INFO);
|
||||
}
|
||||
|
||||
fn other_ksp_debug_enabled() -> bool {
|
||||
return tracing::enabled!(target: OTHER_KSP_TARGET, tracing::Level::DEBUG);
|
||||
}
|
||||
|
||||
fn external_error_enabled() -> bool {
|
||||
return tracing::enabled!(target: EXTERNAL_TARGET, tracing::Level::ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn global_runtime_supports_takeover_hot_reload_and_single_initialization() {
|
||||
let disabled = ksp_logging_lib::LoggingSettings::new(
|
||||
ksp_logging_lib::LogFilterLevel::Info,
|
||||
ksp_logging_lib::SpanEvents::Off,
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
);
|
||||
let initialize_result = ksp_logging_lib::initialize(&disabled);
|
||||
assert!(initialize_result.is_ok());
|
||||
let mut guard = match initialize_result {
|
||||
std::result::Result::Ok(guard) => guard,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert!(!logging_trace_enabled());
|
||||
assert!(!other_ksp_info_enabled());
|
||||
assert!(!external_error_enabled());
|
||||
let enabled = 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::option::Option::None,
|
||||
)
|
||||
.with_target_filter(ksp_logging_lib::TargetFilter::new(LOGGING_TARGET, ksp_logging_lib::LogFilterLevel::Trace));
|
||||
let reload_result = ksp_logging_lib::reinitialize(&mut guard, &enabled);
|
||||
assert!(reload_result.is_ok());
|
||||
assert_eq!(guard.settings(), &enabled);
|
||||
assert!(logging_trace_enabled());
|
||||
assert!(other_ksp_info_enabled());
|
||||
assert!(!other_ksp_debug_enabled());
|
||||
assert!(!external_error_enabled());
|
||||
let unsupported_file = ksp_logging_lib::LoggingSettings::new(
|
||||
ksp_logging_lib::LogFilterLevel::Error,
|
||||
ksp_logging_lib::SpanEvents::Full,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(ksp_logging_lib::FileSettings::new("logs", "ksp", ksp_logging_lib::FileRotation::Daily)),
|
||||
);
|
||||
let failed_reload = ksp_logging_lib::reinitialize(&mut guard, &unsupported_file);
|
||||
assert!(failed_reload.is_err());
|
||||
assert_eq!(guard.settings(), &enabled);
|
||||
assert!(logging_trace_enabled());
|
||||
assert!(!external_error_enabled());
|
||||
let second_initialize = ksp_logging_lib::initialize(&disabled);
|
||||
assert!(second_initialize.is_err());
|
||||
let error = match second_initialize {
|
||||
std::result::Result::Ok(_) => return,
|
||||
std::result::Result::Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.code(), ksp_logging_lib::ERROR_CODE_ALREADY_INITIALIZED);
|
||||
}
|
||||
56
crates/ksp-logging-lib/unit_tests/runtime.rs
Normal file
56
crates/ksp-logging-lib/unit_tests/runtime.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// file: crates/ksp-logging-lib/unit_tests/runtime.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn level_mapping_covers_all_ksp_levels() {
|
||||
assert_eq!(super::map_filter_level(crate::LogFilterLevel::Off), tracing_subscriber::filter::LevelFilter::OFF);
|
||||
assert_eq!(super::map_filter_level(crate::LogFilterLevel::Error), tracing_subscriber::filter::LevelFilter::ERROR);
|
||||
assert_eq!(super::map_filter_level(crate::LogFilterLevel::Warn), tracing_subscriber::filter::LevelFilter::WARN);
|
||||
assert_eq!(super::map_filter_level(crate::LogFilterLevel::Info), tracing_subscriber::filter::LevelFilter::INFO);
|
||||
assert_eq!(super::map_filter_level(crate::LogFilterLevel::Debug), tracing_subscriber::filter::LevelFilter::DEBUG);
|
||||
assert_eq!(super::map_filter_level(crate::LogFilterLevel::Trace), tracing_subscriber::filter::LevelFilter::TRACE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn takeover_filter_silences_external_targets_and_applies_ksp_overrides() {
|
||||
let settings = crate::LoggingSettings::new(
|
||||
crate::LogFilterLevel::Info,
|
||||
crate::SpanEvents::Off,
|
||||
std::option::Option::Some(crate::ConsoleSettings::stdout()),
|
||||
std::option::Option::None,
|
||||
)
|
||||
.with_target_filter(crate::TargetFilter::new("ksp-logging-lib", crate::LogFilterLevel::Trace));
|
||||
let filter = super::build_target_filter(&settings);
|
||||
assert!(filter.would_enable("ksp-store-lib", &tracing::Level::INFO));
|
||||
assert!(!filter.would_enable("ksp-store-lib", &tracing::Level::DEBUG));
|
||||
assert!(filter.would_enable("ksp-logging-lib", &tracing::Level::TRACE));
|
||||
assert!(!filter.would_enable("sqlx", &tracing::Level::ERROR));
|
||||
assert!(!filter.would_enable("hyper", &tracing::Level::ERROR));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn span_event_mapping_supports_disabled_timing_and_full_lifecycle() {
|
||||
assert_eq!(super::map_span_events(crate::SpanEvents::Off), tracing_subscriber::fmt::format::FmtSpan::NONE);
|
||||
assert_eq!(
|
||||
super::map_span_events(crate::SpanEvents::NewAndClose),
|
||||
tracing_subscriber::fmt::format::FmtSpan::NEW | tracing_subscriber::fmt::format::FmtSpan::CLOSE,
|
||||
);
|
||||
assert_eq!(super::map_span_events(crate::SpanEvents::Full), tracing_subscriber::fmt::format::FmtSpan::FULL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_output_is_rejected_until_file_runtime_is_introduced() {
|
||||
let settings = crate::LoggingSettings::new(
|
||||
crate::LogFilterLevel::Info,
|
||||
crate::SpanEvents::Off,
|
||||
std::option::Option::None,
|
||||
std::option::Option::Some(crate::FileSettings::new("logs", "ksp", crate::FileRotation::Daily)),
|
||||
);
|
||||
let result = super::prepare_runtime_layers(&settings);
|
||||
assert!(result.is_err());
|
||||
let error = match result {
|
||||
std::result::Result::Ok(_) => return,
|
||||
std::result::Result::Err(error) => error,
|
||||
};
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_SETTINGS);
|
||||
}
|
||||
305
deltas/0.1.2/pre.003.md
Normal file
305
deltas/0.1.2/pre.003.md
Normal file
@@ -0,0 +1,305 @@
|
||||
<!-- file: deltas/0.1.2/pre.003.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta 0.1.2-pre.003
|
||||
|
||||
## Base requise
|
||||
|
||||
Livraison précédente validée :
|
||||
|
||||
```text
|
||||
0.1.2-pre.002-fix.001
|
||||
```
|
||||
|
||||
La base de développement validée porte :
|
||||
|
||||
```text
|
||||
workspace.package.version = "0.1.2-pre.2.fix.1"
|
||||
Cargo.toml header version = 28
|
||||
```
|
||||
|
||||
Les validations remontées avant l'ouverture de cette tranche sont propres :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
## Objectif
|
||||
|
||||
Introduire le runtime subscriber de Logging sans encore ouvrir le backend fichier/non bloquant :
|
||||
|
||||
- ajouter `tracing-subscriber` avec la feature minimale `fmt` ;
|
||||
- installer une seule fois le subscriber global KSP ;
|
||||
- appliquer le takeover KSP et rendre silencieux les targets externes par défaut ;
|
||||
- mapper `LogFilterLevel` vers `LevelFilter` ;
|
||||
- appliquer un niveau KSP global puis les overrides par préfixe de target ;
|
||||
- introduire une première couche console stdout/stderr ;
|
||||
- intégrer les événements de lifecycle des spans `Off`, `NewAndClose` et `Full` ;
|
||||
- introduire `LoggingGuard`, `initialize()` et `reinitialize()` ;
|
||||
- permettre un démarrage sans sink puis une activation à chaud ;
|
||||
- vérifier le hot reload sans second subscriber global.
|
||||
|
||||
La console reste volontairement synchrone dans cette tranche intermédiaire. `pre.004` la remplacera par un writer `tracing-appender` non bloquant et ajoutera fichier, guards, dropped-line counters et stripping ANSI avant toute stabilisation de `0.1.2`.
|
||||
|
||||
## Version Cargo
|
||||
|
||||
`workspace.package.version` passe de :
|
||||
|
||||
```text
|
||||
0.1.2-pre.2.fix.1
|
||||
```
|
||||
|
||||
à :
|
||||
|
||||
```text
|
||||
0.1.2-pre.3
|
||||
```
|
||||
|
||||
L'identifiant de livraison est :
|
||||
|
||||
```text
|
||||
0.1.2-pre.003
|
||||
```
|
||||
|
||||
Le header du `Cargo.toml` racine passe de version 28 à 29.
|
||||
|
||||
## Dépendance `tracing-subscriber`
|
||||
|
||||
L'audit du 2026-08-14 confirme `tracing-subscriber 0.3.23` dans la génération `^0.3`.
|
||||
|
||||
La dépendance est centralisée sous `[workspace.dependencies]` :
|
||||
|
||||
```toml
|
||||
tracing-subscriber = { version = "^0.3", default-features = false, features = ["fmt"] }
|
||||
```
|
||||
|
||||
`ksp-logging-lib` la consomme avec :
|
||||
|
||||
```toml
|
||||
tracing-subscriber.workspace = true
|
||||
```
|
||||
|
||||
La feature `fmt` fournit le formatter et entraîne les capacités `registry`/`std` nécessaires à la composition retenue. Ne sont pas activés par anticipation :
|
||||
|
||||
- `env-filter` ;
|
||||
- `ansi` ;
|
||||
- `tracing-log` ;
|
||||
- `json` ;
|
||||
- `time` ;
|
||||
- `chrono` ;
|
||||
- `parking_lot`.
|
||||
|
||||
`tracing-appender` reste absent jusqu'à `pre.004`.
|
||||
|
||||
## Takeover et filtering
|
||||
|
||||
Le runtime utilise `tracing_subscriber::filter::Targets`.
|
||||
|
||||
La construction est conceptuellement :
|
||||
|
||||
```text
|
||||
default unmatched targets = OFF
|
||||
ksp-* = LoggingSettings.default_filter
|
||||
target overrides = TargetFilter entries
|
||||
```
|
||||
|
||||
Conséquences :
|
||||
|
||||
- un événement `sqlx`, `hyper`, `rustls` ou autre target externe reste silencieux même à `ERROR` tant qu'aucune couche KSP ne le réémet explicitement ;
|
||||
- les crates KSP utilisent leur nom Cargo comme target ;
|
||||
- `ksp-logging-lib`, `ksp-store-lib`, etc. suivent le niveau global KSP ;
|
||||
- un `TargetFilter` plus spécifique peut relever ou abaisser le niveau d'une crate KSP donnée ;
|
||||
- aucune chaîne `RUST_LOG` ou `EnvFilter` n'est introduite.
|
||||
|
||||
## Console initiale
|
||||
|
||||
`ConsoleSettings::stdout()` et `ConsoleSettings::stderr()` construisent une couche `fmt` avec :
|
||||
|
||||
- target affiché ;
|
||||
- ANSI explicitement désactivé ;
|
||||
- lifecycle de span selon `SpanEvents` ;
|
||||
- filtering KSP `Targets`.
|
||||
|
||||
Cette couche utilise encore directement `std::io::stdout` / `std::io::stderr`. Ce writer synchrone est uniquement la fondation de `pre.003`; il n'est pas le contrat final de la release.
|
||||
|
||||
## Spans runtime
|
||||
|
||||
Le mapping retenu est :
|
||||
|
||||
```text
|
||||
SpanEvents::Off -> FmtSpan::NONE
|
||||
SpanEvents::NewAndClose -> FmtSpan::NEW | FmtSpan::CLOSE
|
||||
SpanEvents::Full -> FmtSpan::FULL
|
||||
```
|
||||
|
||||
`NewAndClose` active ainsi la surface nécessaire aux diagnostics de début/fin et de temps busy/idle fournis par le formatter sans obliger les consumers à utiliser directement `tracing-subscriber`.
|
||||
|
||||
## Subscriber global
|
||||
|
||||
La nouvelle API publique est :
|
||||
|
||||
```text
|
||||
ksp_logging_lib::LoggingGuard
|
||||
ksp_logging_lib::initialize(&LoggingSettings) -> Result<LoggingGuard>
|
||||
ksp_logging_lib::reinitialize(&mut LoggingGuard, &LoggingSettings) -> Result<()>
|
||||
```
|
||||
|
||||
`initialize()` :
|
||||
|
||||
1. valide/prépare les layers ;
|
||||
2. crée une unique infrastructure `reload::Layer` ;
|
||||
3. installe le subscriber global avec l'API fallible `tracing::subscriber::set_global_default` ;
|
||||
4. retourne un `LoggingGuard` possédant le handle de reload et les settings actifs.
|
||||
|
||||
Une seconde installation globale retourne :
|
||||
|
||||
```text
|
||||
logging.already_initialized
|
||||
```
|
||||
|
||||
La cause `SetGlobalDefaultError` est conservée comme `source` Core.
|
||||
|
||||
## Hot reload
|
||||
|
||||
La composition interne retenue est :
|
||||
|
||||
```text
|
||||
Registry
|
||||
-> reload::Layer
|
||||
-> Vec<Box<dyn Layer<Registry> + Send + Sync>>
|
||||
```
|
||||
|
||||
Le `Vec` peut être vide. Cela permet :
|
||||
|
||||
```text
|
||||
initialize(no sink)
|
||||
-> subscriber global installé mais silencieux
|
||||
|
||||
reinitialize(console enabled)
|
||||
-> console activée sans second subscriber global
|
||||
```
|
||||
|
||||
Le choix d'un `Vec` de layers boxed prépare directement `pre.004`, qui pourra ajouter ou retirer console/fichier sans changer la surface publique de reload.
|
||||
|
||||
`reinitialize()` prépare d'abord complètement la nouvelle représentation. Une erreur de validation/préparation retourne avant le swap et conserve :
|
||||
|
||||
- les settings actifs du `LoggingGuard` ;
|
||||
- les layers actuellement installés ;
|
||||
- le comportement de filtering en cours.
|
||||
|
||||
Une erreur effective du handle `reload` retourne :
|
||||
|
||||
```text
|
||||
logging.reload_failed
|
||||
```
|
||||
|
||||
et conserve sa cause externe via le contrat `source` Core.
|
||||
|
||||
## File settings pendant `pre.003`
|
||||
|
||||
`FileSettings` reste dans la surface publique définie par `pre.002`, mais le backend fichier n'est pas encore construit dans cette tranche.
|
||||
|
||||
`initialize()` / `reinitialize()` refusent donc temporairement une configuration avec `file = Some(...)` avec `logging.invalid_settings` et contexte `field = file` au lieu d'ignorer silencieusement la demande.
|
||||
|
||||
Cette restriction transitoire disparaîtra lorsque le backend fichier réel sera introduit en `pre.004`.
|
||||
|
||||
## Erreurs ajoutées
|
||||
|
||||
```text
|
||||
logging.already_initialized
|
||||
logging.reload_failed
|
||||
```
|
||||
|
||||
Elles s'ajoutent à :
|
||||
|
||||
```text
|
||||
logging.invalid_settings
|
||||
```
|
||||
|
||||
Aucune connaissance Logging n'est ajoutée à Core.
|
||||
|
||||
## Tests ajoutés
|
||||
|
||||
### Unitaires runtime
|
||||
|
||||
- mapping complet des niveaux KSP ;
|
||||
- silence des targets externes ;
|
||||
- default KSP `Info` ;
|
||||
- override `ksp-logging-lib = Trace` ;
|
||||
- mapping des événements de span ;
|
||||
- rejet temporaire du backend fichier avant `pre.004`.
|
||||
|
||||
### Intégration runtime global
|
||||
|
||||
Un seul test global dans sa crate de test dédiée vérifie :
|
||||
|
||||
1. `initialize()` avec aucun sink ;
|
||||
2. absence d'admission des callsites tant que Logging est désactivé ;
|
||||
3. `reinitialize()` avec console active ;
|
||||
4. activation `Trace` de `ksp-logging-lib` par override ;
|
||||
5. maintien de `ksp-store-lib` à `Info` ;
|
||||
6. maintien de `sqlx` à `Off` même pour `Error` ;
|
||||
7. échec d'un reload demandant le backend fichier non encore disponible ;
|
||||
8. conservation des anciens settings/filtering après cet échec ;
|
||||
9. refus d'un deuxième `initialize()`.
|
||||
|
||||
Le changement de filtering est observé via des fonctions contenant des callsites `tracing::enabled!` stables, afin de vérifier que le reload invalide correctement l'intérêt mis en cache.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
- `crates/ksp-logging-lib/src/runtime.rs`
|
||||
- `crates/ksp-logging-lib/unit_tests/runtime.rs`
|
||||
- `crates/ksp-logging-lib/tests/runtime.rs`
|
||||
- `deltas/0.1.2/pre.003.md`
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
- `Cargo.toml`
|
||||
- `crates/ksp-logging-lib/Cargo.toml`
|
||||
- `crates/ksp-logging-lib/src/error.rs`
|
||||
- `crates/ksp-logging-lib/src/lib.rs`
|
||||
- `crates/ksp-logging-lib/tests/public_api.rs`
|
||||
- `docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md`
|
||||
|
||||
## Fichiers supprimés
|
||||
|
||||
Aucun.
|
||||
|
||||
## Validations exécutées pendant la préparation
|
||||
|
||||
- revérification documentaire de `tracing-subscriber 0.3.23` et de ses features ;
|
||||
- contrôle TOML des manifests ;
|
||||
- contrôle des headers `file:` / `version:` ;
|
||||
- contrôle de la centralisation de `tracing-subscriber` sous `[workspace.dependencies]` ;
|
||||
- contrôle que `tracing-appender` reste absent ;
|
||||
- contrôle que le code production ajouté n'utilise ni `unwrap`, ni `expect`, ni `panic`, ni opérateur `?`, ni `unsafe` ;
|
||||
- contrôle que les usages directs de la stack tracing restent dans `ksp-logging-lib` ;
|
||||
- contrôle du contenu du delta contre la base reconstruite `0.1.2-pre.2.fix.1`.
|
||||
|
||||
## Validations à exécuter dans le workspace
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets
|
||||
cargo test --workspace
|
||||
cargo tree -p ksp-logging-lib
|
||||
cargo tree -p ksp-logging-lib -d
|
||||
cargo tree -p ksp-logging-lib -e features
|
||||
```
|
||||
|
||||
Aucune validation Cargo non exécutable dans l'environnement de préparation n'est déclarée réussie.
|
||||
|
||||
## Suite
|
||||
|
||||
Après validation de `pre.003`, passer à `0.1.2-pre.004` :
|
||||
|
||||
- `tracing-appender` ;
|
||||
- console non bloquante ;
|
||||
- fichier Never/Hourly/Daily ;
|
||||
- `WorkerGuard` / `ErrorCounter` ;
|
||||
- stripping ANSI fichier ;
|
||||
- hot reload des sinks non bloquants et de leurs guards.
|
||||
@@ -1,13 +1,13 @@
|
||||
<!-- file: docs/plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# 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` puis concrétisé par la première surface fonctionnelle de `0.1.2-pre.002`.
|
||||
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` crée `ksp-logging-lib`, ses settings runtime, sa façade d'événements/spans et son instrumentation async. Le subscriber runtime, le takeover effectif, les sorties et le hot reload restent réservés aux tranches suivantes.
|
||||
`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
|
||||
|
||||
@@ -147,9 +147,15 @@ tracing-appender = { version = "^0.2", default-features = false }
|
||||
|
||||
### Politique d'ajout au manifest
|
||||
|
||||
`pre.002` ajoute uniquement `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.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`.
|
||||
|
||||
`tracing-subscriber` et `tracing-appender` restent absents jusqu'aux prereleases qui utilisent effectivement leurs APIs.
|
||||
`pre.003` revérifie `tracing-subscriber 0.3.23` et ajoute :
|
||||
|
||||
```toml
|
||||
tracing-subscriber = { version = "^0.3", default-features = false, features = ["fmt"] }
|
||||
```
|
||||
|
||||
La feature `fmt` apporte `registry` et `std`, nécessaires à la composition des layers, `Targets`, `reload` et 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 :
|
||||
|
||||
@@ -920,15 +926,24 @@ Objectifs :
|
||||
|
||||
### `0.1.2-pre.003` — subscriber + takeover + console + reload foundation
|
||||
|
||||
Objectifs :
|
||||
Statut : implémenté dans la tranche `pre.003`, sous réserve des validations Cargo à exécuter dans l'environnement de développement.
|
||||
|
||||
- revérifier puis ajouter `tracing-subscriber` ;
|
||||
- implémenter le mapping `LogFilterLevel` ;
|
||||
- implémenter le silence externe + default KSP + overrides ;
|
||||
- implémenter la couche console ;
|
||||
- installer le subscriber global avec API fallible ;
|
||||
- introduire l'infrastructure reloadable et les premiers tests de hot reload ;
|
||||
- intégrer les événements lifecycle de spans `Off/NewAndClose/Full`.
|
||||
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
|
||||
|
||||
@@ -1016,17 +1031,19 @@ La release peut être stabilisée lorsque :
|
||||
- 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.002`
|
||||
## 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.
|
||||
|
||||
Restent à confirmer par les prereleases runtime sans remettre en cause ce contrat :
|
||||
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.
|
||||
|
||||
1. la composition interne la moins coûteuse pour le hot reload (reload de filters/layers ciblés ou routing dynamique KSP), tout en conservant un seul subscriber global ;
|
||||
2. l'API publique exacte d'observation des dropped lines ;
|
||||
3. le détail visuel exact du formatter humain, sans transformer sa ponctuation en contrat public.
|
||||
Restent à confirmer par les prereleases suivantes sans remettre en cause ce contrat :
|
||||
|
||||
La prochaine action après validation de `pre.002` est `0.1.2-pre.003` : subscriber, takeover, filtering, console initiale et fondation du hot reload.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user