v0.1.3-pre.006
This commit is contained in:
181
crates/ksp-logging-lib/src/domain.rs
Normal file
181
crates/ksp-logging-lib/src/domain.rs
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user