v0.1.3-pre.006

This commit is contained in:
2026-08-15 20:40:38 +02:00
parent 3a479e4f43
commit b7323fe961
15 changed files with 629 additions and 106 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-logging-lib/README.md -->
<!-- version: 4 -->
<!-- version: 5 -->
# ksp-logging-lib
@@ -14,8 +14,8 @@ La crate possède :
- `LoggingSettings`, la console explicite et les settings fichier indépendants de Config ;
- les formats runtime `Human/Compact/Pretty/Json` ;
- zéro, un ou plusieurs outputs fichier actifs simultanément, identifiés par `output_id` unique ;
- le routing par output sur niveau et target KSP ;
- le contrat de filtre `domain`, dont l'activation runtime structurée est volontairement séparée dans la tranche suivante ;
- le routing par output sur niveau, target KSP et champ structuré `domain` ;
- l'héritage du `domain` effectif à travers les spans, avec possibilité pour un event ou un span enfant de le remplacer explicitement ;
- l'installation unique du subscriber global ;
- le hot reload via `reinitialize` sans second subscriber global ;
- le takeover des logs : les targets externes sont silencieux par défaut ;
@@ -28,7 +28,7 @@ L'API async de production reste indépendante de tout executor. Tokio est utilis
## Routing `domain`
`OutputFilter` représente déjà :
`OutputFilter` applique désormais les trois dimensions :
```text
level
@@ -36,9 +36,17 @@ targets[]
domains[]
```
`0.1.3-pre.005` active réellement `level` et `targets[]`. Un output actif dont `domains[]` est différent de `[*]` est refusé explicitement par `initialize/reinitialize` jusqu'à la tranche dédiée au routing structuré par champ `domain`.
Le `domain` reste un champ structuré distinct du target. Sa résolution runtime suit ces règles :
Ce refus évite d'assimiler artificiellement `domain` au target ou d'ignorer silencieusement la dimension demandée.
- le `domain` porté directement par un event est prioritaire ;
- sinon l'event hérite du `domain` effectif de son span ;
- un span qui porte son propre `domain` remplace celui de son parent ;
- un span sans `domain` hérite de celui de son parent au moment de sa création ;
- les événements de lifecycle de span utilisent le `domain` effectif du span concerné ;
- un selector `domains = ["*"]` accepte aussi les événements sans `domain` ;
- un selector nommé correspond par préfixe et ne sélectionne pas un événement sans `domain`.
Le routing `domain` est appliqué en plus du niveau et du target de l'output. Il ne modifie ni le target propriétaire KSP ni les champs formatés.
## Frontières

View File

@@ -1,20 +1,17 @@
<!-- file: crates/ksp-logging-lib/TODO.md -->
<!-- version: 4 -->
<!-- version: 5 -->
# TODO ksp-logging-lib
## À fermer pendant `0.1.3`
`0.1.3-pre.005` active le runtime multi-sink pour les dimensions disponibles directement dans les métadonnées `tracing` : niveau et target.
Les trois tranches Logging nécessaires à la fondation Config sont maintenant couvertes fonctionnellement :
La tranche Logging suivante doit encore :
- `pre.004` : contrats/settings multi-output ;
- `pre.005` : runtime multi-sink, formats et routing level/target ;
- `pre.006` : routing structuré `domain`, héritage de spans et lifecycle.
- appliquer réellement `OutputFilter.domains[]` à partir du champ structuré `domain` ;
- définir l'héritage du domain pour les events émis dans un span lorsqu'ils ne portent pas eux-mêmes le champ ;
- conserver une sémantique cohérente pour les événements de lifecycle de span ;
- couvrir ce routing par des tests dédiés avant de figer `std.logging.schema.json`.
Tant que cette tranche n'est pas livrée, un output actif avec `domains[] != ["*"]` est refusé explicitement par `initialize/reinitialize`.
Avant le gel de `std.logging.schema.json`, il reste uniquement à faire valider `pre.006` par les commandes workspace usuelles. Les travaux suivants de `0.1.3` reviennent ensuite à `ksp-config-lib`.
## Capacités désormais actives
@@ -22,12 +19,13 @@ Le runtime supporte maintenant :
- zéro, un ou plusieurs outputs fichier simultanés ;
- une console indépendante ;
- routing par niveau et target pour chaque output ;
- routing par niveau, target et `domain` structuré pour chaque output ;
- `Human`, `Compact`, `Pretty` et `Json` ;
- ANSI console configurable et fichiers persistants sans ANSI ;
- guards non bloquants indépendants ;
- compteurs agrégés et compteurs cumulatifs par `output_id` fichier ;
- hot reload transactionnel du groupe de sinks.
- hot reload transactionnel du groupe de sinks ;
- héritage du `domain` effectif pour les events/spans et lifecycle de spans.
## Capacités différées

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-logging-lib/USAGE.md -->
<!-- version: 4 -->
<!-- version: 5 -->
# Utilisation de ksp-logging-lib
@@ -46,18 +46,20 @@ Une sortie fichier possède un `output_id` stable et unique dans `LoggingSetting
Les fichiers persistants interdisent `ansi = true`.
## Runtime multi-output de `pre.005`
## Runtime multi-output et routing `domain`
`0.1.3-pre.005` active réellement :
`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 et target pour chaque output ;
- 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 routing `domain` est volontairement séparé. `OutputFilter` continue de représenter `domains[]`, mais un output actif dont cette dimension est différente de `["*"]` est refusé par `initialize/reinitialize` jusqu'à la tranche suivante. La configuration n'est donc jamais acceptée puis partiellement ignorée.
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.
@@ -92,7 +94,7 @@ Une configuration sans output actif est valide et installe une infrastructure in
## Construction d'un runtime multi-output
Le runtime peut maintenant activer plusieurs sorties ayant des formats et filtres metadata distincts :
Le runtime peut activer plusieurs sorties ayant des formats et filtres niveau/target/domain distincts :
```rust
let console = ksp_logging_lib::ConsoleSettings::new(
@@ -131,7 +133,19 @@ let settings = ksp_logging_lib::LoggingSettings::new(
let validation = settings.validate();
```
`validate()` vérifie le contrat structurel. `initialize/reinitialize` appliquent le niveau et les targets par output ; les domains spécifiques restent refusés jusqu'à la tranche de routing structuré.
`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

View File

@@ -0,0 +1,181 @@
// file: crates/ksp-logging-lib/src/domain.rs
// version: 1
std::thread_local! {
static CURRENT_DOMAIN: std::cell::RefCell<std::option::Option<std::string::String>> = const { std::cell::RefCell::new(std::option::Option::None) };
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct SpanDomain {
value: std::option::Option<std::string::String>,
}
#[derive(Default)]
struct DomainVisitor {
value: std::option::Option<std::string::String>,
}
impl tracing::field::Visit for DomainVisitor {
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
if field.name() == "domain" {
self.value = std::option::Option::Some(value.to_string());
}
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "domain" {
self.value = std::option::Option::Some(format!("{value:?}"));
}
}
}
pub(crate) struct DomainContextLayer;
impl DomainContextLayer {
pub(crate) const fn new() -> Self {
return Self;
}
}
impl<S> tracing_subscriber::Layer<S> for DomainContextLayer
where
S: tracing::Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
fn on_new_span(&self, attrs: &tracing::span::Attributes<'_>, id: &tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
let mut visitor = DomainVisitor::default();
attrs.record(&mut visitor);
let effective_domain = match visitor.value {
std::option::Option::Some(domain) => std::option::Option::Some(domain),
std::option::Option::None => span_parent_domain(id, &ctx),
};
if let std::option::Option::Some(span) = ctx.span(id) {
span.extensions_mut().insert(SpanDomain { value: effective_domain.clone() });
}
set_current_domain(effective_domain.as_deref());
}
fn on_record(&self, id: &tracing::span::Id, values: &tracing::span::Record<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
let mut visitor = DomainVisitor::default();
values.record(&mut visitor);
if let std::option::Option::Some(domain) = visitor.value {
if let std::option::Option::Some(span) = ctx.span(id) {
let mut extensions = span.extensions_mut();
if let std::option::Option::Some(stored) = extensions.get_mut::<SpanDomain>() {
stored.value = std::option::Option::Some(domain.clone());
} else {
extensions.insert(SpanDomain { value: std::option::Option::Some(domain.clone()) });
}
}
set_current_domain(std::option::Option::Some(domain.as_str()));
return;
}
let domain = span_domain(id, &ctx);
set_current_domain(domain.as_deref());
}
fn on_event(&self, event: &tracing::Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
let mut visitor = DomainVisitor::default();
event.record(&mut visitor);
let effective_domain = match visitor.value {
std::option::Option::Some(domain) => std::option::Option::Some(domain),
std::option::Option::None => event_parent_domain(event, &ctx),
};
set_current_domain(effective_domain.as_deref());
}
fn on_enter(&self, id: &tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
let domain = span_domain(id, &ctx);
set_current_domain(domain.as_deref());
}
fn on_exit(&self, id: &tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
let domain = span_domain(id, &ctx);
set_current_domain(domain.as_deref());
}
fn on_close(&self, id: tracing::span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
let domain = span_domain(&id, &ctx);
set_current_domain(domain.as_deref());
}
}
pub(crate) fn current_domain_matches(selectors: &[std::string::String]) -> bool {
if let [selector] = selectors
&& selector == "*"
{
return true;
}
return CURRENT_DOMAIN.with(|current| -> bool {
let borrow_result = current.try_borrow();
let current = match borrow_result {
std::result::Result::Ok(current) => current,
std::result::Result::Err(_) => return false,
};
let domain = match current.as_ref() {
std::option::Option::Some(domain) => domain,
std::option::Option::None => return false,
};
return selectors.iter().any(|selector| -> bool {
return domain.starts_with(selector.as_str());
});
});
}
fn set_current_domain(domain: std::option::Option<&str>) {
CURRENT_DOMAIN.with(|current| {
let borrow_result = current.try_borrow_mut();
if let std::result::Result::Ok(mut current) = borrow_result {
*current = domain.map(std::borrow::ToOwned::to_owned);
}
});
}
fn span_parent_domain<S>(id: &tracing::span::Id, ctx: &tracing_subscriber::layer::Context<'_, S>) -> std::option::Option<std::string::String>
where
S: tracing::Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
let span = match ctx.span(id) {
std::option::Option::Some(span) => span,
std::option::Option::None => return std::option::Option::None,
};
let parent = match span.parent() {
std::option::Option::Some(parent) => parent,
std::option::Option::None => return std::option::Option::None,
};
let extensions = parent.extensions();
return extensions.get::<SpanDomain>().and_then(|domain| -> std::option::Option<std::string::String> {
return domain.value.clone();
});
}
fn span_domain<S>(id: &tracing::span::Id, ctx: &tracing_subscriber::layer::Context<'_, S>) -> std::option::Option<std::string::String>
where
S: tracing::Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
let span = match ctx.span(id) {
std::option::Option::Some(span) => span,
std::option::Option::None => return std::option::Option::None,
};
let extensions = span.extensions();
return extensions.get::<SpanDomain>().and_then(|domain| -> std::option::Option<std::string::String> {
return domain.value.clone();
});
}
fn event_parent_domain<S>(event: &tracing::Event<'_>, ctx: &tracing_subscriber::layer::Context<'_, S>) -> std::option::Option<std::string::String>
where
S: tracing::Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
let parent = match ctx.event_span(event) {
std::option::Option::Some(parent) => parent,
std::option::Option::None => return std::option::Option::None,
};
let extensions = parent.extensions();
return extensions.get::<SpanDomain>().and_then(|domain| -> std::option::Option<std::string::String> {
return domain.value.clone();
});
}
#[cfg(test)]
#[path = "../unit_tests/domain.rs"]
mod tests;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/lib.rs
// version: 6
// version: 7
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
@@ -7,10 +7,11 @@
//! 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. The crate owns the single global subscriber, KSP takeover filtering, hot reload and non-blocking outputs. `0.1.3-pre.005` activates
//! multiple simultaneous file outputs, per-output level/target routing, selectable formats, console ANSI and per-file dropped-line accounting. Structured
//! `domain` routing remains explicitly rejected until its dedicated runtime tranche so field-based routing is never approximated or ignored silently.
//! `tracing` stack. The crate owns the single global subscriber, KSP takeover filtering, hot reload and non-blocking outputs. `0.1.3-pre.006` supports
//! multiple simultaneous outputs with per-output level/target/domain routing, selectable formats, console ANSI and per-file dropped-line accounting. Structured
//! `domain` routing remains distinct from targets and follows explicit event domains or inherited span domains.
mod domain;
mod error;
mod macros;
mod runtime;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/runtime.rs
// version: 10
// version: 11
use tracing_subscriber::Layer; // rust-rules: trait-import
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
@@ -219,10 +219,6 @@ fn prepare_runtime(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<Pr
if let std::option::Option::Some(error) = validation_error {
return std::result::Result::Err(error);
}
let runtime_validation = validate_current_runtime_capabilities(settings);
if let std::result::Result::Err(error) = runtime_validation {
return std::result::Result::Err(error);
}
let enabled_console = settings.console().filter(|console| -> bool {
return console.enabled();
});
@@ -248,38 +244,11 @@ fn prepare_runtime(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<Pr
if output_layers.is_empty() {
return std::result::Result::Ok(PreparedRuntime { layers: RuntimeLayers::new(), outputs });
}
output_layers.insert(0, crate::domain::DomainContextLayer::new().boxed());
let takeover_layer = build_target_filter(settings).and_then(output_layers).boxed();
return std::result::Result::Ok(PreparedRuntime { layers: vec![takeover_layer], outputs });
}
fn validate_current_runtime_capabilities(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<()> {
if let std::option::Option::Some(console) = settings.console()
&& console.enabled()
&& !domains_are_unrestricted(console.filter())
{
return runtime_capability_error("domain routing requires the dedicated structured-domain runtime tranche");
}
for file in settings.files() {
if file.enabled() && !domains_are_unrestricted(file.filter()) {
return runtime_capability_error("domain routing requires the dedicated structured-domain runtime tranche");
}
}
return std::result::Result::Ok(());
}
fn domains_are_unrestricted(filter: &crate::OutputFilter) -> bool {
return match filter.domains() {
[selector] => selector == "*",
_ => false,
};
}
fn runtime_capability_error(message: &str) -> ksp_core_lib::Result<()> {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, message).with_context("runtime_contract", "metadata-routing-before-domain-routing"),
);
}
fn build_console_output(console: &crate::ConsoleSettings, settings: &crate::LoggingSettings) -> PreparedOutput {
return match console.output() {
crate::ConsoleOutput::Stdout => build_non_blocking_output(

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/src/writer.rs
// version: 2
// version: 3
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum StripAnsiState {
@@ -158,9 +158,12 @@ fn metadata_matches_filter(metadata: &tracing::Metadata<'_>, filter: &crate::Out
if !level_is_enabled(metadata.level(), filter.level()) {
return false;
}
return filter.targets().iter().any(|selector| -> bool {
if !filter.targets().iter().any(|selector| -> bool {
return selector == "*" || metadata.target().starts_with(selector.as_str());
});
}) {
return false;
}
return crate::domain::current_domain_matches(filter.domains());
}
fn level_is_enabled(level: &tracing::Level, filter: crate::LogFilterLevel) -> bool {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/tests/runtime.rs
// version: 7
// version: 8
//! Integration tests for global initialization, takeover filtering, non-blocking outputs and hot reload.
@@ -173,21 +173,6 @@ fn global_runtime_supports_takeover_non_blocking_outputs_hot_reload_and_single_i
assert!(logging_trace_enabled());
assert!(!external_error_enabled());
exercise_concurrent_reload(&mut guard, &disabled);
let domain_routed = ksp_logging_lib::LoggingSettings::new(
ksp_logging_lib::LogFilterLevel::Info,
ksp_logging_lib::SpanEvents::Off,
std::option::Option::Some(ksp_logging_lib::ConsoleSettings::new(
true,
ksp_logging_lib::ConsoleOutput::Stderr,
false,
ksp_logging_lib::LogFormat::Human,
ksp_logging_lib::OutputFilter::new(ksp_logging_lib::LogFilterLevel::Info, std::vec!["*".to_string()], std::vec!["logging".to_string()]),
)),
std::vec::Vec::new(),
);
let domain_reload = ksp_logging_lib::reinitialize(&mut guard, &domain_routed);
assert!(domain_reload.is_err());
assert_ne!(guard.settings(), &domain_routed);
let human_directory = root.join("human");
let compact_directory = root.join("compact");
let pretty_directory = root.join("pretty");
@@ -272,6 +257,119 @@ fn global_runtime_supports_takeover_non_blocking_outputs_hot_reload_and_single_i
assert_eq!(guard.dropped_file_lines("file.pretty.error"), std::option::Option::Some(0));
assert_eq!(guard.dropped_file_lines("file.json.logging"), std::option::Option::Some(0));
assert_eq!(guard.dropped_file_lines("file.unknown"), std::option::Option::None);
let domain_logging_directory = root.join("domain-logging");
let domain_store_directory = root.join("domain-store");
let domain_any_directory = root.join("domain-any");
let domain_settings = ksp_logging_lib::LoggingSettings::new(
ksp_logging_lib::LogFilterLevel::Trace,
ksp_logging_lib::SpanEvents::Full,
std::option::Option::None,
std::vec![
ksp_logging_lib::FileSettings::new(
"file.domain.logging",
true,
domain_logging_directory.as_path(),
"logging.log",
ksp_logging_lib::FileRotation::Never,
ksp_logging_lib::LogFormat::Human,
ksp_logging_lib::OutputFilter::new(
ksp_logging_lib::LogFilterLevel::Trace,
std::vec![LOGGING_TARGET.to_string()],
std::vec!["logging".to_string()],
),
),
ksp_logging_lib::FileSettings::new(
"file.domain.store",
true,
domain_store_directory.as_path(),
"store.log",
ksp_logging_lib::FileRotation::Never,
ksp_logging_lib::LogFormat::Human,
ksp_logging_lib::OutputFilter::new(
ksp_logging_lib::LogFilterLevel::Trace,
std::vec![LOGGING_TARGET.to_string()],
std::vec!["store".to_string()],
),
),
ksp_logging_lib::FileSettings::new(
"file.domain.any",
true,
domain_any_directory.as_path(),
"any.log",
ksp_logging_lib::FileRotation::Never,
ksp_logging_lib::LogFormat::Human,
ksp_logging_lib::OutputFilter::new(ksp_logging_lib::LogFilterLevel::Trace, std::vec![LOGGING_TARGET.to_string()], std::vec!["*".to_string()],),
),
],
);
let domain_reload = ksp_logging_lib::reinitialize(&mut guard, &domain_settings);
assert!(domain_reload.is_ok());
ksp_logging_lib::info!(target: LOGGING_TARGET, domain = "logging.runtime", "direct logging domain marker");
ksp_logging_lib::info!(target: LOGGING_TARGET, domain = "store", "direct store domain marker");
ksp_logging_lib::info!(target: LOGGING_TARGET, "undomained marker");
let logging_lifecycle = ksp_logging_lib::info_span!(target: LOGGING_TARGET, "logging_lifecycle_span", domain = "logging");
logging_lifecycle.in_scope(|| {
return;
});
let store_lifecycle = ksp_logging_lib::info_span!(target: LOGGING_TARGET, "store_lifecycle_span", domain = "store");
store_lifecycle.in_scope(|| {
return;
});
let logging_parent = ksp_logging_lib::info_span!(target: LOGGING_TARGET, "logging_parent_span", domain = "logging");
logging_parent.in_scope(|| {
ksp_logging_lib::info!(target: LOGGING_TARGET, "inherited logging marker");
ksp_logging_lib::info!(target: LOGGING_TARGET, domain = "store", "event override store marker");
let inherited_child = ksp_logging_lib::info_span!(target: LOGGING_TARGET, "inherited_child_span");
inherited_child.in_scope(|| {
ksp_logging_lib::info!(target: LOGGING_TARGET, "child inherited logging marker");
});
let store_child = ksp_logging_lib::info_span!(target: LOGGING_TARGET, "store_child_span", domain = "store");
store_child.in_scope(|| {
ksp_logging_lib::info!(target: LOGGING_TARGET, "child explicit store marker");
});
});
let store_parent = ksp_logging_lib::info_span!(target: LOGGING_TARGET, "store_parent_span", domain = "store");
store_parent.in_scope(|| {
ksp_logging_lib::info!(target: LOGGING_TARGET, "inherited store marker");
});
let disable_after_domain = ksp_logging_lib::reinitialize(&mut guard, &disabled);
assert!(disable_after_domain.is_ok());
let domain_logging_text = read_directory_text(domain_logging_directory.as_path());
let domain_store_text = read_directory_text(domain_store_directory.as_path());
let domain_any_text = read_directory_text(domain_any_directory.as_path());
assert!(domain_logging_text.contains("direct logging domain marker"));
assert!(domain_logging_text.contains("inherited logging marker"));
assert!(domain_logging_text.contains("child inherited logging marker"));
assert!(domain_logging_text.contains("logging_lifecycle_span"));
assert!(!domain_logging_text.contains("direct store domain marker"));
assert!(!domain_logging_text.contains("event override store marker"));
assert!(!domain_logging_text.contains("child explicit store marker"));
assert!(!domain_logging_text.contains("inherited store marker"));
assert!(!domain_logging_text.contains("store_lifecycle_span"));
assert!(!domain_logging_text.contains("undomained marker"));
assert!(domain_store_text.contains("direct store domain marker"));
assert!(domain_store_text.contains("event override store marker"));
assert!(domain_store_text.contains("child explicit store marker"));
assert!(domain_store_text.contains("inherited store marker"));
assert!(domain_store_text.contains("store_lifecycle_span"));
assert!(!domain_store_text.contains("direct logging domain marker"));
assert!(!domain_store_text.contains("inherited logging marker"));
assert!(!domain_store_text.contains("child inherited logging marker"));
assert!(!domain_store_text.contains("logging_lifecycle_span"));
assert!(!domain_store_text.contains("undomained marker"));
assert!(domain_any_text.contains("direct logging domain marker"));
assert!(domain_any_text.contains("direct store domain marker"));
assert!(domain_any_text.contains("undomained marker"));
assert!(domain_any_text.contains("inherited logging marker"));
assert!(domain_any_text.contains("event override store marker"));
assert!(domain_any_text.contains("child inherited logging marker"));
assert!(domain_any_text.contains("child explicit store marker"));
assert!(domain_any_text.contains("inherited store marker"));
assert!(domain_any_text.contains("logging_lifecycle_span"));
assert!(domain_any_text.contains("store_lifecycle_span"));
assert_eq!(guard.dropped_file_lines("file.domain.logging"), std::option::Option::Some(0));
assert_eq!(guard.dropped_file_lines("file.domain.store"), std::option::Option::Some(0));
assert_eq!(guard.dropped_file_lines("file.domain.any"), std::option::Option::Some(0));
let dropped = guard.dropped_lines();
assert_eq!(dropped.total(), dropped.console().saturating_add(dropped.file()));
let second_initialize = ksp_logging_lib::initialize(&disabled);

View File

@@ -0,0 +1,20 @@
// file: crates/ksp-logging-lib/unit_tests/domain.rs
// version: 1
#[test]
fn wildcard_domain_matches_with_or_without_current_domain() {
super::set_current_domain(std::option::Option::None);
assert!(super::current_domain_matches(&["*".to_string()]));
super::set_current_domain(std::option::Option::Some("logging"));
assert!(super::current_domain_matches(&["*".to_string()]));
}
#[test]
fn named_domain_requires_current_matching_prefix() {
super::set_current_domain(std::option::Option::None);
assert!(!super::current_domain_matches(&["logging".to_string()]));
super::set_current_domain(std::option::Option::Some("logging.runtime"));
assert!(super::current_domain_matches(&["logging".to_string()]));
assert!(super::current_domain_matches(&["store".to_string(), "logging.runtime".to_string()]));
assert!(!super::current_domain_matches(&["store".to_string()]));
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-logging-lib/unit_tests/runtime.rs
// version: 8
// version: 9
#[test]
fn level_mapping_covers_all_ksp_levels() {
@@ -154,7 +154,7 @@ fn multi_sink_runtime_accepts_metadata_routing_formats_and_console_ansi() {
}
#[test]
fn domain_routing_is_explicitly_deferred_instead_of_ignored() {
fn domain_routing_is_accepted_by_runtime_preparation() {
let console = crate::ConsoleSettings::new(
true,
crate::ConsoleOutput::Stdout,
@@ -165,12 +165,7 @@ fn domain_routing_is_explicitly_deferred_instead_of_ignored() {
let settings = crate::LoggingSettings::new(crate::LogFilterLevel::Info, crate::SpanEvents::Off, std::option::Option::Some(console), std::vec::Vec::new());
assert!(settings.validate().is_ok());
let result = super::prepare_runtime(&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);
assert!(result.is_ok());
}
struct BlockingWriter {