v0.1.2-pre.002

This commit is contained in:
2026-08-14 18:03:20 +02:00
parent bd8401c0d0
commit 7b4444fb07
14 changed files with 1067 additions and 26 deletions

View File

@@ -0,0 +1,5 @@
// file: crates/ksp-logging-lib/src/error.rs
// version: 1
/// 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");

View File

@@ -0,0 +1,42 @@
// file: crates/ksp-logging-lib/src/lib.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! 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.
mod error;
mod macros;
mod settings;
mod span;
/// Error code used when runtime logging settings are invalid.
pub use self::error::ERROR_CODE_INVALID_SETTINGS;
/// Console stream selected for human-readable logs.
pub use self::settings::ConsoleOutput;
/// Runtime settings for the optional console output.
pub use self::settings::ConsoleSettings;
/// Rotation cadence for the optional file output.
pub use self::settings::FileRotation;
/// Runtime settings for the optional file output.
pub use self::settings::FileSettings;
/// Runtime filter level used by KSP logging settings.
pub use self::settings::LogFilterLevel;
/// Complete runtime settings consumed by Logging initialization and reload.
pub use self::settings::LoggingSettings;
/// Lifecycle events emitted for spans by the formatted subscriber.
pub use self::settings::SpanEvents;
/// Per-target filter override owned by Logging.
pub use self::settings::TargetFilter;
/// KSP-owned handle to a tracing span.
pub use self::span::Span;
/// Instruments an asynchronous future with a KSP span.
pub use self::span::instrument;
#[doc(hidden)]
/// Internal macro bridge. KSP consumers must not use this reexport directly.
pub extern crate tracing as __private_tracing;

View File

@@ -0,0 +1,97 @@
// file: crates/ksp-logging-lib/src/macros.rs
// version: 1
/// Emits a KSP error event with an explicit owning target.
#[macro_export]
macro_rules! error {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::error!(target: $target, $($argument)+);
}};
}
/// Emits a KSP warning event with an explicit owning target.
#[macro_export]
macro_rules! warn {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::warn!(target: $target, $($argument)+);
}};
}
/// Emits a KSP informational event with an explicit owning target.
#[macro_export]
macro_rules! info {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::info!(target: $target, $($argument)+);
}};
}
/// Emits a KSP debug event with an explicit owning target.
#[macro_export]
macro_rules! debug {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::debug!(target: $target, $($argument)+);
}};
}
/// Emits a KSP trace event with an explicit owning target.
#[macro_export]
macro_rules! trace {
(target: $target:expr, $($argument:tt)+) => {{
$crate::__private_tracing::trace!(target: $target, $($argument)+);
}};
}
/// Creates a KSP error span with an explicit owning target.
#[macro_export]
macro_rules! error_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::error_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::error_span!(target: $target, $name, $($field)+))
}};
}
/// Creates a KSP warning span with an explicit owning target.
#[macro_export]
macro_rules! warn_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::warn_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::warn_span!(target: $target, $name, $($field)+))
}};
}
/// Creates a KSP informational span with an explicit owning target.
#[macro_export]
macro_rules! info_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::info_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::info_span!(target: $target, $name, $($field)+))
}};
}
/// Creates a KSP debug span with an explicit owning target.
#[macro_export]
macro_rules! debug_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::debug_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::debug_span!(target: $target, $name, $($field)+))
}};
}
/// Creates a KSP trace span with an explicit owning target.
#[macro_export]
macro_rules! trace_span {
(target: $target:expr, $name:expr) => {{
$crate::Span::__from_tracing($crate::__private_tracing::trace_span!(target: $target, $name))
}};
(target: $target:expr, $name:expr, $($field:tt)+) => {{
$crate::Span::__from_tracing($crate::__private_tracing::trace_span!(target: $target, $name, $($field)+))
}};
}

View File

@@ -0,0 +1,233 @@
// file: crates/ksp-logging-lib/src/settings.rs
// version: 1
/// Runtime filter level used by KSP logging settings.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LogFilterLevel {
/// Disables matching logging events and spans.
Off,
/// Enables only error-level events and spans.
Error,
/// Enables warning and error events and spans.
Warn,
/// Enables informational, warning and error events and spans.
Info,
/// Enables debug and less verbose events and spans.
Debug,
/// Enables all KSP logging events and spans.
Trace,
}
/// Per-target filter override owned by Logging.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TargetFilter {
target_prefix: std::string::String,
level: crate::LogFilterLevel,
}
impl TargetFilter {
/// Creates a filter override for a KSP target prefix.
#[must_use]
pub fn new(target_prefix: impl std::convert::Into<std::string::String>, level: crate::LogFilterLevel) -> Self {
return Self { target_prefix: target_prefix.into(), level };
}
/// Returns the configured target prefix.
#[must_use]
pub fn target_prefix(&self) -> &str {
return self.target_prefix.as_str();
}
/// Returns the configured filter level.
#[must_use]
pub const fn level(&self) -> crate::LogFilterLevel {
return self.level;
}
}
/// Lifecycle events emitted for spans by the formatted subscriber.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SpanEvents {
/// Does not synthesize span lifecycle events.
Off,
/// Emits span creation and closure events for timing-oriented diagnostics.
NewAndClose,
/// Emits all supported span lifecycle events.
Full,
}
/// Console stream selected for human-readable logs.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ConsoleOutput {
/// Writes console logs to standard output.
Stdout,
/// Writes console logs to standard error.
Stderr,
}
/// Runtime settings for the optional console output.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ConsoleSettings {
output: crate::ConsoleOutput,
}
impl ConsoleSettings {
/// Creates console settings targeting standard output.
#[must_use]
pub const fn stdout() -> Self {
return Self { output: crate::ConsoleOutput::Stdout };
}
/// Creates console settings targeting standard error.
#[must_use]
pub const fn stderr() -> Self {
return Self { output: crate::ConsoleOutput::Stderr };
}
/// Returns the selected console stream.
#[must_use]
pub const fn output(&self) -> crate::ConsoleOutput {
return self.output;
}
}
/// Rotation cadence for the optional file output.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum FileRotation {
/// Keeps a single non-rotating file.
Never,
/// Rotates the file every hour.
Hourly,
/// Rotates the file every day.
Daily,
}
/// Runtime settings for the optional file output.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FileSettings {
directory: std::path::PathBuf,
file_name_prefix: std::string::String,
rotation: crate::FileRotation,
}
impl FileSettings {
/// Creates file output settings.
#[must_use]
pub fn new(
directory: impl std::convert::Into<std::path::PathBuf>,
file_name_prefix: impl std::convert::Into<std::string::String>,
rotation: crate::FileRotation,
) -> Self {
return Self { directory: directory.into(), file_name_prefix: file_name_prefix.into(), rotation };
}
/// Returns the directory containing log files.
#[must_use]
pub fn directory(&self) -> &std::path::Path {
return self.directory.as_path();
}
/// Returns the file-name prefix passed to the file appender.
#[must_use]
pub fn file_name_prefix(&self) -> &str {
return self.file_name_prefix.as_str();
}
/// Returns the selected file rotation cadence.
#[must_use]
pub const fn rotation(&self) -> crate::FileRotation {
return self.rotation;
}
}
/// Complete runtime settings consumed by `ksp-logging-lib` initialization and reload.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LoggingSettings {
default_filter: crate::LogFilterLevel,
target_filters: std::vec::Vec<crate::TargetFilter>,
span_events: crate::SpanEvents,
console: std::option::Option<crate::ConsoleSettings>,
file: std::option::Option<crate::FileSettings>,
}
impl LoggingSettings {
/// Creates explicit Logging settings without any target override.
#[must_use]
pub fn new(
default_filter: crate::LogFilterLevel,
span_events: crate::SpanEvents,
console: std::option::Option<crate::ConsoleSettings>,
file: std::option::Option<crate::FileSettings>,
) -> Self {
return Self { default_filter, target_filters: std::vec::Vec::new(), span_events, console, file };
}
/// Adds one target-prefix override and returns the updated settings.
#[must_use]
pub fn with_target_filter(mut self, target_filter: crate::TargetFilter) -> Self {
self.target_filters.push(target_filter);
return self;
}
/// Returns the default level applied to KSP-owned targets.
#[must_use]
pub const fn default_filter(&self) -> crate::LogFilterLevel {
return self.default_filter;
}
/// Returns target-prefix overrides in insertion order.
#[must_use]
pub fn target_filters(&self) -> &[crate::TargetFilter] {
return self.target_filters.as_slice();
}
/// Returns the selected span lifecycle event policy.
#[must_use]
pub const fn span_events(&self) -> crate::SpanEvents {
return self.span_events;
}
/// Returns console settings when console output is enabled.
#[must_use]
pub fn console(&self) -> std::option::Option<&crate::ConsoleSettings> {
return self.console.as_ref();
}
/// Returns file settings when file output is enabled.
#[must_use]
pub fn file(&self) -> std::option::Option<&crate::FileSettings> {
return self.file.as_ref();
}
/// Validates backend-independent invariants of the runtime settings.
pub fn validate(&self) -> ksp_core_lib::Result<()> {
for target_filter in &self.target_filters {
if target_filter.target_prefix().is_empty() {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "target filter prefix must not be empty")
.with_context("field", "target_filters.target_prefix"),
);
}
if !target_filter.target_prefix().starts_with("ksp-") {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "target filter prefix must identify a KSP-owned target")
.with_context("field", "target_filters.target_prefix")
.with_context("target_prefix", target_filter.target_prefix()),
);
}
}
if let std::option::Option::Some(file) = self.file.as_ref() {
if file.file_name_prefix().is_empty() {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "file name prefix must not be empty")
.with_context("field", "file.file_name_prefix"),
);
}
}
return std::result::Result::Ok(());
}
}
#[cfg(test)]
#[path = "../unit_tests/settings.rs"]
mod tests;

View File

@@ -0,0 +1,42 @@
// file: crates/ksp-logging-lib/src/span.rs
// version: 1
/// KSP-owned handle to a tracing span.
#[derive(Clone, Debug)]
pub struct Span {
inner: tracing::Span,
}
impl Span {
/// Runs synchronous work while this span is entered.
pub fn in_scope<T>(&self, operation: impl std::ops::FnOnce() -> T) -> T {
return self.inner.in_scope(operation);
}
#[doc(hidden)]
/// Constructs the KSP span wrapper for macro expansion support.
#[must_use]
pub fn __from_tracing(inner: tracing::Span) -> Self {
return Self { inner };
}
/// Consumes this wrapper and returns the internal tracing span.
pub(crate) fn into_tracing(self) -> tracing::Span {
return self.inner;
}
}
/// Instruments an asynchronous future with a KSP span.
///
/// The span is entered whenever the future is polled and exited whenever it yields, so no enter guard is held across an `.await` point.
#[must_use]
pub fn instrument<F>(span: crate::Span, future: F) -> impl std::future::Future<Output = F::Output>
where
F: std::future::Future,
{
return tracing::Instrument::instrument(future, span.into_tracing());
}
#[cfg(test)]
#[path = "../unit_tests/span.rs"]
mod tests;