Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
parse_dynamic_lib_subprocess_payload,
)
from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import ALL_AVAILABLE_LIBNAMES
from cuda.pathfinder._utils.diagnostic_log import LOGGER
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS

if TYPE_CHECKING:
Expand Down Expand Up @@ -312,6 +313,9 @@ def load_nvidia_dynamic_lib(libname: str) -> LoadedDL:
The search is performed **per library**. There is currently no mechanism to
guarantee that multiple libraries are all resolved from the same location.

Set ``CUDA_PATHFINDER_LOG_LEVEL=INFO`` to log where each library was
found (``abs_path`` and ``found_via``) to the ``cuda.pathfinder`` logger.

"""
pointer_size_bits = struct.calcsize("P") * 8
if pointer_size_bits != 64:
Expand All @@ -327,4 +331,7 @@ def load_nvidia_dynamic_lib(libname: str) -> LoadedDL:
f"Library name {libname!r} is known but not available on {_PLATFORM_NAME}. "
f"Supported names on {_PLATFORM_NAME}: {sorted(ALL_AVAILABLE_LIBNAMES)}"
)
return _load_lib_no_cache(libname)
loaded = _load_lib_no_cache(libname)
if LOGGER is not None:
LOGGER.info("loaded %s from %s (found via %s)", libname, loaded.abs_path, loaded.found_via)
return loaded
45 changes: 45 additions & 0 deletions cuda_pathfinder/cuda/pathfinder/_utils/diagnostic_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Opt-in logging of where :func:`load_nvidia_dynamic_lib` found each library.

Disabled by default. Set ``CUDA_PATHFINDER_LOG_LEVEL`` to a level name (e.g.
``INFO``) or number to enable it. ``logging`` is only imported when the variable
is set, and the root logger is never configured.
"""

from __future__ import annotations

import os
import warnings
from typing import TYPE_CHECKING

if TYPE_CHECKING:
import logging

ENV_VAR_NAME = "CUDA_PATHFINDER_LOG_LEVEL"
LOGGER_NAME = "cuda.pathfinder"
_LEVEL_NAMES = ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG")


def _make_logger() -> logging.Logger | None:
raw = os.environ.get(ENV_VAR_NAME, "").strip()
if not raw:
return None

import logging

name = raw.upper()
level = int(raw) if raw.isdecimal() else getattr(logging, name) if name in _LEVEL_NAMES else None
if level is None:
warnings.warn(f"{ENV_VAR_NAME}={raw!r} is not a valid logging level; logging stays disabled.", stacklevel=2)
return None

logger = logging.getLogger(LOGGER_NAME)
logger.addHandler(logging.NullHandler())
logger.setLevel(level)
return logger


#: ``None`` when logging is disabled, so call sites can skip all work.
LOGGER: logging.Logger | None = _make_logger()
127 changes: 127 additions & 0 deletions cuda_pathfinder/tests/test_diagnostic_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Tests for opt-in logging in load_nvidia_dynamic_lib. No GPU or NVIDIA library needed."""

import importlib
import logging
import os
from unittest.mock import patch

import pytest

from cuda.pathfinder._dynamic_libs import load_nvidia_dynamic_lib as mod
from cuda.pathfinder._dynamic_libs.load_dl_common import DynamicLibNotFoundError, LoadedDL
from cuda.pathfinder._utils import diagnostic_log

pytestmark = pytest.mark.thread_unsafe(reason="reloads diagnostic_log under a patched os.environ")

ENV_VAR = diagnostic_log.ENV_VAR_NAME
LOGGER_NAME = diagnostic_log.LOGGER_NAME
LOADED = LoadedDL("/opt/cuda/lib64/libcudart.so.12", False, 1234, "CUDA_PATH")


def reload_with_env(value):
"""Reimport diagnostic_log with ENV_VAR set to *value* (None to unset)."""
env = {k: v for k, v in os.environ.items() if k != ENV_VAR}
if value is not None:
env[ENV_VAR] = value
with patch.dict(os.environ, env, clear=True):
return importlib.reload(diagnostic_log)


@pytest.fixture(autouse=True)
def restore_diagnostic_log():
logger = logging.getLogger(LOGGER_NAME)
saved = (logger.level, list(logger.handlers))
yield
logger.setLevel(saved[0])
logger.handlers[:] = saved[1]
reload_with_env(None)


@pytest.mark.parametrize("value", [None, "", " "])
@pytest.mark.agent_authored(model="claude-opus-5")
def test_disabled_by_default(value):
assert reload_with_env(value).LOGGER is None


@pytest.mark.parametrize(("value", "level"), [("INFO", logging.INFO), (" debug ", logging.DEBUG), ("10", 10)])
@pytest.mark.agent_authored(model="claude-opus-5")
def test_env_var_enables_logger(value, level):
logger = reload_with_env(value).LOGGER
assert logger is not None
assert logger.name == LOGGER_NAME
assert logger.level == level
assert all(isinstance(h, logging.NullHandler) for h in logger.handlers)


@pytest.mark.parametrize("value", ["VERBOSE", "\u00b2"]) # superscript two: isdigit() but not int()-parsable
@pytest.mark.agent_authored(model="claude-opus-5")
def test_invalid_value_warns_and_stays_disabled(value):
with pytest.warns(UserWarning, match=ENV_VAR):
assert reload_with_env(value).LOGGER is None


@pytest.mark.agent_authored(model="claude-opus-5")
def test_does_not_configure_root_logger():
root = logging.getLogger()
before = (root.level, list(root.handlers))
reload_with_env("DEBUG")
assert (root.level, root.handlers) == before


@pytest.mark.agent_authored(model="claude-opus-5")
def test_logging_not_imported_at_module_scope():
with open(diagnostic_log.__file__) as f:
lines = f.read().splitlines()
assert not [line for line in lines if line.startswith(("import logging", "from logging"))]


@pytest.fixture
def load_cudart(monkeypatch):
"""Call load_nvidia_dynamic_lib('cudart') with the search stubbed out."""

def call(logger, result=LOADED):
monkeypatch.setattr(mod, "LOGGER", logger)

def fake_load(_libname):
if isinstance(result, Exception):
raise result
return result

monkeypatch.setattr(mod, "_load_lib_no_cache", fake_load)
mod.load_nvidia_dynamic_lib.cache_clear()
try:
return mod.load_nvidia_dynamic_lib("cudart")
finally:
mod.load_nvidia_dynamic_lib.cache_clear()

return call


@pytest.mark.agent_authored(model="claude-opus-5")
def test_successful_load_logs_one_info_record(load_cudart, caplog):
logger = reload_with_env("INFO").LOGGER
with caplog.at_level(logging.INFO, logger=LOGGER_NAME):
assert load_cudart(logger) is LOADED

records = [r for r in caplog.records if r.name == LOGGER_NAME]
assert len(records) == 1
assert records[0].levelno == logging.INFO
assert records[0].getMessage() == "loaded cudart from /opt/cuda/lib64/libcudart.so.12 (found via CUDA_PATH)"


@pytest.mark.agent_authored(model="claude-opus-5")
def test_failed_load_logs_nothing(load_cudart, caplog):
logger = reload_with_env("DEBUG").LOGGER
with caplog.at_level(logging.DEBUG, logger=LOGGER_NAME), pytest.raises(DynamicLibNotFoundError):
load_cudart(logger, DynamicLibNotFoundError("not found"))
assert [r for r in caplog.records if r.name == LOGGER_NAME] == []


@pytest.mark.agent_authored(model="claude-opus-5")
def test_silent_when_disabled(load_cudart, caplog):
with caplog.at_level(logging.DEBUG, logger=LOGGER_NAME):
assert load_cudart(None) is LOADED
assert [r for r in caplog.records if r.name == LOGGER_NAME] == []
Loading