Crate uniffi::deps::log

source ·
Expand description

A lightweight logging facade.

The log crate provides a single logging API that abstracts over the actual logging implementation. Libraries can use the logging API provided by this crate, and the consumer of those libraries can choose the logging implementation that is most suitable for its use case.

If no logging implementation is selected, the facade falls back to a “noop” implementation that ignores all log messages. The overhead in this case is very small - just an integer load, comparison and jump.

A log request consists of a target, a level, and a body. A target is a string which defaults to the module path of the location of the log request, though that default may be overridden. Logger implementations typically use the target to filter requests based on some user configuration.

Usage

The basic use of the log crate is through the five logging macros: error!, warn!, info!, debug! and trace! where error! represents the highest-priority log messages and trace! the lowest. The log messages are filtered by configuring the log level to exclude messages with a lower priority. Each of these macros accept format strings similarly to println!.

In libraries

Libraries should link only to the log crate, and use the provided macros to log whatever information will be useful to downstream consumers.

Examples

use log::{info, warn};

pub fn shave_the_yak(yak: &mut Yak) {
    info!(target: "yak_events", "Commencing yak shaving for {:?}", yak);

    loop {
        match find_a_razor() {
            Ok(razor) => {
                info!("Razor located: {}", razor);
                yak.shave(razor);
                break;
            }
            Err(err) => {
                warn!("Unable to locate a razor: {}, retrying", err);
            }
        }
    }
}

In executables

Executables should choose a logging implementation and initialize it early in the runtime of the program. Logging implementations will typically include a function to do this. Any log messages generated before the implementation is initialized will be ignored.

The executable itself may use the log crate to log as well.

Warning

The logging system may only be initialized once.

Structured logging

If you enable the kv_unstable feature you can associate structured values with your log records. If we take the example from before, we can include some additional context besides what’s in the formatted message:

use log::{info, warn, as_serde, as_error};

pub fn shave_the_yak(yak: &mut Yak) {
    info!(target: "yak_events", yak = as_serde!(yak); "Commencing yak shaving");

    loop {
        match find_a_razor() {
            Ok(razor) => {
                info!(razor = razor; "Razor located");
                yak.shave(razor);
                break;
            }
            Err(err) => {
                warn!(err = as_error!(err); "Unable to locate a razor, retrying");
            }
        }
    }
}

Available logging implementations

In order to produce log output executables have to use a logger implementation compatible with the facade. There are many available implementations to choose from, here are some of the most popular ones:

Implementing a Logger

Loggers implement the Log trait. Here’s a very basic example that simply logs all messages at the Error, Warn or Info levels to stdout:

use log::{Record, Level, Metadata};

struct SimpleLogger;

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= Level::Info
    }

    fn log(&self, record: &Record) {
        if self.enabled(record.metadata()) {
            println!("{} - {}", record.level(), record.args());
        }
    }

    fn flush(&self) {}
}

Loggers are installed by calling the set_logger function. The maximum log level also needs to be adjusted via the set_max_level function. The logging facade uses this as an optimization to improve performance of log messages at levels that are disabled. It’s important to set it, as it defaults to Off, so no log messages will ever be captured! In the case of our example logger, we’ll want to set the maximum log level to Info, since we ignore any Debug or Trace level log messages. A logging implementation should provide a function that wraps a call to set_logger and set_max_level, handling initialization of the logger:

use log::{SetLoggerError, LevelFilter};

static LOGGER: SimpleLogger = SimpleLogger;

pub fn init() -> Result<(), SetLoggerError> {
    log::set_logger(&LOGGER)
        .map(|()| log::set_max_level(LevelFilter::Info))
}

Implementations that adjust their configurations at runtime should take care to adjust the maximum log level as well.

Use with std

set_logger requires you to provide a &'static Log, which can be hard to obtain if your logger depends on some runtime configuration. The set_boxed_logger function is available with the std Cargo feature. It is identical to set_logger except that it takes a Box<Log> rather than a &'static Log:

pub fn init() -> Result<(), SetLoggerError> {
    log::set_boxed_logger(Box::new(SimpleLogger))
        .map(|()| log::set_max_level(LevelFilter::Info))
}

Compile time filters

Log levels can be statically disabled at compile time via Cargo features. Log invocations at disabled levels will be skipped and will not even be present in the resulting binary. This level is configured separately for release and debug builds. The features are:

  • max_level_off
  • max_level_error
  • max_level_warn
  • max_level_info
  • max_level_debug
  • max_level_trace
  • release_max_level_off
  • release_max_level_error
  • release_max_level_warn
  • release_max_level_info
  • release_max_level_debug
  • release_max_level_trace

These features control the value of the STATIC_MAX_LEVEL constant. The logging macros check this value before logging a message. By default, no levels are disabled.

Libraries should avoid using the max level features because they’re global and can’t be changed once they’re set.

For example, a crate can disable trace level logs in debug builds and trace, debug, and info level logs in release builds with the following configuration:

[dependencies]
log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] }

Crate Feature Flags

The following crate feature flags are available in addition to the filters. They are configured in your Cargo.toml.

  • std allows use of std crate instead of the default core. Enables using std::error and set_boxed_logger functionality.
  • serde enables support for serialization and deserialization of Level and LevelFilter.
[dependencies]
log = { version = "0.4", features = ["std", "serde"] }

Version compatibility

The 0.3 and 0.4 versions of the log crate are almost entirely compatible. Log messages made using log 0.3 will forward transparently to a logger implementation using log 0.4. Log messages made using log 0.4 will forward to a logger implementation using log 0.3, but the module path and file name information associated with the message will unfortunately be lost.

Macros

  • Logs a message at the debug level.
  • Logs a message at the error level.
  • Logs a message at the info level.
  • The standard logging macro.
  • Determines if a message logged at the specified level in that module will be logged.
  • Logs a message at the trace level.
  • Logs a message at the warn level.

Structs

Enums

  • An enum representing the available verbosity levels of the logger.
  • An enum representing the available verbosity level filters of the logger.

Constants

Traits

  • A trait encapsulating the operations required of a logger.

Functions