diff --git a/cffi/cdefs.h b/cffi/cdefs.h index aa75004..023295c 100644 --- a/cffi/cdefs.h +++ b/cffi/cdefs.h @@ -182,6 +182,7 @@ enum ly_stmt { #define LY_LOSTORE ... #define LY_LOSTORE_LAST ... int ly_log_options(int); +uint32_t *ly_temp_log_options(uint32_t *); LY_LOG_LEVEL ly_log_level(LY_LOG_LEVEL); extern "Python" void lypy_log_cb(LY_LOG_LEVEL, const char *, const char *, const char *, uint64_t); diff --git a/libyang/__init__.py b/libyang/__init__.py index ff15755..ff8b4c3 100644 --- a/libyang/__init__.py +++ b/libyang/__init__.py @@ -65,7 +65,7 @@ ) from .extension import ExtensionPlugin, LibyangExtensionError from .keyed_list import KeyedList -from .log import configure_logging +from .log import configure_logging, temp_log_options from .schema import ( Extension, ExtensionCompiled, diff --git a/libyang/log.py b/libyang/log.py index f92c70f..e36e8ae 100644 --- a/libyang/log.py +++ b/libyang/log.py @@ -2,8 +2,11 @@ # Copyright (c) 2020 6WIND S.A. # SPDX-License-Identifier: MIT +from contextlib import contextmanager import logging +import cffi + from _libyang import ffi, lib from .util import c2str @@ -26,6 +29,16 @@ def get_libyang_level(py_level): return None +@contextmanager +def temp_log_options(opt: int = 0): + _ffi = cffi.FFI() + opts = _ffi.new("uint32_t *", opt) + + lib.ly_temp_log_options(opts) + yield + lib.ly_temp_log_options(ffi.NULL) + + @ffi.def_extern(name="lypy_log_cb") def libyang_c_logging_callback(level, msg, data_path, schema_path, line): args = [c2str(msg)] @@ -38,7 +51,7 @@ def libyang_c_logging_callback(level, msg, data_path, schema_path, line): args.append(c2str(schema_path)) if line != 0: fmt += " line %u" - args.append(str(line)) + args.append(line) LOG.log(LOG_LEVELS.get(level, logging.NOTSET), fmt, *args) diff --git a/tests/test_log.py b/tests/test_log.py new file mode 100644 index 0000000..1fa94b8 --- /dev/null +++ b/tests/test_log.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: MIT +# +# July 7 2025, Christian Hopps +# +# Copyright (c) 2025, LabN Consulting, L.L.C. +# +import logging +import os +import sys +import unittest + +from libyang import Context, LibyangError, configure_logging, temp_log_options + + +YANG_DIR = os.path.join(os.path.dirname(__file__), "yang") + + +class LogTest(unittest.TestCase): + def setUp(self): + self.ctx = Context(YANG_DIR) + configure_logging(False, logging.INFO) + + def tearDown(self): + if self.ctx is not None: + self.ctx.destroy() + self.ctx = None + + def _cause_log(self): + try: + assert self.ctx is not None + _ = self.ctx.parse_data_mem("bad", fmt="xml") + except LibyangError: + pass + + @unittest.skipIf(sys.version_info < (3, 10), "Test requires Python 3.10+") + def test_configure_logging(self): + """Test configure_logging API.""" + with self.assertNoLogs("libyang", level="ERROR"): + self._cause_log() + + configure_logging(True, logging.INFO) + with self.assertLogs("libyang", level="ERROR"): + self._cause_log() + + @unittest.skipIf(sys.version_info < (3, 10), "Test requires Python 3.10+") + def test_with_temp_log(self): + """Test configure_logging API.""" + configure_logging(True, logging.INFO) + + with self.assertLogs("libyang", level="ERROR"): + self._cause_log() + + with self.assertNoLogs("libyang", level="ERROR"): + with temp_log_options(0): + self._cause_log() + + with self.assertLogs("libyang", level="ERROR"): + self._cause_log()