Logging

MuJoCo uses its own logging system, more in detail described in their documentation (C code guide).

Pure wrappers

We provide safe wrappers around their logging API, available in the mujoco_rs::wrappers::mj_logging module. The module provides abstractions over the raw logging functions, such as log_info, log_message and others. The default MuJoCo handler is configured via set_log_config, which is an unsafe function: MuJoCo keeps the configuration in one non-atomic global that every logging call reads, so the caller must hold every other thread that uses MuJoCo.

log_error exits the program to adhere to the MuJoCo’s internal error assumptions.

Rust ecosystem integration

Rust has a de facto standard of doing logging, which is done via the log crate. We provide a hook for integrating the MuJoCo logging with the log crate. The hook can be installed by calling install_logging_hook once at the start of the program.

Note

Installing the hook, through either function, replaces MuJoCo’s own handler, so the logto_console, logto_file and logfile fields of MjLogConfig no longer reach the output. The install also writes every topic into the global topic bitmask once, so a bitmask that the program set before the install is lost. The producer-side topic gate still reads that bitmask, so a set_log_config call after the install still suppresses the topics it clears.

Attention

install_logging_hook and set_log_handler are unsafe. MuJoCo keeps the log handler and the handler configuration in two globals that it writes and reads without synchronization, so a thread that emits a message during the installation races with it. Call either function at the start of the program, before any thread steps a simulation, loads a model, or emits a message.

A message with level MjtLogLevel::mjLOG_ERROR still ends the process to adhere to MuJoCo’s error assumptions, thus avoiding possible undefined behaviors.

An example (using the env_logger backend):

use mujoco_rs::prelude::*;

fn main() {
    env_logger::init();  // Any `log` backend works.
    // SAFETY: no other thread uses MuJoCo yet.
    unsafe { install_logging_hook() };

    // log's logging function
    log::warn!("this is an example warning!");

    // MuJoCo's logging function
    log_warning("the log backend receives this, with the target 'mujoco'");
}

Run the example with RUST_LOG=warn, because env_logger displays only errors by default.

In the above example, the log’s target is set to the Rust-module path when called from the user’s code. The message is displayed in a format similar to the following:

[WARN <module path>] this is an example warning!

When calling log_warning from the user’s program, or when MuJoCo tries to log internally, the message is displayed in a format similar to the following:

[WARN mujoco<:: optional sub-target>] the log backend receives this, with the target ‘mujoco’

Most MuJoCo logging messages will have no specific sub-topic, thus the format will be something like:

[WARN mujoco] …

The translation from a MuJoCo logging topic (MjtLogTopic) to the log target is as follows:

Topic

log target

mjTOPIC_NONE

mujoco

mjTOPIC_TIME_STP

mujoco::time_stp

mjTOPIC_TIME_CMP

mujoco::time_cmp

mjTOPIC_SLEEP

mujoco::sleep

The MuJoCo levels mjLOG_DEBUG, mjLOG_INFO, mjLOG_WARNING and mjLOG_ERROR map onto log’s Debug, Info, Warn and Error.

See Logging example.

Custom logging callback

In addition to the default log integration, the hook also accepts a single safe callback function that takes a regular Rust reference to the MjLogMessage, containing the requested message to log.

The user callback function can be registered via set_log_handler.

When setting a custom logging callback inside MuJoCo-rs, there is no need to call install_logging_hook separately, because the set_log_handler does it automatically.

All log_ function calls (and internal MuJoCo calls) will now be redirected to the handler:

use mujoco_rs::prelude::*;

fn handler(message: &MjLogMessage) {
    println!("{:?}: {}", message.level(), message.subject());
}

fn main() {
    // SAFETY: no other thread uses MuJoCo yet.
    unsafe { set_log_handler(handler) };
    log_warning("the handler receives this, and the `log` backend receives nothing");
}

Attention

Setting a custom handler using only set_log_handler only redirects MuJoCo messages to the user handler.

Any message emitted using log:: invocations bypass the handler. This also applies to the internal invocations of MuJoCo-rs as well. For a custom callback of that, a custom logger can be defined. Such logger can also process the messages from MuJoCo itself, provided set_log_handler is not used.