From da047c6a4d38283794944716df304ee962dd0953 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 7 Apr 2026 11:29:12 +0200 Subject: [PATCH 01/10] log: create simple logging system --- afmformats/__init__.py | 1 + afmformats/formats/__init__.py | 59 +++++++++++++++++++++++++--------- afmformats/logging_setup.py | 46 ++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 afmformats/logging_setup.py diff --git a/afmformats/__init__.py b/afmformats/__init__.py index f6bd57b..7362ea3 100644 --- a/afmformats/__init__.py +++ b/afmformats/__init__.py @@ -4,6 +4,7 @@ from .afm_qmap import AFMQMap from .formats import find_data, load_data from .formats import supported_extensions +from .logging_setup import DEFAULT_LOG_PATH, configure_logging from .mod_creep_compliance import AFMCreepCompliance from .mod_force_distance import AFMForceDistance from .mod_stress_relaxation import AFMStressRelaxation diff --git a/afmformats/formats/__init__.py b/afmformats/formats/__init__.py index c816006..89b5ff0 100644 --- a/afmformats/formats/__init__.py +++ b/afmformats/formats/__init__.py @@ -1,7 +1,8 @@ +import logging import pathlib - from .. import errors from .. import meta +from ..logging_setup import configure_logging from .fmt_hdf5 import recipe_hdf5 from .fmt_igor import recipe_ibw from .fmt_jpk import ( @@ -24,6 +25,10 @@ "formats_by_suffix", "formats_by_modality", "supported_extensions"] +configure_logging() +logger = logging.getLogger(__name__) + + class AFMFormatRecipe(object): def __init__(self, recipe): """A wrapper class for file format recipes @@ -167,7 +172,7 @@ def find_data(path, modality=None): get_recipe(path=path, modality=modality) except errors.FileFormatNotSupportedError: # not a valid file format - pass + logger.debug("Skipping unsupported file '%s'", path) else: # valid file format file_list.append(path) @@ -197,10 +202,22 @@ def get_recipe(path, modality=None): recipes = formats_by_suffix[path.suffix] for rec in recipes: - if ((modality is None or modality in rec.modalities) - and rec.detect(path)): + try: + supported = rec.detect(path) + except BaseException: + logger.debug( + "Detect failed for '%s' using recipe '%s'. Traceback follows.", + path, rec, exc_info=True) + supported = False + if ((modality is None or modality in rec.modalities) and supported): break + logger.debug( + "Recipe '%s' did not match '%s' for modality '%s'", + rec, path, modality) else: + logger.debug( + "No recipe matched '%s' for modality '%s'. Tried: %s", + path, modality, [r.descr for r in recipes]) raise errors.FileFormatNotSupportedError( f"Could not determine file format recipe for '{path}'!") @@ -255,18 +272,28 @@ def load_data(path, meta_override=None, modality=None, afm_data_class = data_classes_by_modality[modality] else: afm_data_class = default_data_classes_by_modality[modality] - for dd in loader(path, - callback=callback, - meta_override=meta_override): - dd["metadata"]["format"] = "{} ({})".format(cur_recipe["maker"], - cur_recipe["descr"]) - if fix_modality and dd["metadata"]["imaging mode"] != modality: - # The user explicitly requested this modality. - continue - ddi = afm_data_class(data=dd["data"], - metadata=dd["metadata"], - diskcache=diskcache) - afmdata.append(ddi) + try: + for dd in loader(path, + callback=callback, + meta_override=meta_override): + dd["metadata"]["format"] = "{} ({})".format( + cur_recipe["maker"], cur_recipe["descr"]) + if fix_modality and dd["metadata"]["imaging mode"] != modality: + # The user explicitly requested this modality. + logger.debug( + "Skipping dataset with modality '%s' (expected '%s') " + "from '%s'", + dd["metadata"]["imaging mode"], modality, path) + continue + ddi = afm_data_class(data=dd["data"], + metadata=dd["metadata"], + diskcache=diskcache) + afmdata.append(ddi) + except BaseException: + logger.debug( + "Loader failed for '%s' using recipe '%s'. Traceback follows.", + path, cur_recipe, exc_info=True) + raise else: raise ValueError("Unsupported file extension: '{}'!".format(path)) return afmdata diff --git a/afmformats/logging_setup.py b/afmformats/logging_setup.py new file mode 100644 index 0000000..17471d7 --- /dev/null +++ b/afmformats/logging_setup.py @@ -0,0 +1,46 @@ +"""Package-wide logging configuration.""" + +import logging +import os +import pathlib +import tempfile +from typing import Optional + +__all__ = ["DEFAULT_LOG_PATH", "configure_logging"] + + +def configure_logging(log_path: Optional[str] = None) -> str: + """Ensure afmformats writes debug logs to a file. + + Priority: explicit argument -> AFMFORMATS_LOG_PATH env -> temp dir. + Returns the log path used so CI pipelines can publish it as an artifact. + """ + path = ( + log_path + or os.environ.get("AFMFORMATS_LOG_PATH") + or os.path.join(tempfile.gettempdir(), "afmformats.log") + ) + path = str(pathlib.Path(path)) + logger = logging.getLogger("afmformats") + logger.setLevel(logging.DEBUG) + logger.propagate = False + + handler_exists = any( + isinstance(h, logging.FileHandler) + and getattr(h, "baseFilename", None) == path + for h in logger.handlers + ) + if not handler_exists: + pathlib.Path(path).parent.mkdir(parents=True, exist_ok=True) + fh = logging.FileHandler(path, encoding="utf-8") + fh.setLevel(logging.DEBUG) + formatter = logging.Formatter( + "%(asctime)s %(levelname)s %(name)s: %(message)s" + ) + fh.setFormatter(formatter) + logger.addHandler(fh) + logger.debug("afmformats logging initialized at %s", path) + return path + + +DEFAULT_LOG_PATH = configure_logging() From 3beed9ef9d03795b3e79bd8f1dd51d426d1aeeae Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Tue, 7 Apr 2026 11:29:42 +0200 Subject: [PATCH 02/10] ci: add logging artifact step for ci --- .github/workflows/check.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index af8406e..2aca6d0 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -8,6 +8,8 @@ jobs: build: runs-on: ${{ matrix.os }} + env: + AFMFORMATS_LOG_PATH: ${{ github.workspace }}/afmformats.log strategy: matrix: python-version: ['3.11', '3.12', '3.13'] @@ -34,5 +36,11 @@ jobs: - name: Test with pytest run: | coverage run --source=afmformats -m pytest tests + - name: Upload afmformats log artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: afmformats-log-${{ matrix.os }}-py${{ matrix.python-version }} + path: afmformats.log - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 From a49bc743689f88c6c927d9a7284834a0e25a501e Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Wed, 8 Apr 2026 10:43:51 +0200 Subject: [PATCH 03/10] tests: add some logger unit tests --- afmformats/formats/__init__.py | 7 ++++++ tests/test_logger.py | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tests/test_logger.py diff --git a/afmformats/formats/__init__.py b/afmformats/formats/__init__.py index 89b5ff0..1ec5986 100644 --- a/afmformats/formats/__init__.py +++ b/afmformats/formats/__init__.py @@ -294,7 +294,14 @@ def load_data(path, meta_override=None, modality=None, "Loader failed for '%s' using recipe '%s'. Traceback follows.", path, cur_recipe, exc_info=True) raise + logger.debug( + "Loaded %d dataset(s) from '%s' using '%s'", + len(afmdata), path, cur_recipe.descr) else: + logger.debug( + "Loader failed for '%s' as the extension '%s' is " + "not recognised. Traceback follows.", + path, path.suffix, exc_info=True) raise ValueError("Unsupported file extension: '{}'!".format(path)) return afmdata diff --git a/tests/test_logger.py b/tests/test_logger.py new file mode 100644 index 0000000..a74b31e --- /dev/null +++ b/tests/test_logger.py @@ -0,0 +1,42 @@ +import logging +import pathlib +import pytest + +import afmformats + + +data_path = pathlib.Path(__file__).resolve().parent / "data" + + +def test_logger_load_data_success(): + path = data_path / "fmt-tab-fd_version_0.13.3.tab" + log_path = pathlib.Path(afmformats.DEFAULT_LOG_PATH) + + afmdata = afmformats.load_data(path) + assert len(afmdata) == 1 + + with log_path.open(encoding="utf-8") as fd: + new_output = fd.read() + + assert "afmformats logging initialized" in new_output + assert "Loaded 1 dataset(s)" in new_output + assert str(path) in new_output + assert "tab-separated values" in new_output + + +@pytest.mark.parametrize( + "file_name", + ["fmt-tab-fd_version_0.13.3.slab"]) +def test_logger_load_data_failure_bad_extension(file_name): + path = data_path / file_name + log_path = pathlib.Path(afmformats.DEFAULT_LOG_PATH) + + with pytest.raises(ValueError, + match=r"Unsupported file extension"): + _ = afmformats.load_data(path) + + with log_path.open(encoding="utf-8") as fd: + new_output = fd.read() + + assert "afmformats logging initialized" in new_output + assert "Loader failed" in new_output \ No newline at end of file From 07ed34054149c5b94f77d1aaf87db52795ab1196 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Wed, 8 Apr 2026 11:34:02 +0200 Subject: [PATCH 04/10] docs: add logging section to the docs --- afmformats/__init__.py | 6 +++++- afmformats/logging_setup.py | 24 +++++++++++++++++++++++- docs/sec_advanced_usage.rst | 30 +++++++++++++++++++++++++++++- tests/test_logger.py | 4 +--- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/afmformats/__init__.py b/afmformats/__init__.py index 7362ea3..fea9593 100644 --- a/afmformats/__init__.py +++ b/afmformats/__init__.py @@ -4,7 +4,11 @@ from .afm_qmap import AFMQMap from .formats import find_data, load_data from .formats import supported_extensions -from .logging_setup import DEFAULT_LOG_PATH, configure_logging +from .logging_setup import ( + DEFAULT_LOG_PATH, + configure_console_logging, + configure_logging, +) from .mod_creep_compliance import AFMCreepCompliance from .mod_force_distance import AFMForceDistance from .mod_stress_relaxation import AFMStressRelaxation diff --git a/afmformats/logging_setup.py b/afmformats/logging_setup.py index 17471d7..1e3e7fc 100644 --- a/afmformats/logging_setup.py +++ b/afmformats/logging_setup.py @@ -3,10 +3,15 @@ import logging import os import pathlib +import sys import tempfile from typing import Optional -__all__ = ["DEFAULT_LOG_PATH", "configure_logging"] +__all__ = [ + "DEFAULT_LOG_PATH", + "configure_console_logging", + "configure_logging", +] def configure_logging(log_path: Optional[str] = None) -> str: @@ -43,4 +48,21 @@ def configure_logging(log_path: Optional[str] = None) -> str: return path +def configure_console_logging(level: int = logging.DEBUG) -> None: + """Mirror afmformats log output to the terminal.""" + logger = logging.getLogger("afmformats") + handler_exists = any( + isinstance(h, logging.StreamHandler) + and not isinstance(h, logging.FileHandler) + and getattr(h, "stream", None) is sys.stderr + for h in logger.handlers + ) + if not handler_exists: + handler = logging.StreamHandler() + handler.setLevel(level) + handler.setFormatter(logging.Formatter( + "%(levelname)s:%(name)s:%(message)s")) + logger.addHandler(handler) + + DEFAULT_LOG_PATH = configure_logging() diff --git a/docs/sec_advanced_usage.rst b/docs/sec_advanced_usage.rst index a94b389..026af26 100644 --- a/docs/sec_advanced_usage.rst +++ b/docs/sec_advanced_usage.rst @@ -28,4 +28,32 @@ functionalities: # You can also extract a subgroup that matches a certin path In [6]: subgroup = group.subgroup_with_path("data/force-map2x2-example.jpk-force-map") - In [7]: print(subgroup) \ No newline at end of file + In [7]: print(subgroup) + + +Logging (for developers) +======================== +``afmformats`` now has a simple logging system. When loading data in a script +or in the console, debug-level logs will be written to `afmformats.log` in +your machine's temp folder. Additionally, when debugging file loading in a +local script, the ``afmformats.log`` debug log will output to the terminal if +you call ``afmformats.configure_console_logging()``: + +.. code-block:: python + + import pathlib + import afmformats + + data_path = pathlib.Path("tests/data") + + afmformats.configure_console_logging() + + afmformats.load_data(data_path / "fmt-hdf5-fd_version_0.13.3.h5") + +Output of the above script: + +.. code-block:: + + DEBUG:afmformats.formats:Loaded 1 dataset(s) from '...\afmformats\tests\data\fmt-hdf5-fd_version_0.13.3.h5' using 'HDF5-based' + + Process finished with exit code 0 diff --git a/tests/test_logger.py b/tests/test_logger.py index a74b31e..892eb3b 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -1,10 +1,8 @@ -import logging import pathlib import pytest import afmformats - data_path = pathlib.Path(__file__).resolve().parent / "data" @@ -39,4 +37,4 @@ def test_logger_load_data_failure_bad_extension(file_name): new_output = fd.read() assert "afmformats logging initialized" in new_output - assert "Loader failed" in new_output \ No newline at end of file + assert "Loader failed" in new_output From 3772ef382b6b12e1efb24dbd063c3980f5ae80f7 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Wed, 8 Apr 2026 11:42:49 +0200 Subject: [PATCH 05/10] docs: minor text changes for logging section --- docs/sec_advanced_usage.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/sec_advanced_usage.rst b/docs/sec_advanced_usage.rst index 026af26..ad4f353 100644 --- a/docs/sec_advanced_usage.rst +++ b/docs/sec_advanced_usage.rst @@ -35,9 +35,11 @@ Logging (for developers) ======================== ``afmformats`` now has a simple logging system. When loading data in a script or in the console, debug-level logs will be written to `afmformats.log` in -your machine's temp folder. Additionally, when debugging file loading in a -local script, the ``afmformats.log`` debug log will output to the terminal if -you call ``afmformats.configure_console_logging()``: +your machine's temp folder. This happens automatically. + +Optionally, when running a local script, the ``afmformats.log`` +debug log can be output to the terminal if +you call :func:`afmformats.configure_console_logging()`: .. code-block:: python From 97d3c565289e34f30b7e4347429b2e79d664e056 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Thu, 9 Apr 2026 11:03:46 +0200 Subject: [PATCH 06/10] ref: let the user choose to call logger --- afmformats/__init__.py | 6 +----- afmformats/formats/__init__.py | 2 -- afmformats/logging_setup.py | 18 +++++++----------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/afmformats/__init__.py b/afmformats/__init__.py index fea9593..7362ea3 100644 --- a/afmformats/__init__.py +++ b/afmformats/__init__.py @@ -4,11 +4,7 @@ from .afm_qmap import AFMQMap from .formats import find_data, load_data from .formats import supported_extensions -from .logging_setup import ( - DEFAULT_LOG_PATH, - configure_console_logging, - configure_logging, -) +from .logging_setup import DEFAULT_LOG_PATH, configure_logging from .mod_creep_compliance import AFMCreepCompliance from .mod_force_distance import AFMForceDistance from .mod_stress_relaxation import AFMStressRelaxation diff --git a/afmformats/formats/__init__.py b/afmformats/formats/__init__.py index 1ec5986..74942f9 100644 --- a/afmformats/formats/__init__.py +++ b/afmformats/formats/__init__.py @@ -2,7 +2,6 @@ import pathlib from .. import errors from .. import meta -from ..logging_setup import configure_logging from .fmt_hdf5 import recipe_hdf5 from .fmt_igor import recipe_ibw from .fmt_jpk import ( @@ -25,7 +24,6 @@ "formats_by_suffix", "formats_by_modality", "supported_extensions"] -configure_logging() logger = logging.getLogger(__name__) diff --git a/afmformats/logging_setup.py b/afmformats/logging_setup.py index 1e3e7fc..3c50ea9 100644 --- a/afmformats/logging_setup.py +++ b/afmformats/logging_setup.py @@ -9,22 +9,19 @@ __all__ = [ "DEFAULT_LOG_PATH", - "configure_console_logging", "configure_logging", ] +DEFAULT_LOG_PATH = os.path.join(tempfile.gettempdir(), "afmformats.log") -def configure_logging(log_path: Optional[str] = None) -> str: + +def configure_logging(log_path: Optional[str] = DEFAULT_LOG_PATH, + console_logging_level: bool | int = 0) -> str: """Ensure afmformats writes debug logs to a file. - Priority: explicit argument -> AFMFORMATS_LOG_PATH env -> temp dir. Returns the log path used so CI pipelines can publish it as an artifact. """ - path = ( - log_path - or os.environ.get("AFMFORMATS_LOG_PATH") - or os.path.join(tempfile.gettempdir(), "afmformats.log") - ) + path = log_path path = str(pathlib.Path(path)) logger = logging.getLogger("afmformats") logger.setLevel(logging.DEBUG) @@ -45,6 +42,8 @@ def configure_logging(log_path: Optional[str] = None) -> str: fh.setFormatter(formatter) logger.addHandler(fh) logger.debug("afmformats logging initialized at %s", path) + if console_logging_level: + configure_console_logging(console_logging_level) return path @@ -63,6 +62,3 @@ def configure_console_logging(level: int = logging.DEBUG) -> None: handler.setFormatter(logging.Formatter( "%(levelname)s:%(name)s:%(message)s")) logger.addHandler(handler) - - -DEFAULT_LOG_PATH = configure_logging() From fa71ffc8b404c9ac229d88dbcc38b63589902e97 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Thu, 9 Apr 2026 11:04:21 +0200 Subject: [PATCH 07/10] tests: setup the logger during pytest config and use path as stash fixture --- tests/conftest.py | 19 +++++++++++++++++++ tests/test_logger.py | 9 +++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 77ab86f..5135cc7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,15 @@ +import os import shutil import tempfile import time +from pathlib import Path + +import afmformats +import pytest TMPDIR = tempfile.mkdtemp(prefix=time.strftime( "afmformats_test_%H.%M_")) +LOG_PATH_KEY = pytest.StashKey[Path]() def pytest_configure(config): @@ -13,6 +19,14 @@ def pytest_configure(config): file after command line options have been parsed. """ tempfile.tempdir = TMPDIR + # deal with logging directory + ci_log_path = os.getenv("AFMFORMATS_LOG_PATH") + if ci_log_path: + log_path = ci_log_path + else: + log_path = os.path.join(TMPDIR, "afmformats.log") + configured_log_path = afmformats.configure_logging(log_path) + config.stash[LOG_PATH_KEY] = Path(configured_log_path) def pytest_unconfigure(config): @@ -20,3 +34,8 @@ def pytest_unconfigure(config): called before test process is exited. """ shutil.rmtree(TMPDIR, ignore_errors=True) + + +@pytest.fixture +def afmformats_log_path(pytestconfig): + return pytestconfig.stash[LOG_PATH_KEY] diff --git a/tests/test_logger.py b/tests/test_logger.py index 892eb3b..dc8948b 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -6,9 +6,9 @@ data_path = pathlib.Path(__file__).resolve().parent / "data" -def test_logger_load_data_success(): +def test_logger_load_data_success(afmformats_log_path): path = data_path / "fmt-tab-fd_version_0.13.3.tab" - log_path = pathlib.Path(afmformats.DEFAULT_LOG_PATH) + log_path = pathlib.Path(afmformats_log_path) afmdata = afmformats.load_data(path) assert len(afmdata) == 1 @@ -25,9 +25,10 @@ def test_logger_load_data_success(): @pytest.mark.parametrize( "file_name", ["fmt-tab-fd_version_0.13.3.slab"]) -def test_logger_load_data_failure_bad_extension(file_name): +def test_logger_load_data_failure_bad_extension( + afmformats_log_path, file_name): path = data_path / file_name - log_path = pathlib.Path(afmformats.DEFAULT_LOG_PATH) + log_path = pathlib.Path(afmformats_log_path) with pytest.raises(ValueError, match=r"Unsupported file extension"): From 600f65ad40aab00d18e923e7ac723e785c3b4e22 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Thu, 9 Apr 2026 11:12:13 +0200 Subject: [PATCH 08/10] docs: update section to reflect new logging setup --- docs/sec_advanced_usage.rst | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/sec_advanced_usage.rst b/docs/sec_advanced_usage.rst index ad4f353..6ad6320 100644 --- a/docs/sec_advanced_usage.rst +++ b/docs/sec_advanced_usage.rst @@ -34,28 +34,45 @@ functionalities: Logging (for developers) ======================== ``afmformats`` now has a simple logging system. When loading data in a script -or in the console, debug-level logs will be written to `afmformats.log` in -your machine's temp folder. This happens automatically. +or in the console, debug-level logs can be written to `afmformats.log` in +your machine's temp folder if you call +:func:`afmformats.configure_logging()`. +When running tests via ``pytest``, the logs will be written to a temp directory +as defined in :func:`tests.conftest.pytest_configure`. -Optionally, when running a local script, the ``afmformats.log`` -debug log can be output to the terminal if -you call :func:`afmformats.configure_console_logging()`: +.. code-block:: python + + import pathlib + import afmformats + + data_path = pathlib.Path("tests/data") + + # write logs to tmp/afmformats.log + afmformats.configure_logging() + + afmformats.load_data(data_path / "fmt-hdf5-fd_version_0.13.3.h5") + + +If you would like the logs also to be output to the terminal when running +scripts you can set the logging level: .. code-block:: python import pathlib import afmformats + import logging data_path = pathlib.Path("tests/data") - afmformats.configure_console_logging() + # write logs to tmp/afmformats.log and to terminal + afmformats.configure_logging(console_logging_level=logging.DEBUG) afmformats.load_data(data_path / "fmt-hdf5-fd_version_0.13.3.h5") + Output of the above script: .. code-block:: DEBUG:afmformats.formats:Loaded 1 dataset(s) from '...\afmformats\tests\data\fmt-hdf5-fd_version_0.13.3.h5' using 'HDF5-based' - Process finished with exit code 0 From 91001b824d2b222c4265be3d54e7d881d8142a96 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell Date: Thu, 9 Apr 2026 12:09:21 +0200 Subject: [PATCH 09/10] ref: correct use of log levels --- afmformats/formats/__init__.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/afmformats/formats/__init__.py b/afmformats/formats/__init__.py index 74942f9..0bad556 100644 --- a/afmformats/formats/__init__.py +++ b/afmformats/formats/__init__.py @@ -18,12 +18,10 @@ from ..mod_creep_compliance import AFMCreepCompliance from ..mod_stress_relaxation import AFMStressRelaxation - __all__ = ["AFMFormatRecipe", "find_data", "get_recipe", "load_data", "default_data_classes_by_modality", "formats_available", "formats_by_suffix", "formats_by_modality", "supported_extensions"] - logger = logging.getLogger(__name__) @@ -288,18 +286,17 @@ def load_data(path, meta_override=None, modality=None, diskcache=diskcache) afmdata.append(ddi) except BaseException: - logger.debug( + logger.exception( "Loader failed for '%s' using recipe '%s'. Traceback follows.", - path, cur_recipe, exc_info=True) + path, cur_recipe) raise logger.debug( "Loaded %d dataset(s) from '%s' using '%s'", len(afmdata), path, cur_recipe.descr) else: logger.debug( - "Loader failed for '%s' as the extension '%s' is " - "not recognised. Traceback follows.", - path, path.suffix, exc_info=True) + "Loader failed for '%s' as the extension '%s' is not recognised.", + path, path.suffix) raise ValueError("Unsupported file extension: '{}'!".format(path)) return afmdata From e3590913e592339e5c4017cef92522a2a1dd3ea6 Mon Sep 17 00:00:00 2001 From: Eoghan O'Connell <43960046+PinkShnack@users.noreply.github.com> Date: Thu, 9 Apr 2026 15:04:58 +0200 Subject: [PATCH 10/10] ref: Update sec_advanced_usage.rst --- docs/sec_advanced_usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sec_advanced_usage.rst b/docs/sec_advanced_usage.rst index 6ad6320..50f6725 100644 --- a/docs/sec_advanced_usage.rst +++ b/docs/sec_advanced_usage.rst @@ -33,7 +33,7 @@ functionalities: Logging (for developers) ======================== -``afmformats`` now has a simple logging system. When loading data in a script +``afmformats`` has a simple logging system. When loading data in a script or in the console, debug-level logs can be written to `afmformats.log` in your machine's temp folder if you call :func:`afmformats.configure_logging()`.