diff --git a/doc/changelog.d/1569.added.md b/doc/changelog.d/1569.added.md new file mode 100644 index 0000000000..948ffec94b --- /dev/null +++ b/doc/changelog.d/1569.added.md @@ -0,0 +1 @@ +Add wbtrace option for logger - embedding diff --git a/src/ansys/mechanical/core/embedding/app.py b/src/ansys/mechanical/core/embedding/app.py index 613ed3c8b8..87da9d46e5 100644 --- a/src/ansys/mechanical/core/embedding/app.py +++ b/src/ansys/mechanical/core/embedding/app.py @@ -199,7 +199,7 @@ class App: Create App with Mechanical project file and version: >>> from ansys.mechanical.core import App - >>> app = App(db_file="path/to/file.mechdat", version=252) + >>> app = App(db_file="path/to/file.mechdat", version=261) Disable copying the user profile when private appdata is enabled diff --git a/src/ansys/mechanical/core/embedding/logger/__init__.py b/src/ansys/mechanical/core/embedding/logger/__init__.py index 3622edb727..076e07e008 100644 --- a/src/ansys/mechanical/core/embedding/logger/__init__.py +++ b/src/ansys/mechanical/core/embedding/logger/__init__.py @@ -53,6 +53,7 @@ import logging import os import typing +import warnings from ansys.mechanical.core.embedding import initializer from ansys.mechanical.core.embedding.logger import environ, linux_api, sinks, windows_api @@ -90,7 +91,14 @@ class Configuration: """Configures logger for Mechanical embedding.""" @classmethod - def configure(cls, level=logging.WARNING, directory=None, base_directory=None, to_stdout=True): + def configure( + cls, + level=logging.WARNING, + directory=None, + base_directory=None, + to_stdout=True, + wbtracing=None, + ): """Configure the logger for PyMechanical embedding. Parameters @@ -108,6 +116,13 @@ def configure(cls, level=logging.WARNING, directory=None, base_directory=None, t to_stdout : bool, optional Whether to write log messages to the standard output, which is the command line. The default is ``True``. + wbtracing : bool, optional + Whether to enable WBTRACING for COM-level diagnostic output. + The default is ``None``, which preserves the current setting. + When enabled, trace messages from Mechanical's internal COM + implementation are written to stdout. Setting this after + Mechanical is initialized will have no effect on the running + instance but will persist in the environment. """ # Set up the global log configuration. cls.set_log_directory(directory) @@ -122,6 +137,10 @@ def configure(cls, level=logging.WARNING, directory=None, base_directory=None, t cls._commit_enabled_configuration() cls.set_log_level(level) + # Configure WBTRACING if specified. + if wbtracing is not None: + cls.set_wbtracing(wbtracing) + @classmethod def set_log_to_stdout(cls, value: bool) -> None: """Configure logging to write to the standard output.""" @@ -165,6 +184,37 @@ def _commit_enabled_configuration(cls) -> None: for sink in LOGGING_SINKS: _get_backend().enable(sink) + @classmethod + def set_wbtracing(cls, enabled: bool) -> None: + """Enable or disable WBTRACING for COM-level diagnostic output. + + WBTRACING enables trace-level diagnostic output from Mechanical's + internal COM implementation. When enabled, trace messages are + written to stdout. + + .. note:: + Setting this after Mechanical has been initialized will have no + effect on the running instance. The env var is set for completeness + (e.g. for a future instance in the same process), but a warning + is issued. + + Parameters + ---------- + enabled : bool + Whether to enable WBTRACING. + """ + if initializer.INITIALIZED_VERSION is not None: + warnings.warn( + "WBTRACING was set after Mechanical is already initialized. " + "It will have no effect on the current instance.", + UserWarning, + stacklevel=2, + ) + if enabled: + os.environ["WBTRACING"] = "1" + else: + os.environ.pop("WBTRACING", None) + @classmethod def _store_stdout_sink_enabled(cls, value: bool) -> None: if value: diff --git a/tests/embedding/test_logger.py b/tests/embedding/test_logger.py index d2fba88b35..6281c2065b 100644 --- a/tests/embedding/test_logger.py +++ b/tests/embedding/test_logger.py @@ -43,6 +43,7 @@ def _unset_var(env, var) -> None: _unset_var(env, "ANSYS_WORKBENCH_LOGGING_CONSOLE") _unset_var(env, "ANSYS_WORKBENCH_LOGGING_AUTO_FLUSH") _unset_var(env, "ANSYS_WORKBENCH_LOGGING_DIRECTORY") + _unset_var(env, "WBTRACING") return env @@ -52,8 +53,8 @@ def _run_embedding_log_test( pytestconfig, testname: str, pass_expected: bool = True, -) -> tuple[bytes, bytes]: - """Run the process and returns it after it finishes.""" +) -> tuple[str, str]: + """Run the process and returns (stdout, stderr) after it finishes.""" version = pytestconfig.getoption("ansys_version") embedded_py = Path(rootdir) / "tests" / "scripts" / "embedding_log_test.py" @@ -68,11 +69,11 @@ def _run_embedding_log_test( subprocess_pass_expected, ) + stdout = stdout.decode() if not subprocess_pass_expected: - stdout = stdout.decode() _assert_success(stdout, pass_expected) stderr = stderr.decode() - return stderr + return stdout, stderr def _assert_success(stdout: str, pass_expected: bool) -> int: @@ -93,7 +94,7 @@ def _assert_success(stdout: str, pass_expected: bool) -> int: @pytest.mark.embedding_logging def test_logging_write_log_before_init(rootdir, run_subprocess, pytestconfig): """Test that an error is thrown when trying to log before initializing.""" - stderr = _run_embedding_log_test( + _, stderr = _run_embedding_log_test( run_subprocess, rootdir, pytestconfig, "log_before_initialize", False ) assert "Can't log to the embedding logger until Mechanical is initialized" in stderr @@ -105,7 +106,7 @@ def test_logging_write_info_after_initialize_with_error_level( rootdir, run_subprocess, pytestconfig ): """Test that no output is written when an info is logged when configured at the error level.""" - stderr = _run_embedding_log_test( + _, stderr = _run_embedding_log_test( run_subprocess, rootdir, pytestconfig, @@ -119,7 +120,7 @@ def test_logging_write_info_after_initialize_with_error_level( @pytest.mark.embedding_logging def test_addin_configuration(rootdir, run_subprocess, pytestconfig, addin_configuration): """Test that mechanical can start with both the Mechanical and WorkBench configuration.""" - stderr = _run_embedding_log_test( + _, stderr = _run_embedding_log_test( run_subprocess, rootdir, pytestconfig, @@ -134,7 +135,7 @@ def test_logging_write_error_after_initialize_with_info_level( rootdir, run_subprocess, pytestconfig ): """Test that output is written when an error is logged when configured at the info level.""" - stderr = _run_embedding_log_test( + _, stderr = _run_embedding_log_test( run_subprocess, rootdir, pytestconfig, "log_error_after_initialize_with_info_level" ) assert "Will no one rid me of this turbulent priest?" in stderr @@ -151,7 +152,48 @@ def test_logging_level_before_and_after_initialization(rootdir, run_subprocess, @pytest.mark.embedding_logging def test_logging_all_level(rootdir, run_subprocess, pytestconfig): """Test all logging level after initialization.""" - stderr = _run_embedding_log_test( + _, stderr = _run_embedding_log_test( run_subprocess, rootdir, pytestconfig, "log_check_all_log_level" ) assert all(keyword in stderr for keyword in ["debug", "warning", "info", "error", "fatal"]) + + +@pytest.mark.embedding_logging +def test_wbtracing_configuration(monkeypatch, pytestconfig): + """Test WBTRACING environment variable configuration and post-init warning.""" + from ansys.mechanical.core.embedding import initializer + from ansys.mechanical.core.embedding.logger import Configuration + + version = pytestconfig.getoption("ansys_version") + monkeypatch.delenv("WBTRACING", raising=False) + + # set_wbtracing directly + Configuration.set_wbtracing(True) + assert os.environ.get("WBTRACING") == "1" + Configuration.set_wbtracing(False) + assert "WBTRACING" not in os.environ + + # via configure() + Configuration.configure(wbtracing=None, to_stdout=True) + assert "WBTRACING" not in os.environ + Configuration.configure(wbtracing=True, to_stdout=True) + assert os.environ.get("WBTRACING") == "1" + Configuration.configure(wbtracing=False, to_stdout=True) + assert "WBTRACING" not in os.environ + + # warns when called after init, but still sets the env var + monkeypatch.setattr(initializer, "INITIALIZED_VERSION", version) + with pytest.warns(UserWarning, match="no effect on the current instance"): + Configuration.set_wbtracing(True) + assert os.environ.get("WBTRACING") == "1" + + +@pytest.mark.embedding_scripts +@pytest.mark.embedding_logging +def test_logging_with_wbtracing(rootdir, run_subprocess, pytestconfig): + """Test that WBTRACING can be enabled through the logger configuration.""" + stdout, stderr = _run_embedding_log_test( + run_subprocess, rootdir, pytestconfig, "log_with_wbtracing" + ) + assert "wbtracing_enabled" in stderr + assert "Looking for GUID" in stdout diff --git a/tests/scripts/embedding_log_test.py b/tests/scripts/embedding_log_test.py index 9e7d896893..4259dd888a 100644 --- a/tests/scripts/embedding_log_test.py +++ b/tests/scripts/embedding_log_test.py @@ -23,6 +23,7 @@ """Test cases for embedding logging.""" import logging +import os import sys import ansys.mechanical.core as mech @@ -92,6 +93,15 @@ def log_check_all_log_level(version): Logger.fatal("fatal") +def log_with_wbtracing(version): + """Enable WBTRACING via Configuration and verify logging works.""" + Configuration.configure(level=logging.ERROR, to_stdout=True) + Configuration.set_wbtracing(True) + assert os.environ.get("WBTRACING") == "1" + _ = mech.App(version=version) + Logger.error("wbtracing_enabled") + + if __name__ == "__main__": version = sys.argv[1] test_name = sys.argv[2] @@ -103,6 +113,7 @@ def log_check_all_log_level(version): "log_configuration_Mechanical": log_configuration_mechanical, "log_configuration_WorkBench": log_configuration_workbench, "log_check_all_log_level": log_check_all_log_level, + "log_with_wbtracing": log_with_wbtracing, } tests[test_name](int(version)) print("@@success@@")