Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
16 changes: 10 additions & 6 deletions dm_control/_render/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@
import collections
import os

from absl import logging
import logging
from dm_control._render import constants

# Import-time status logging must not install a handler on the application root
# logger. A named stdlib logger leaves logging.basicConfig() available to users.
_LOGGER = logging.getLogger(__name__)

BACKEND = os.environ.get(constants.MUJOCO_GL)


Expand Down Expand Up @@ -85,24 +89,24 @@ def no_renderer(*args, **kwargs):
raise RuntimeError(
'Environment variable {} must be one of {!r}: got {!r}.'
.format(constants.MUJOCO_GL, sorted(all_names), BACKEND))
logging.info('MUJOCO_GL=%s, attempting to import specified OpenGL backend.',
_LOGGER.info('MUJOCO_GL=%s, attempting to import specified OpenGL backend.',
BACKEND)
Renderer = import_func()
else:
logging.info('MUJOCO_GL is not set, so an OpenGL backend will be chosen '
_LOGGER.info('MUJOCO_GL is not set, so an OpenGL backend will be chosen '
'automatically.')
# Otherwise try importing them in descending order of priority until
# successful.
for names, import_func in _ALL_RENDERERS:
try:
Renderer = import_func()
BACKEND = names[0]
logging.info('Successfully imported OpenGL backend: %s', names[0])
_LOGGER.info('Successfully imported OpenGL backend: %s', names[0])
break
except ImportError:
logging.info('Failed to import OpenGL backend: %s', names[0])
_LOGGER.info('Failed to import OpenGL backend: %s', names[0])
if BACKEND is None:
logging.info('No OpenGL backend could be imported. Attempting to create a '
_LOGGER.info('No OpenGL backend could be imported. Attempting to create a '
'rendering context will result in a RuntimeError.')
Renderer = _no_renderer()

Expand Down
61 changes: 61 additions & 0 deletions dm_control/_render/import_logging_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright 2026 The dm_control Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================

"""Regression tests for application logging when importing dm_control."""

import subprocess
import sys

from absl.testing import absltest


class ImportLoggingTest(absltest.TestCase):

def test_mjcf_import_leaves_root_logger_unconfigured(self):
script = """
import logging
assert not logging.root.handlers
from dm_control import mjcf
assert not logging.root.handlers, logging.root.handlers
logging.basicConfig(level=logging.INFO, format='APP LOG: %(message)s')
logging.info('configured after import')
"""
result = subprocess.run(
[sys.executable, "-c", script], check=False,
capture_output=True, text=True, timeout=30)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("APP LOG: configured after import", result.stderr)

def test_mjcf_import_preserves_existing_handler(self):
script = """
import logging
import sys
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(logging.Formatter('BEFORE: %(message)s'))
logging.root.addHandler(handler)
logging.root.setLevel(logging.INFO)
from dm_control import mjcf
assert logging.root.handlers == [handler], logging.root.handlers
logging.info('handler still configured')
"""
result = subprocess.run(
[sys.executable, "-c", script], check=False,
capture_output=True, text=True, timeout=30)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("BEFORE: handler still configured", result.stderr)


if __name__ == "__main__":
absltest.main()
8 changes: 5 additions & 3 deletions dm_control/mujoco/wrapper/mjbindings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,22 @@
"""Import core names of MuJoCo ctypes bindings."""


from absl import logging
import logging

from dm_control.mujoco.wrapper.mjbindings import constants
from dm_control.mujoco.wrapper.mjbindings import enums
from dm_control.mujoco.wrapper.mjbindings import sizes

_LOGGER = logging.getLogger(__name__)

# Internal analytics import.

# pylint: disable=g-import-not-at-top
try:
from dm_control.mujoco.wrapper.mjbindings import functions
from dm_control.mujoco.wrapper.mjbindings.functions import mjlib
logging.info('MuJoCo library version is: %s', mjlib.mj_versionString())
_LOGGER.info('MuJoCo library version is: %s', mjlib.mj_versionString())
# Internal analytics.
except (IOError, OSError):
logging.warning('mjbindings failed to import mjlib and other functions. '
_LOGGER.warning('mjbindings failed to import mjlib and other functions. '
'libmujoco.so may not be accessible.')
Loading