Introduce new logging API, fixes #858

PiperOrigin-RevId: 930744288
Change-Id: I6ec1203b55c031390f3eef23192e2337508ce886
This commit is contained in:
Yuval Tassa
2026-06-11 14:36:17 -07:00
committed by Copybara-Service
parent a2abaf7aef
commit 58f6d52491
45 changed files with 2586 additions and 422 deletions
+16 -9
View File
@@ -32,10 +32,14 @@ you are simulating multiple models in parallel, they use the same set of callbac
mju_user_error
~~~~~~~~~~~~~~
This is called from within the main error function :ref:`mju_error`. When installed, this function overrides the default
error processing. Once it prints error messages (or whatever else the user wants to do), it must **exit** the program.
MuJoCo is written with the assumption that mju_error will not return. If it does, the behavior of the software is
undefined.
.. deprecated::
Use :ref:`mju_setLogHandler` instead. See :ref:`siLogHandler`.
Called by the default log handler when a fatal error occurs. If installed, this function overrides the default error
processing. It may ``longjmp`` out or return. MuJoCo is written with the assumption that error handlers will not
return; if they do, the behavior of the software is undefined.
If a custom log handler is installed via :ref:`mju_setLogHandler`, this callback is not consulted.
.. code-block:: C
@@ -47,8 +51,11 @@ undefined.
mju_user_warning
~~~~~~~~~~~~~~~~
This is called from within the main warning function :ref:`mju_warning`. It is similar to the error handler, but instead
it must return without exiting the program.
.. deprecated::
Use :ref:`mju_setLogHandler` instead. See :ref:`siLogHandler`.
Called by the default log handler when a warning occurs. If a custom log handler is installed via
:ref:`mju_setLogHandler`, this callback is not consulted.
.. code-block:: C
@@ -185,9 +192,9 @@ mjcb_time
~~~~~~~~~
Installing this callback enables the built-in profiler, and keeps timing statistics in ``mjData.timer``. The return type
is mjtNum, while the time units are up to the user. :ref:`simulate.cc <saSimulate>` assumes the unit is 1 millisecond.
In order to be useful, the callback should use high-resolution timers with at least microsecond precision. This is
because the computations being timed are very fast.
is mjtNum, while the time units are up to the user. Both :ref:`simulate.cc <saSimulate>` and the ``mjTOPIC_TIME_STP``
informational :ref:`topic <mjtLogTopic>` assume the unit is 1 millisecond. In order to be useful, the callback should
use high-resolution timers with at least microsecond precision.
.. code-block:: C
+77
View File
@@ -540,6 +540,33 @@ Sleep state of an object.
.. mujoco-include:: mjtSleepState
.. _tyLogEnums:
Logging
~~~~~~~
.. _mjtLogLevel:
mjtLogLevel
"""""""""""
Log message severity level.
.. mujoco-include:: mjtLogLevel
.. _mjtLogTopic:
mjtLogTopic
"""""""""""
Topic identifiers for informational messages. Used with :ref:`mju_info` for topic-based filtering.
Topic 0 (``mjTOPIC_NONE``) always passes through the default handler's filter. Other topics must be enabled in
the :ref:`mjLogConfig` bitmask. Since topics are 1-indexed, the bitmask for topic ``t`` is ``(1 << (t - 1))``.
.. mujoco-include:: mjtLogTopic
.. _tyVisEnums:
Visualization
@@ -1059,6 +1086,36 @@ Asset cache used by the compiler to avoid repeated slow recompilation. See :ref:
.. mujoco-include:: mjCache
.. _tyLogStructure:
Logging
^^^^^^^
.. _mjLogMessage:
mjLogMessage
~~~~~~~~~~~~
Structured log message passed to :ref:`mjfLogHandler` callbacks. Contains the severity level, optional topic for
info messages, a one-line subject, an optional multi-line body, and optional source location (function name, file
name, line number).
.. mujoco-include:: mjLogMessage
.. _mjLogConfig:
mjLogConfig
~~~~~~~~~~~
Configuration for the default log handler. Controls whether messages are printed to the console and/or written to
a log file (default: ``MUJOCO_LOG.TXT``). The ``logto_file`` field enables file logging, while ``logfile`` specifies
the file path. The ``topics`` field is a bitmask of :ref:`mjtLogTopic` values: bit ``(topic - 1)`` enables
that topic. Topic 0 (``mjTOPIC_NONE``) always passes through.
.. mujoco-include:: mjLogConfig
.. _tyStatStructure:
Sim statistics
@@ -1836,6 +1893,26 @@ mjfCollision
This is the function type of the callbacks in the collision table :ref:`mjCOLLISIONFUNC`.
.. _tyLogCallbacks:
Log Callbacks
^^^^^^^^^^^^^
.. _mjfLogHandler:
mjfLogHandler
~~~~~~~~~~~~~
.. code-block:: C
typedef void (*mjfLogHandler)(const mjLogMessage*);
This is the function type of the log handler callback installed via :ref:`mju_setLogHandler`. The handler receives
all errors, warnings and informational messages as structured :ref:`mjLogMessage` data. It must be thread-safe.
It must not call :ref:`mju_error` from within the callback.
.. _tyUICallbacks:
UI Callbacks
+112 -3
View File
@@ -1971,7 +1971,9 @@ Error and memory
.. mujoco-include:: mju_error
Main error function; does not return to caller.
Main error function. The error message is dispatched to the active log handler (see :ref:`mju_setLogHandler`).
Errors are always fatal: if the handler returns, the process is terminated with ``exit(EXIT_FAILURE)``. Handlers
wishing to recover must ``longjmp`` or otherwise transfer control before returning.
.. _mju_warning:
@@ -1980,7 +1982,7 @@ Main error function; does not return to caller.
.. mujoco-include:: mju_warning
Main warning function; returns to caller.
Main warning function; returns to caller. The warning message is dispatched to the active log handler.
.. _mju_clearHandlers:
@@ -1989,7 +1991,114 @@ Main warning function; returns to caller.
.. mujoco-include:: mju_clearHandlers
Clear user error and memory handlers.
Clear all user handlers and restore defaults. Resets the legacy error/warning/memory callbacks to ``NULL``, restores
the default log handler, and resets the log configuration to its defaults (console and file output enabled, all info
topics disabled).
.. _mju_setLogHandler:
`mju_setLogHandler <#mju_setLogHandler>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mju_setLogHandler
Set the active global log handler. Returns the previous handler (which is never ``NULL``), intended for save/restore
or callback chaining. If ``handler`` is ``NULL``, the default handler is restored. The handler receives all errors,
warnings and informational messages as a structured :ref:`mjLogMessage`. See :ref:`siLogHandler` for usage examples.
.. _mju_getLogConfig:
`mju_getLogConfig <#mju_getLogConfig>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mju_getLogConfig
Get the current default handler configuration. See :ref:`mjLogConfig`.
.. _mju_setLogConfig:
`mju_setLogConfig <#mju_setLogConfig>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mju_setLogConfig
Set the default handler configuration. Controls console output, file output, and info topic filtering.
See :ref:`mjLogConfig`.
Example usage (disabling file output):
.. code-block:: C
mjLogConfig config = mju_getLogConfig();
config.logto_file = false;
mju_setLogConfig(config);
.. _mju_info:
`mju_info <#mju_info>`__
~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mju_info
Log an informational message with optional topic filtering. The ``topic`` argument is a :ref:`mjtLogTopic` value.
Topic 0 (``mjTOPIC_NONE``) always passes through. Other topics must be enabled in the default handler configuration
via :ref:`mju_setLogConfig`. Note that topic filtering is implemented in the default handler; custom handlers
receive all info messages regardless.
.. _mju_message:
`mju_message <#mju_message>`__
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. mujoco-include:: mju_message
Dispatch a structured :ref:`mjLogMessage` to the active log handler. This is the primary entry point for emitting
log messages with full control over all fields. The convenience functions :ref:`mju_error`, :ref:`mju_warning`, and
:ref:`mju_info` are thin wrappers that populate an ``mjLogMessage`` and call this function.
The ``subject`` field is a one-line summary (up to 1024 bytes, inline in the struct). The ``body`` field is an
optional ``const char*`` pointer to multi-line detail text, owned by the caller. When ``body`` is ``NULL``, only the
subject line is printed.
The default handler formats the output as follows:
.. code-block:: text
LEVEL FUNC (FILE:LINE) TIME: SUBJECT
BODY
where:
- ``LEVEL`` is ``ERROR``, ``WARNING``, ``INFO``, or ``DEBUG``.
- ``FUNC`` is present when the ``func`` field is set.
- ``(FILE:LINE)`` is present when the ``file`` and ``line`` fields are set.
- ``TIME`` is present when the ``timestamp`` field is set or file logging is active.
- ``SUBJECT`` is the contents of the ``subject`` field.
- ``BODY`` follows on the next line(s), printed raw without indentation or separators, only if non-NULL.
The default handler appends a trailing blank line after ``ERROR``, ``WARNING``, and ``INFO`` messages for visual
separation. ``DEBUG`` messages are printed compactly without a trailing blank line.
Example usage:
.. code-block:: C
mjLogMessage msg = {
.level = mjLOG_INFO,
.timestamp = true,
.body = " height: 0.001 m\n velocity: 0.000 m/s\n bounces: 47",
};
snprintf(msg.subject, sizeof(msg.subject), "The ball has come to rest");
mju_message(&msg);
This produces:
.. code-block:: text
INFO Mon Jun 9 15:04:05 2026: The ball has come to rest
height: 0.001 m
velocity: 0.000 m/s
bounces: 47
.. _mju_malloc:
+96
View File
@@ -463,6 +463,102 @@ time, while :ref:`mjui_update` is called only when changes in the UI take place.
.. _Errorandmemory:
.. _mju_error:
Main error function. The error message is dispatched to the active log handler (see :ref:`mju_setLogHandler`).
Errors are always fatal: if the handler returns, the process is terminated with ``exit(EXIT_FAILURE)``. Handlers
wishing to recover must ``longjmp`` or otherwise transfer control before returning.
.. _mju_warning:
Main warning function; returns to caller. The warning message is dispatched to the active log handler.
.. _mju_clearHandlers:
Clear all user handlers and restore defaults. Resets the legacy error/warning/memory callbacks to ``NULL``, restores
the default log handler, and resets the log configuration to its defaults (console and file output enabled, all info
topics disabled).
.. _mju_setLogHandler:
Set the active global log handler. Returns the previous handler (which is never ``NULL``), intended for save/restore
or callback chaining. If ``handler`` is ``NULL``, the default handler is restored. The handler receives all errors,
warnings and informational messages as a structured :ref:`mjLogMessage`. See :ref:`siLogHandler` for usage examples.
.. _mju_getLogConfig:
Get the current default handler configuration. See :ref:`mjLogConfig`.
.. _mju_setLogConfig:
Set the default handler configuration. Controls console output, file output, and info topic filtering.
See :ref:`mjLogConfig`.
Example usage (disabling file output):
.. code-block:: C
mjLogConfig config = mju_getLogConfig();
config.logto_file = false;
mju_setLogConfig(config);
.. _mju_info:
Log an informational message with optional topic filtering. The ``topic`` argument is a :ref:`mjtLogTopic` value.
Topic 0 (``mjTOPIC_NONE``) always passes through. Other topics must be enabled in the default handler configuration
via :ref:`mju_setLogConfig`. Note that topic filtering is implemented in the default handler; custom handlers
receive all info messages regardless.
.. _mju_message:
Dispatch a structured :ref:`mjLogMessage` to the active log handler. This is the primary entry point for emitting
log messages with full control over all fields. The convenience functions :ref:`mju_error`, :ref:`mju_warning`, and
:ref:`mju_info` are thin wrappers that populate an ``mjLogMessage`` and call this function.
The ``subject`` field is a one-line summary (up to 1024 bytes, inline in the struct). The ``body`` field is an
optional ``const char*`` pointer to multi-line detail text, owned by the caller. When ``body`` is ``NULL``, only the
subject line is printed.
The default handler formats the output as follows:
.. code-block:: text
LEVEL FUNC (FILE:LINE) TIME: SUBJECT
BODY
where:
- ``LEVEL`` is ``ERROR``, ``WARNING``, ``INFO``, or ``DEBUG``.
- ``FUNC`` is present when the ``func`` field is set.
- ``(FILE:LINE)`` is present when the ``file`` and ``line`` fields are set.
- ``TIME`` is present when the ``timestamp`` field is set or file logging is active.
- ``SUBJECT`` is the contents of the ``subject`` field.
- ``BODY`` follows on the next line(s), printed raw without indentation or separators, only if non-NULL.
The default handler appends a trailing blank line after ``ERROR``, ``WARNING``, and ``INFO`` messages for visual
separation. ``DEBUG`` messages are printed compactly without a trailing blank line.
Example usage:
.. code-block:: C
mjLogMessage msg = {
.level = mjLOG_INFO,
.timestamp = true,
.body = " height: 0.001 m\n velocity: 0.000 m/s\n bounces: 47",
};
snprintf(msg.subject, sizeof(msg.subject), "The ball has come to rest");
mju_message(&msg);
This produces:
.. code-block:: text
INFO Mon Jun 9 15:04:05 2026: The ball has come to rest
height: 0.001 m
velocity: 0.000 m/s
bounces: 47
.. _Standardmath:
The "functions" in this section are preprocessor macros replaced with the corresponding C standard library math
+10
View File
@@ -10,6 +10,16 @@ General
- Added :ref:`mju_threadpool`, a new function for creating a thread pool on an ``mjData`` instance. When a thread pool
is initialized, parts of the simulation pipeline, such as collision detection and constraint solving across islands,
are parallelized. The thread pool is automatically destroyed when the ``mjData`` is freed.
- Added a unified :ref:`logging API<siError>`:
- All errors, warnings, and informational messages are now routed through a single :ref:`mjfLogHandler` callback
receiving a structured :ref:`mjLogMessage`.
- Users can install a custom handler via :ref:`mju_setLogHandler`,
configure the default handler's behavior (console/file output, topic filtering) via :ref:`mju_setLogConfig`.
- Messages can be emitted via :ref:`mju_info` and :ref:`mju_message`.
- New types: :ref:`mjtLogLevel`, :ref:`mjtLogTopic`, :ref:`mjLogMessage`, :ref:`mjLogConfig`.
- The legacy callbacks :ref:`mju_user_error` and :ref:`mju_user_warning` are deprecated but remain functional.
- Improved primal solver convergence under float32. Improvements initially proposed by :github:user:`n3b` in
:issue:`2313` and :github:user:`denzeler-nvidia` in :doc:`MJWarp <mjwarp/index>` pull request
`1374 <https://github.com/google-deepmind/mujoco_warp/pull/1374>`__.
+37
View File
@@ -2619,6 +2619,38 @@ typedef enum mjtSleepState_ { // sleep state of an object
mjS_ASLEEP = 0, // object is asleep
mjS_AWAKE = 1 // object is awake
} mjtSleepState;
typedef enum mjtLogLevel_ { // log message severity
mjLOG_DEBUG = 0, // internal engine debug trace (opt-in via topic filtering)
mjLOG_INFO, // informational (opt-in via topic filtering)
mjLOG_WARNING, // warning
mjLOG_ERROR, // error
} mjtLogLevel;
typedef enum mjtLogTopic_ { // log topic identifiers
mjTOPIC_NONE = 0, // no topic (always passes filtering)
// INFO topics:
mjTOPIC_TIME_STP = 1, // timing diagnostics (step)
mjTOPIC_TIME_CMP = 2, // timing diagnostics (compile)
// DEBUG topics:
mjTOPIC_SLEEP = 3, // sleep/wake events
mjNTOPIC = 3 // number of filterable topics
} mjtLogTopic;
typedef struct mjLogMessage_ { // structured log message
int level; // mjtLogLevel
int topic; // mjtLogTopic (0 for error/warning/user)
char subject[1024]; // message subject (one-liner, printf-formatted)
const char* body; // message body (multi-line detail, or NULL)
const char* func; // __func__ or NULL
const char* file; // __FILE__ or NULL
int line; // __LINE__ or 0
mjtBool timestamp; // prepend timestamp to output
} mjLogMessage;
typedef struct mjLogConfig_ { // log handler default configuration
mjtBool logto_console; // print to console (default: true)
mjtBool logto_file; // print to log file (default: true)
char logfile[1024]; // log file path (default: "MUJOCO_LOG.TXT")
int topics; // enabled info topic bitmask (default: 0)
} mjLogConfig;
typedef enum mjtButton_ { // mouse button
mjBUTTON_NONE = 0, // no button
mjBUTTON_LEFT, // left button
@@ -3495,6 +3527,11 @@ void mjui_render(mjUI* ui, const mjuiState* state, const mjrContext* con);
void mju_error(const char* msg, ...) mjPRINTFLIKE(1, 2);
void mju_warning(const char* msg, ...) mjPRINTFLIKE(1, 2);
void mju_clearHandlers(void);
mjfLogHandler mju_setLogHandler(mjfLogHandler handler);
mjLogConfig mju_getLogConfig(void);
void mju_setLogConfig(mjLogConfig config);
void mju_info(int topic, const char* msg, ...) mjPRINTFLIKE(2, 3);
void mju_message(const mjLogMessage* msg);
void* mju_malloc(size_t size);
void mju_free(void* ptr);
void mj_warning(mjData* d, int warning, int info);
+161 -29
View File
@@ -886,20 +886,79 @@ and :ref:`mj_stackAllocByte` is provided for allocation of arbitrary number of b
.. _siError:
Errors and warnings
~~~~~~~~~~~~~~~~~~~
Errors, warnings, logging
~~~~~~~~~~~~~~~~~~~~~~~~~
When a terminal error occurs, MuJoCo calls the function :ref:`mju_error` internally. Here is what mju_error does:
MuJoCo has a unified logging system for errors, warnings and informational messages. All log output is routed
through a single callback of type :ref:`mjfLogHandler`, which receives a structured :ref:`mjLogMessage`
containing the severity level, message text, and optional source location. Errors are fatal and terminate the
program by default. Warnings indicate problematic but non-fatal conditions. Informational messages provide
optional diagnostic output.
#. Append the error message at the end of the file MUJOCO_LOG.TXT in the program directory (create the file if it does
not exist). Also write the date and time along with the error message.
#. If the user error callback :ref:`mju_user_error` is installed, call that function with the error message as
argument. Otherwise, print the error message and "Press Enter to exit..." to standard output. Then wait for any
keyboard input, and then terminate the simulator with failure.
.. _siLogHandler:
If a user error callback is installed, it must **not** return, otherwise the behavior of the simulator is undefined.
The idea here is that if mju_error is called, the simulation cannot continue and the user is expected to make some
change such that the error condition is avoided. The error messages are self-explanatory.
Installing a handler
^^^^^^^^^^^^^^^^^^^^
Users who want to intercept and process MuJoCo's log output should install a log handler using
:ref:`mju_setLogHandler`. The handler receives all errors, warnings and info messages as a single structured
callback:
.. code-block:: C
void my_handler(const mjLogMessage* msg) {
// do something with msg, for example:
printf("%s\n", msg->subject);
}
// install handler, save previous
mjfLogHandler prev = mju_setLogHandler(my_handler);
// ... do work ...
// restore previous handler
mju_setLogHandler(prev);
:ref:`mju_setLogHandler` returns the previously installed handler (which is never ``NULL``; passing ``NULL`` restores
the default handler). The previous handler can be used in two ways:
* **Save/restore**: A library or subsystem can temporarily install its own handler and later restore the previous one.
* **Chaining**: A custom handler can act as a pure observer by calling the previous handler at the end of its callback
to preserve existing behavior. Conversely, handlers intended to intercept and recover from errors (e.g., via
``longjmp``) should not chain to the previous handler.
When the handler is called with ``level == mjLOG_ERROR``, the error is always fatal: the :ref:`default handler
<siDefaultHandler>` terminates the process with ``exit(EXIT_FAILURE)`` (unless a legacy error handler is installed).
Handlers that wish to recover from errors (e.g., to throw a C++ exception or convert to a Python exception) must not
return — they should ``longjmp`` to a previously established recovery point or otherwise transfer control before
returning. This is how the compiler and Python bindings handle errors. MuJoCo is written with the assumption that
error handlers will not return; if they do, the behavior of the software is undefined.
.. warning::
Log handlers must not call :ref:`mju_error` from within the callback; this will cause infinite recursion.
.. _siDefaultHandler:
Default handler
^^^^^^^^^^^^^^^
If no custom handler is installed (or if ``NULL`` is passed to :ref:`mju_setLogHandler`), MuJoCo uses a default
handler that provides the following behavior:
#. If a legacy handler (:ref:`mju_user_error` or :ref:`mju_user_warning`) is installed, it is called with the
formatted message text. This provides backward compatibility with existing code.
#. Otherwise, the message is written to the log file (default: ``MUJOCO_LOG.TXT``) and printed to the
console (``stderr`` for errors and warnings, ``stdout`` for info).
#. For errors, the program is terminated with ``exit(EXIT_FAILURE)`` (unless a legacy error handler is installed).
The default handler's behavior can be configured using :ref:`mju_setLogConfig` and :ref:`mju_getLogConfig`,
which control whether output goes to the console, the log file path (or disabling file logging by setting it to
an empty string), and which info topics are enabled.
.. _siErrorRecovery:
Error recovery
^^^^^^^^^^^^^^
One situation where it is desirable to continue even after an error is an interactive simulator that fails to load a
model file. This could be because the user provided the wrong file name, or because model compilation failed. This is
@@ -909,26 +968,99 @@ operation fails, and there is no need to exit the program. In the case of mj_loa
containing the parser or compiler error that caused the failure, while mj_loadModel generates corresponding warnings
(see below).
Internally mj_loadXML actually uses the mju_error mechanism, by temporarily installing a "user" handler that triggers
a C++ exception, which is then intercepted. This is possible because the parser, compiler and runtime are compiled and
linked together, and use the same copy of the C/C++ memory manager and standard library. If the user implements an
error callback that triggers a C++ exception, this will be in their workspace which is not necessarily the same as the
MuJoCo library workspace, and so it is not clear what will happen; the outcome probably depends on the compiler and
platform. It is better to avoid this approach and simply exit when mju_error is called (which is the default behavior
in the absence of a user handler).
Internally mj_loadXML actually uses the mju_error mechanism, by temporarily installing a thread-local handler
(using the internal ``_mjPRIVATE_setTlsLogHandler``) that triggers a C++ exception, which is then intercepted.
This thread-local override takes priority over the global handler and affects only the calling thread.
MuJoCo can also generate warnings. They indicate conditions that are likely to cause numerical inaccuracies, but can
also indicate problems with loading a model and other problematic situations where the simulator is nevertheless able
to continue normal operation. The warning mechanism has two levels. The high-level is implemented with the function
:ref:`mj_warning`. It registers a warning in mjData as explained in more detail in the :ref:`diagnostics
<siDiagnostics>` section below, and also calls the low-level function :ref:`mju_warning`. Alternatively, the low-level
function may be called directly (from within mj_loadModel for example) without registering a warning in mjData. This
is done in places where mjData is not available.
.. _siInfoMessages:
mju_warning does the following: if the user callback :ref:`mju_user_warning` is installed, it calls that callback.
Otherwise it appends the warning message to MUJOCO_LOG.TXT and also does a printf, similar to mju_error but without
exiting. When MuJoCo wrappers are developed for environments such as MATLAB, it makes sense to install a user callback
which prints warnings in the command window (with mexPrintf).
Informational messages
^^^^^^^^^^^^^^^^^^^^^^
MuJoCo provides two :ref:`levels<mjtLogLevel>` of opt-in diagnostic logging: informational messages (``mjLOG_INFO``) and
debug traces (``mjLOG_DEBUG``). Both use topic identifiers from the :ref:`mjtLogTopic` enum, but have a subtle
architectural distinction in how filtering is applied:
* **INFO messages**: Emitted unconditionally by the engine. Filtering happens on the **consumer side** inside the
default handler. Custom handlers installed via :ref:`mju_setLogHandler` receive all INFO messages and
can implement their own filtering logic.
* **DEBUG messages**: Designed for tight, high-frequency simulation loops where constructing strings would be a
performance bottleneck. Therefore, filtering happens on the **producer side** via :ref:`mju_isTopicEnabled`. If a
topic is disabled, the message is never constructed or dispatched. Consequently, custom handlers will only receive
DEBUG messages if the topic is explicitly enabled in the active :ref:`mjLogConfig`.
In the default handler, INFO messages are followed by a blank line for readability, whereas high-frequency DEBUG traces
are printed compactly without trailing blank lines.
To enable topics in the default handler configuration:
.. code-block:: C
// enable sleep/wake messages
mjLogConfig config = mju_getLogConfig();
config.topics |= (1 << (mjTOPIC_SLEEP - 1));
mju_setLogConfig(config);
Topic 0 (``mjTOPIC_NONE``) always passes through, regardless of the topic configuration.
Note that topics are 1-indexed, so the bitmask for topic ``t`` is ``(1 << (t - 1))``. This is also how the
``topics`` field of :ref:`mjLogConfig` is encoded.
Topics can also be enabled via the environment variable ``MUJOCO_LOG_TOPICS``, which is read once at startup.
The value is a comma-separated list of topic names (case-insensitive), derived from the :ref:`mjtLogTopic` enum
by removing the ``mjTOPIC_`` prefix and lowercasing (e.g., ``mjTOPIC_SLEEP`` becomes ``sleep``).
For example:
.. code-block:: shell
export MUJOCO_LOG_TOPICS=sleep,time_stp
This is equivalent to programmatically enabling the corresponding topic bits via :ref:`mju_setLogConfig`, and is
useful for enabling diagnostics without modifying code.
.. _siLogFrameworks:
Frameworks and wrappers
^^^^^^^^^^^^^^^^^^^^^^^
Framework authors (e.g., those building Python bindings, MATLAB wrappers, or game engine integrations) should
install a custom log handler to route MuJoCo's output to their environment's logging system:
.. code-block:: C
// example: route to a framework's logging API
void framework_handler(const mjLogMessage* msg) {
if (msg->level == mjLOG_ERROR) {
framework_log_error(msg->subject);
framework_abort(); // must not return
} else if (msg->level == mjLOG_WARNING) {
framework_log_warning(msg->subject);
} else {
framework_log_info(msg->subject);
}
}
mju_setLogHandler(framework_handler);
The :ref:`mjLogMessage` struct also provides source location information (``func``, ``file``, ``line``) when
available, which can be useful for debugging.
.. _siLogLegacy:
Legacy handlers
^^^^^^^^^^^^^^^
The global function pointers :ref:`mju_user_error` and :ref:`mju_user_warning` are still supported for backward
compatibility, but are deprecated in favor of :ref:`mju_setLogHandler`. When both a custom log handler and legacy
handlers are installed, the custom log handler takes precedence. The legacy handlers are only consulted by the
*default* handler when no custom handler has been installed.
.. _siLogMemory:
Memory handlers
^^^^^^^^^^^^^^^
When MuJoCo allocates and frees memory on the heap, it always uses the functions :ref:`mju_malloc` and
:ref:`mju_free`. These functions call the user callbacks :ref:`mju_user_malloc` and :ref:`mju_user_free` when