v0.1.3-pre.005
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/lib.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -7,9 +7,9 @@
|
||||
//! 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.004` extends
|
||||
//! the public settings contract with explicit console properties, multiple file descriptors, per-output level/target/domain filters and selectable formats;
|
||||
//! runtime activation of the newly represented multi-output routing is completed separately so unsupported capabilities are never ignored silently.
|
||||
//! `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.
|
||||
|
||||
mod error;
|
||||
mod macros;
|
||||
@@ -38,9 +38,9 @@ pub use self::runtime::reinitialize;
|
||||
pub use self::settings::ConsoleOutput;
|
||||
/// Runtime settings for the optional console output.
|
||||
pub use self::settings::ConsoleSettings;
|
||||
/// Rotation cadence for the optional file output.
|
||||
/// Rotation cadence for one file output.
|
||||
pub use self::settings::FileRotation;
|
||||
/// Runtime settings for the optional file output.
|
||||
/// Runtime settings for one file output.
|
||||
pub use self::settings::FileSettings;
|
||||
/// Runtime filter level used by KSP logging settings.
|
||||
pub use self::settings::LogFilterLevel;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/runtime.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
use tracing_subscriber::Layer; // rust-rules: trait-import
|
||||
use tracing_subscriber::layer::SubscriberExt; // rust-rules: trait-import
|
||||
@@ -47,6 +47,7 @@ pub struct LoggingGuard {
|
||||
settings: crate::LoggingSettings,
|
||||
outputs: RuntimeOutputs,
|
||||
retired_dropped_lines: crate::DroppedLines,
|
||||
retired_file_dropped_lines: std::collections::HashMap<std::string::String, usize>,
|
||||
}
|
||||
|
||||
impl LoggingGuard {
|
||||
@@ -61,6 +62,19 @@ impl LoggingGuard {
|
||||
pub fn dropped_lines(&self) -> crate::DroppedLines {
|
||||
return self.retired_dropped_lines.saturating_add(self.outputs.dropped_lines());
|
||||
}
|
||||
|
||||
/// Returns cumulative dropped-line counters for one file `output_id` when that output has existed in the runtime.
|
||||
#[must_use]
|
||||
pub fn dropped_file_lines(&self, output_id: &str) -> std::option::Option<usize> {
|
||||
let retired = self.retired_file_dropped_lines.get(output_id).copied();
|
||||
let active = self.outputs.file_dropped_lines(output_id);
|
||||
return match (retired, active) {
|
||||
(std::option::Option::Some(retired), std::option::Option::Some(active)) => std::option::Option::Some(retired.saturating_add(active)),
|
||||
(std::option::Option::Some(retired), std::option::Option::None) => std::option::Option::Some(retired),
|
||||
(std::option::Option::None, std::option::Option::Some(active)) => std::option::Option::Some(active),
|
||||
(std::option::Option::None, std::option::Option::None) => std::option::Option::None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
type BoxedRuntimeLayer = std::boxed::Box<dyn tracing_subscriber::Layer<tracing_subscriber::Registry> + std::marker::Send + std::marker::Sync + 'static>;
|
||||
@@ -75,7 +89,7 @@ struct PreparedRuntime {
|
||||
#[derive(Default)]
|
||||
struct RuntimeOutputs {
|
||||
console: std::option::Option<RuntimeOutput>,
|
||||
file: std::option::Option<RuntimeOutput>,
|
||||
files: std::vec::Vec<RuntimeFileOutput>,
|
||||
}
|
||||
|
||||
impl RuntimeOutputs {
|
||||
@@ -84,12 +98,44 @@ impl RuntimeOutputs {
|
||||
std::option::Option::Some(output) => output.dropped_lines(),
|
||||
std::option::Option::None => 0,
|
||||
};
|
||||
let file = match self.file.as_ref() {
|
||||
std::option::Option::Some(output) => output.dropped_lines(),
|
||||
std::option::Option::None => 0,
|
||||
};
|
||||
let mut file = 0_usize;
|
||||
for output in &self.files {
|
||||
file = file.saturating_add(output.output.dropped_lines());
|
||||
}
|
||||
return crate::DroppedLines { console, file };
|
||||
}
|
||||
|
||||
fn file_dropped_lines(&self, output_id: &str) -> std::option::Option<usize> {
|
||||
return self
|
||||
.files
|
||||
.iter()
|
||||
.find(|output| -> bool {
|
||||
return output.output_id == output_id;
|
||||
})
|
||||
.map(|output| -> usize {
|
||||
return output.output.dropped_lines();
|
||||
});
|
||||
}
|
||||
|
||||
fn accumulate_file_dropped_lines(&self, destination: &mut std::collections::HashMap<std::string::String, usize>) {
|
||||
for output in &self.files {
|
||||
let dropped = output.output.dropped_lines();
|
||||
match destination.entry(output.output_id.clone()) {
|
||||
std::collections::hash_map::Entry::Occupied(mut entry) => {
|
||||
let cumulative = entry.get().saturating_add(dropped);
|
||||
*entry.get_mut() = cumulative;
|
||||
},
|
||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||
entry.insert(dropped);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RuntimeFileOutput {
|
||||
output_id: std::string::String,
|
||||
output: RuntimeOutput,
|
||||
}
|
||||
|
||||
struct RuntimeOutput {
|
||||
@@ -108,6 +154,12 @@ struct PreparedOutput {
|
||||
output: RuntimeOutput,
|
||||
}
|
||||
|
||||
struct PreparedFileOutput {
|
||||
output_id: std::string::String,
|
||||
layer: BoxedRuntimeLayer,
|
||||
output: RuntimeOutput,
|
||||
}
|
||||
|
||||
/// Installs the global KSP tracing subscriber.
|
||||
///
|
||||
/// This function may succeed only once for the lifetime of the process. The returned guard owns all non-blocking writer guards and is then used by
|
||||
@@ -124,6 +176,7 @@ pub fn initialize(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<cra
|
||||
settings: settings.clone(),
|
||||
outputs,
|
||||
retired_dropped_lines: crate::DroppedLines::zero(),
|
||||
retired_file_dropped_lines: std::collections::HashMap::new(),
|
||||
}),
|
||||
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),
|
||||
@@ -147,6 +200,7 @@ pub fn reinitialize(guard: &mut crate::LoggingGuard, settings: &crate::LoggingSe
|
||||
return match reload_result {
|
||||
std::result::Result::Ok(()) => {
|
||||
guard.retired_dropped_lines = guard.retired_dropped_lines.saturating_add(guard.outputs.dropped_lines());
|
||||
guard.outputs.accumulate_file_dropped_lines(&mut guard.retired_file_dropped_lines);
|
||||
let retired_outputs = std::mem::replace(&mut guard.outputs, outputs);
|
||||
guard.settings = settings.clone();
|
||||
drop(retired_layers);
|
||||
@@ -172,76 +226,84 @@ fn prepare_runtime(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<Pr
|
||||
let enabled_console = settings.console().filter(|console| -> bool {
|
||||
return console.enabled();
|
||||
});
|
||||
let enabled_file = settings.files().iter().find(|file| -> bool {
|
||||
let enabled_files = settings.files().iter().filter(|file| -> bool {
|
||||
return file.enabled();
|
||||
});
|
||||
if enabled_console.is_none() && enabled_file.is_none() {
|
||||
return std::result::Result::Ok(PreparedRuntime { layers: RuntimeLayers::new(), outputs: RuntimeOutputs::default() });
|
||||
}
|
||||
let prepared_file = match enabled_file {
|
||||
std::option::Option::Some(file) => match build_file_output(file, settings) {
|
||||
std::result::Result::Ok(output) => std::option::Option::Some(output),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
};
|
||||
let prepared_console = enabled_console.map(|console| -> PreparedOutput {
|
||||
return build_console_output(console, settings);
|
||||
});
|
||||
let mut output_layers = RuntimeLayers::new();
|
||||
let mut outputs = RuntimeOutputs::default();
|
||||
if let std::option::Option::Some(console) = prepared_console {
|
||||
output_layers.push(console.layer);
|
||||
outputs.console = std::option::Option::Some(console.output);
|
||||
if let std::option::Option::Some(console) = enabled_console {
|
||||
let prepared_console = build_console_output(console, settings);
|
||||
output_layers.push(prepared_console.layer);
|
||||
outputs.console = std::option::Option::Some(prepared_console.output);
|
||||
}
|
||||
if let std::option::Option::Some(file) = prepared_file {
|
||||
output_layers.push(file.layer);
|
||||
outputs.file = std::option::Option::Some(file.output);
|
||||
for file in enabled_files {
|
||||
let prepared_file_result = build_file_output(file, settings);
|
||||
let prepared_file = match prepared_file_result {
|
||||
std::result::Result::Ok(output) => output,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
output_layers.push(prepared_file.layer);
|
||||
outputs.files.push(RuntimeFileOutput { output_id: prepared_file.output_id, output: prepared_file.output });
|
||||
}
|
||||
if output_layers.is_empty() {
|
||||
return std::result::Result::Ok(PreparedRuntime { layers: RuntimeLayers::new(), outputs });
|
||||
}
|
||||
let takeover_layer = build_target_filter(settings).and_then(output_layers).boxed();
|
||||
let layers = vec![takeover_layer];
|
||||
return std::result::Result::Ok(PreparedRuntime { layers, outputs });
|
||||
return std::result::Result::Ok(PreparedRuntime { layers: vec![takeover_layer], outputs });
|
||||
}
|
||||
|
||||
fn validate_current_runtime_capabilities(settings: &crate::LoggingSettings) -> ksp_core_lib::Result<()> {
|
||||
let enabled_file_count = settings
|
||||
.files()
|
||||
.iter()
|
||||
.filter(|file| -> bool {
|
||||
return file.enabled();
|
||||
})
|
||||
.count();
|
||||
if enabled_file_count > 1 {
|
||||
return runtime_capability_error("multiple enabled file outputs require the multi-sink runtime");
|
||||
}
|
||||
if let std::option::Option::Some(console) = settings.console()
|
||||
&& console.enabled()
|
||||
&& (console.ansi() || console.format() != crate::LogFormat::Human || !console.filter().is_unrestricted())
|
||||
&& !domains_are_unrestricted(console.filter())
|
||||
{
|
||||
return runtime_capability_error("console ANSI, selectable formats and per-output routing require the multi-sink runtime");
|
||||
return runtime_capability_error("domain routing requires the dedicated structured-domain runtime tranche");
|
||||
}
|
||||
for file in settings.files() {
|
||||
if file.enabled() && (file.format() != crate::LogFormat::Human || !file.filter().is_unrestricted()) {
|
||||
return runtime_capability_error("file formats and per-output routing require the multi-sink runtime");
|
||||
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", "single-output-compatibility"),
|
||||
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(std::io::stdout(), "ksp-logging-console", settings, true),
|
||||
crate::ConsoleOutput::Stderr => build_non_blocking_output(std::io::stderr(), "ksp-logging-console", settings, true),
|
||||
crate::ConsoleOutput::Stdout => build_non_blocking_output(
|
||||
std::io::stdout(),
|
||||
"ksp-logging-console",
|
||||
settings.span_events(),
|
||||
true,
|
||||
console.ansi(),
|
||||
console.format(),
|
||||
console.filter(),
|
||||
),
|
||||
crate::ConsoleOutput::Stderr => build_non_blocking_output(
|
||||
std::io::stderr(),
|
||||
"ksp-logging-console",
|
||||
settings.span_events(),
|
||||
true,
|
||||
console.ansi(),
|
||||
console.format(),
|
||||
console.filter(),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn build_file_output(file: &crate::FileSettings, settings: &crate::LoggingSettings) -> ksp_core_lib::Result<PreparedOutput> {
|
||||
fn build_file_output(file: &crate::FileSettings, settings: &crate::LoggingSettings) -> ksp_core_lib::Result<PreparedFileOutput> {
|
||||
let appender_result = tracing_appender::rolling::RollingFileAppender::builder()
|
||||
.rotation(map_file_rotation(file.rotation()))
|
||||
.filename_prefix(file.file_name_prefix())
|
||||
@@ -251,6 +313,7 @@ fn build_file_output(file: &crate::FileSettings, settings: &crate::LoggingSettin
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_FILE_OUTPUT_INITIALIZATION_FAILED, "unable to initialize the KSP rolling file appender")
|
||||
.with_context("output_id", file.output_id())
|
||||
.with_context("directory", file.directory().display().to_string())
|
||||
.with_context("file_name_prefix", file.file_name_prefix())
|
||||
.with_source(error),
|
||||
@@ -258,16 +321,26 @@ fn build_file_output(file: &crate::FileSettings, settings: &crate::LoggingSettin
|
||||
},
|
||||
};
|
||||
let stripped_writer = crate::writer::StripAnsiWriter::new(appender);
|
||||
return std::result::Result::Ok(build_non_blocking_output(stripped_writer, "ksp-logging-file", settings, false));
|
||||
let thread_name = format!("ksp-logging-{}", file.output_id());
|
||||
let prepared = build_non_blocking_output(stripped_writer, thread_name.as_str(), settings.span_events(), false, false, file.format(), file.filter());
|
||||
return std::result::Result::Ok(PreparedFileOutput { output_id: file.output_id().to_string(), layer: prepared.layer, output: prepared.output });
|
||||
}
|
||||
|
||||
fn build_non_blocking_output<W>(writer: W, thread_name: &str, settings: &crate::LoggingSettings, ansi_sanitization: bool) -> PreparedOutput
|
||||
fn build_non_blocking_output<W>(
|
||||
writer: W,
|
||||
thread_name: &str,
|
||||
span_events: crate::SpanEvents,
|
||||
ansi_sanitization: bool,
|
||||
ansi: bool,
|
||||
format: crate::LogFormat,
|
||||
filter: &crate::OutputFilter,
|
||||
) -> PreparedOutput
|
||||
where
|
||||
W: std::io::Write + std::marker::Send + 'static,
|
||||
{
|
||||
let (non_blocking, worker_guard) = non_blocking_builder(thread_name).finish(writer);
|
||||
let error_counter = non_blocking.error_counter();
|
||||
let layer = build_format_layer(non_blocking, settings, ansi_sanitization);
|
||||
let layer = build_format_layer(non_blocking, span_events, ansi_sanitization, ansi, format, filter);
|
||||
return PreparedOutput { layer, output: RuntimeOutput { _worker_guard: worker_guard, error_counter } };
|
||||
}
|
||||
|
||||
@@ -275,16 +348,55 @@ fn non_blocking_builder(thread_name: &str) -> tracing_appender::non_blocking::No
|
||||
return tracing_appender::non_blocking::NonBlockingBuilder::default().lossy(true).thread_name(thread_name);
|
||||
}
|
||||
|
||||
fn build_format_layer(writer: tracing_appender::non_blocking::NonBlocking, settings: &crate::LoggingSettings, ansi_sanitization: bool) -> BoxedRuntimeLayer {
|
||||
return tracing_subscriber::fmt::layer()
|
||||
.with_writer(writer)
|
||||
.with_ansi(false)
|
||||
.with_ansi_sanitization(ansi_sanitization)
|
||||
.with_target(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_span_events(map_span_events(settings.span_events()))
|
||||
.boxed();
|
||||
fn build_format_layer(
|
||||
writer: tracing_appender::non_blocking::NonBlocking,
|
||||
span_events: crate::SpanEvents,
|
||||
ansi_sanitization: bool,
|
||||
ansi: bool,
|
||||
format: crate::LogFormat,
|
||||
filter: &crate::OutputFilter,
|
||||
) -> BoxedRuntimeLayer {
|
||||
let span_events = map_span_events(span_events);
|
||||
return match format {
|
||||
crate::LogFormat::Human => tracing_subscriber::fmt::layer()
|
||||
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_ansi(ansi)
|
||||
.with_ansi_sanitization(ansi_sanitization)
|
||||
.with_target(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_span_events(span_events)
|
||||
.boxed(),
|
||||
crate::LogFormat::Compact => tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_ansi(ansi)
|
||||
.with_ansi_sanitization(ansi_sanitization)
|
||||
.with_target(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_span_events(span_events)
|
||||
.boxed(),
|
||||
crate::LogFormat::Pretty => tracing_subscriber::fmt::layer()
|
||||
.pretty()
|
||||
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_ansi(ansi)
|
||||
.with_ansi_sanitization(ansi_sanitization)
|
||||
.with_target(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_span_events(span_events)
|
||||
.boxed(),
|
||||
crate::LogFormat::Json => tracing_subscriber::fmt::layer()
|
||||
.json()
|
||||
.with_writer(crate::writer::RouteMakeWriter::new(writer, filter.clone()))
|
||||
.with_ansi(false)
|
||||
.with_target(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_span_events(span_events)
|
||||
.boxed(),
|
||||
};
|
||||
}
|
||||
|
||||
fn build_target_filter(settings: &crate::LoggingSettings) -> tracing_subscriber::filter::Targets {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/settings.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
/// Runtime filter level used by KSP logging settings.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
@@ -109,19 +109,6 @@ impl OutputFilter {
|
||||
pub fn domains(&self) -> &[std::string::String] {
|
||||
return self.domains.as_slice();
|
||||
}
|
||||
|
||||
/// Returns whether this filter leaves routing entirely to the global takeover policy.
|
||||
pub(crate) fn is_unrestricted(&self) -> bool {
|
||||
let targets_all = match self.targets.as_slice() {
|
||||
[selector] => selector == "*",
|
||||
_ => false,
|
||||
};
|
||||
let domains_all = match self.domains.as_slice() {
|
||||
[selector] => selector == "*",
|
||||
_ => false,
|
||||
};
|
||||
return self.level == crate::LogFilterLevel::Trace && targets_all && domains_all;
|
||||
}
|
||||
}
|
||||
|
||||
/// Console stream selected for human-readable logs.
|
||||
@@ -376,6 +363,12 @@ impl LoggingSettings {
|
||||
}
|
||||
}
|
||||
if let std::option::Option::Some(console) = self.console.as_ref() {
|
||||
if console.ansi() && console.format() == crate::LogFormat::Json {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_SETTINGS, "ANSI formatting is not compatible with JSON console output")
|
||||
.with_context("field", "console.ansi"),
|
||||
);
|
||||
}
|
||||
let validation_result = validate_output_filter(console.filter(), "console.filter");
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-logging-lib/src/writer.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum StripAnsiState {
|
||||
@@ -100,6 +100,96 @@ impl<W> StripAnsiWriter<W> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct RouteMakeWriter<W> {
|
||||
inner: W,
|
||||
filter: crate::OutputFilter,
|
||||
}
|
||||
|
||||
impl<W> RouteMakeWriter<W> {
|
||||
pub(crate) fn new(inner: W, filter: crate::OutputFilter) -> Self {
|
||||
return Self { inner, filter };
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum RoutedWriter<W> {
|
||||
Enabled(W),
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl<W> std::io::Write for RoutedWriter<W>
|
||||
where
|
||||
W: std::io::Write,
|
||||
{
|
||||
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
|
||||
return match self {
|
||||
Self::Enabled(writer) => std::io::Write::write(writer, buffer),
|
||||
Self::Disabled => std::result::Result::Ok(buffer.len()),
|
||||
};
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
return match self {
|
||||
Self::Enabled(writer) => std::io::Write::flush(writer),
|
||||
Self::Disabled => std::result::Result::Ok(()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<'writer, W> tracing_subscriber::fmt::MakeWriter<'writer> for RouteMakeWriter<W>
|
||||
where
|
||||
W: tracing_subscriber::fmt::MakeWriter<'writer>,
|
||||
{
|
||||
type Writer = RoutedWriter<W::Writer>;
|
||||
|
||||
fn make_writer(&'writer self) -> Self::Writer {
|
||||
return RoutedWriter::Enabled(tracing_subscriber::fmt::MakeWriter::make_writer(&self.inner));
|
||||
}
|
||||
|
||||
fn make_writer_for(&'writer self, metadata: &tracing::Metadata<'_>) -> Self::Writer {
|
||||
if metadata_matches_filter(metadata, &self.filter) {
|
||||
return RoutedWriter::Enabled(tracing_subscriber::fmt::MakeWriter::make_writer_for(&self.inner, metadata));
|
||||
}
|
||||
return RoutedWriter::Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata_matches_filter(metadata: &tracing::Metadata<'_>, filter: &crate::OutputFilter) -> bool {
|
||||
if !level_is_enabled(metadata.level(), filter.level()) {
|
||||
return false;
|
||||
}
|
||||
return filter.targets().iter().any(|selector| -> bool {
|
||||
return selector == "*" || metadata.target().starts_with(selector.as_str());
|
||||
});
|
||||
}
|
||||
|
||||
fn level_is_enabled(level: &tracing::Level, filter: crate::LogFilterLevel) -> bool {
|
||||
return match filter {
|
||||
crate::LogFilterLevel::Off => false,
|
||||
crate::LogFilterLevel::Error => level_rank(level) <= 1,
|
||||
crate::LogFilterLevel::Warn => level_rank(level) <= 2,
|
||||
crate::LogFilterLevel::Info => level_rank(level) <= 3,
|
||||
crate::LogFilterLevel::Debug => level_rank(level) <= 4,
|
||||
crate::LogFilterLevel::Trace => level_rank(level) <= 5,
|
||||
};
|
||||
}
|
||||
|
||||
fn level_rank(level: &tracing::Level) -> u8 {
|
||||
if level == &tracing::Level::ERROR {
|
||||
return 1;
|
||||
}
|
||||
if level == &tracing::Level::WARN {
|
||||
return 2;
|
||||
}
|
||||
if level == &tracing::Level::INFO {
|
||||
return 3;
|
||||
}
|
||||
if level == &tracing::Level::DEBUG {
|
||||
return 4;
|
||||
}
|
||||
return 5;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/writer.rs"]
|
||||
mod tests;
|
||||
|
||||
Reference in New Issue
Block a user