1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
//! Integrates the Rerun SDK with the [`log`] crate.
use log::Log;
use re_types::{archetypes::TextLog, components::TextLogLevel};
use crate::RecordingStream;
// ---
/// Implements a [`log::Log`] that forwards all events to the Rerun SDK.
///
/// ```
/// let rec = rerun::RecordingStreamBuilder::new("rerun_example_app").buffered()?;
///
/// rerun::Logger::new(rec.clone()) // recording streams are ref-counted
/// .with_path_prefix("logs")
/// .with_filter(rerun::default_log_filter())
/// .init()?;
///
/// log::info!("This INFO log got added through the standard logging interface");
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Debug)]
pub struct Logger {
rec: RecordingStream,
filter: Option<env_logger::filter::Filter>,
path_prefix: Option<String>,
}
impl Drop for Logger {
fn drop(&mut self) {
self.flush();
}
}
impl Logger {
/// Returns a new [`Logger`] that forwards all events to the specified [`RecordingStream`].
pub fn new(rec: RecordingStream) -> Self {
Self {
rec,
filter: None,
path_prefix: None,
}
}
/// Configures the [`Logger`] to prefix the specified `path_prefix` to all events.
#[inline]
pub fn with_path_prefix(mut self, path_prefix: impl Into<String>) -> Self {
self.path_prefix = Some(path_prefix.into());
self
}
/// Configures the [`Logger`] to filter events.
///
/// This uses the familiar [env_logger syntax].
///
/// If you don't call this, the [`Logger`] will parse the `RUST_LOG` environment variable
/// instead when you [`Logger::init`] it.
///
/// [env_logger syntax]: https://docs.rs/env_logger/latest/env_logger/index.html#enabling-logging
#[inline]
pub fn with_filter(mut self, filter: impl AsRef<str>) -> Self {
use env_logger::filter::Builder;
self.filter = Some(Builder::new().parse(filter.as_ref()).build());
self
}
/// Sets the [`Logger`] as global logger.
///
/// All calls to [`log`] macros will go through this [`Logger`] from this point on.
pub fn init(mut self) -> Result<(), log::SetLoggerError> {
if self.filter.is_none() {
use env_logger::filter::Builder;
self.filter = Some(Builder::new().parse(&re_log::default_log_filter()).build());
}
// NOTE: We will have to make filtering decisions on a per-crate/module basis, therefore
// there is no global filtering ceiling.
log::set_max_level(log::LevelFilter::max());
log::set_boxed_logger(Box::new(self))
}
}
impl log::Log for Logger {
#[inline]
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
self.filter
.as_ref()
.map_or(true, |filter| filter.enabled(metadata))
}
#[inline]
fn log(&self, record: &log::Record<'_>) {
if !self
.filter
.as_ref()
.map_or(true, |filter| filter.matches(record))
{
return;
}
let target = record.metadata().target().replace("::", "/");
let ent_path = if let Some(path_prefix) = self.path_prefix.as_ref() {
format!("{path_prefix}/{target}")
} else {
target
};
let level = log_level_to_rerun_level(record.metadata().level());
let body = format!("{}", record.args());
self.rec
.log(ent_path, &TextLog::new(body).with_level(level))
.ok(); // ignore error
}
#[inline]
fn flush(&self) {
self.rec.flush_blocking();
}
}
// ---
fn log_level_to_rerun_level(lvl: log::Level) -> TextLogLevel {
match lvl {
log::Level::Error => TextLogLevel::ERROR,
log::Level::Warn => TextLogLevel::WARN,
log::Level::Info => TextLogLevel::INFO,
log::Level::Debug => TextLogLevel::DEBUG,
log::Level::Trace => TextLogLevel::TRACE,
}
.into()
}