Introduce new logging API, fixes #858
PiperOrigin-RevId: 930744288 Change-Id: I6ec1203b55c031390f3eef23192e2337508ce886
This commit is contained in:
committed by
Copybara-Service
parent
a2abaf7aef
commit
58f6d52491
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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,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>`__.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user