Skip to content

Commit

Permalink
TextLog integrations with native loggers (#3522)
Browse files Browse the repository at this point in the history
Everything to seamlessly integrate `TextLog` with native loggers in
Python & Rust.

- [How-to
guide](https://www.rerun.io/preview/154a8a0873def8fbdadc50805778aff2458f08b7/docs/howto/integration-with-native-logger)


- Requires #3525 
- Fixes #3450
  • Loading branch information
teh-cmc authored Sep 29, 2023
1 parent 3c953ca commit 9451e82
Show file tree
Hide file tree
Showing 22 changed files with 358 additions and 75 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ egui-wgpu = "0.23.0"
ehttp = "0.3.1"
emath = "0.23.0"
enumset = "1.0.12"
env_logger = "0.10"
epaint = "0.23.0"
glam = "0.22"
gltf = "1.1"
Expand All @@ -106,6 +107,7 @@ image = { version = "0.24", default-features = false }
infer = "0.15" # infer MIME type by checking the magic number signature
itertools = "0.11"
lazy_static = "1.4"
log = "0.4"
macaw = "0.18"
mimalloc = "0.1.29"
mime = "0.3"
Expand Down
6 changes: 3 additions & 3 deletions crates/re_log/src/setup.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Function to setup logging in binaries and web apps.
/// Get `RUST_LOG` environment variable or `info`, if not set.
/// Get `RUST_LOG` environment variable or `info`, if not set.
///
/// Also set some other log levels on crates that are too loud.
#[cfg(not(target_arch = "wasm32"))]
fn log_filter() -> String {
pub fn default_log_filter() -> String {
let mut rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned());

for crate_name in crate::CRATES_AT_ERROR_LEVEL {
Expand Down Expand Up @@ -36,7 +36,7 @@ pub fn setup_native_logging() {

crate::multi_logger::init().expect("Failed to set logger");

let log_filter = log_filter();
let log_filter = default_log_filter();

if log_filter.contains("trace") {
log::set_max_level(log::LevelFilter::Trace);
Expand Down
7 changes: 6 additions & 1 deletion crates/re_sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ all-features = true


[features]
default = ["demo", "glam", "image"]
default = ["demo", "glam", "image", "log"]

## Enable the `demo` module (helpers for Rerun examples).
demo = []
Expand All @@ -30,6 +30,9 @@ glam = ["re_types/glam"]
## Integration with the [`image`](https://crates.io/crates/image/) crate, plus JPEG support..
image = ["re_types/image"]

## Integration with the [`log`](https://crates.io/crates/log/) crate.
log = ["dep:env_logger", "dep:log"]

## Support serving a web viewer over HTTP.
##
## Enabling this inflates the binary size quite a bit, since it embeds the viewer wasm.
Expand Down Expand Up @@ -75,6 +78,8 @@ re_ws_comms = { workspace = true, optional = true }
re_web_viewer_server = { workspace = true, optional = true }

anyhow = { workspace = true, optional = true }
env_logger = { workspace = true, optional = true }
log = { workspace = true, optional = true }
webbrowser = { version = "0.8", optional = true }


Expand Down
11 changes: 11 additions & 0 deletions crates/re_sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ mod global;
mod log_sink;
mod recording_stream;

#[cfg(feature = "log")]
mod log_integration;

// -------------
// Public items:

Expand Down Expand Up @@ -99,6 +102,11 @@ pub use re_types::{
MaybeOwnedComponentBatch, NamedIndicatorComponent,
};

#[cfg(feature = "log")]
pub use self::log_integration::Logger;
#[cfg(feature = "log")]
pub use re_log::default_log_filter;

/// Methods for spawning the web viewer and streaming the SDK log stream to it.
#[cfg(feature = "web_viewer")]
pub mod web_viewer;
Expand All @@ -114,6 +122,9 @@ pub mod external {
pub use re_log::external::*;
pub use re_log_types::external::*;
pub use re_types::external::*;

#[cfg(feature = "log")]
pub use log;
}

// -----
Expand Down
119 changes: 119 additions & 0 deletions crates/re_sdk/src/log_integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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.
#[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.
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
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()
}
2 changes: 2 additions & 0 deletions crates/re_types/definitions/rerun/archetypes/text_log.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace rerun.archetypes;
// ---

/// A log entry in a text log, comprised of a text body and its log level.
///
/// \example text_log_integration image="https://static.rerun.io/text_log_integration/9737d0c986325802a9885499d6fcc773b1736488/1200w.png"
table TextLog (
"attr.rust.derive": "PartialEq, Eq"
) {
Expand Down
37 changes: 37 additions & 0 deletions crates/re_types/src/archetypes/text_log.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion crates/rerun/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]


[features]
default = ["analytics", "demo", "glam", "image", "sdk", "server"]
default = ["analytics", "demo", "glam", "image", "log", "sdk", "server"]

## Enable telemetry using our analytics SDK.
analytics = [
Expand All @@ -40,6 +40,9 @@ glam = ["re_sdk?/glam"]
## Integration with the [`image`](https://crates.io/crates/image/) crate, plus JPEG support..
image = ["re_sdk?/image"]

## Integration with the [`log`](https://crates.io/crates/log/) crate.
log = ["re_sdk?/log"]

## Support spawning a native viewer.
## This adds a lot of extra dependencies, so only enable this feature if you need it!
native_viewer = ["dep:re_viewer"]
Expand Down
4 changes: 4 additions & 0 deletions docs/code-examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ path = "text_document.rs"
name = "text_log"
path = "text_log.rs"

[[bin]]
name = "text_log_integration"
path = "text_log_integration.rs"

[[bin]]
name = "transform3d_simple"
path = "transform3d_simple.rs"
Expand Down
2 changes: 1 addition & 1 deletion docs/code-examples/roundtrips.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"segmentation_image_simple": ["cpp"],
"tensor_one_dim": ["cpp"],
"tensor_simple": ["cpp"],
"text_log_integration": ["cpp", "rust"], # Missing example for Rust
"text_log_integration": ["cpp"],
"view_coordinates_simple": ["cpp"], # TODO(#2919): Need log_timeless for C++

# This is this script, it's not an example.
Expand Down
3 changes: 1 addition & 2 deletions docs/code-examples/text_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@

rr.init("rerun_example_text_log", spawn=True)

# TODO(emilk): show how to hook up to the log stream.
rr.log("log", rr.TextLog("Application started.", level="INFO"))
rr.log("log", rr.TextLog("Application started.", level=rr.TextLogLevel.INFO))
1 change: 0 additions & 1 deletion docs/code-examples/text_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use rerun::{archetypes::TextLog, RecordingStreamBuilder};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (rec, storage) = RecordingStreamBuilder::new("rerun_example_text_log").memory()?;

// TODO(emilk): show how to hook up to the log stream of the `log` crate.
rec.log(
"log",
&TextLog::new("Application started.").with_level("INFO"),
Expand Down
10 changes: 5 additions & 5 deletions docs/code-examples/text_log_integration.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Log some text entries."""
"""Shows integration of Rerun's `TextLog` with the native logging interface."""
import logging

import rerun as rr

rr.init("rerun_example_text_entry", spawn=True)
rr.init("rerun_example_text_log_integration", spawn=True)

# Log a direct entry directly
rr.log_text_entry("logs", "this entry has loglevel TRACE", level="TRACE")
# Log a text entry directly
rr.log("logs", rr.TextLog("this entry has loglevel TRACE", level=rr.TextLogLevel.TRACE))

# Or log via a logging handler
logging.getLogger().addHandler(rr.LoggingHandler("logs/handler"))
logging.getLogger().setLevel(-1)
logging.info("This log got added through a `LoggingHandler`")
logging.info("This INFO log got added through the standard logging interface")
25 changes: 25 additions & 0 deletions docs/code-examples/text_log_integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! Shows integration of Rerun's `TextLog` with the native logging interface.
use rerun::{archetypes::TextLog, components::TextLogLevel, external::log, RecordingStreamBuilder};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let (rec, storage) =
RecordingStreamBuilder::new("rerun_example_text_log_integration").memory()?;

// Log a text entry directly:
rec.log(
"logs",
&TextLog::new("this entry has loglevel TRACE").with_level(TextLogLevel::TRACE),
)?;

// Or log via a logging handler:
rerun::Logger::new(rec.clone()) // recording streams are ref-counted
.with_path_prefix("logs/handler")
// You can also use the standard `RUST_LOG` environment variable!
.with_filter(rerun::default_log_filter())
.init()?;
log::info!("This INFO log got added through the standard logging interface");

rerun::native_viewer::show(storage.take())?;
Ok(())
}
Loading

0 comments on commit 9451e82

Please sign in to comment.