Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TextLog integrations with native loggers #3522

Merged
merged 11 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary .into()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TextLogLevel::TRACE & friends are actually &strs because TextLogLevel isn't const

}
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())
teh-cmc marked this conversation as resolved.
Show resolved Hide resolved
.init()?;
log::info!("This INFO log got added through the standard logging interface");

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