diff --git a/nomad/config/models/common.py b/nomad/config/models/common.py
index fa89af6ae44887d291c64ab9dd84872b0f4d52ea..4c004e8023cb9ba3ea3e8e1cb6ec1f1ff99a6eea 100644
--- a/nomad/config/models/common.py
+++ b/nomad/config/models/common.py
@@ -65,7 +65,9 @@ class ConfigBaseModel(BaseModel, extra=Extra.ignore):
             return ', '.join([f'"{x}"' for x in items])
 
         if extra_fields:
-            logger = logging.getLogger(__name__)
+            from structlog import get_logger
+
+            logger = get_logger()
             logger.warning(
                 f'The following unsupported keys were found in your configuration, '
                 f'e.g. nomad.yaml: {list_items(extra_fields)}.'
diff --git a/pyproject.toml b/pyproject.toml
index d57fbcbe43586ed000581140d8c17a89de2f0e0c..00fcb8cbfa0312e50c67650ef5bcc90439c2945a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -127,10 +127,10 @@ dev = [
     'names==0.3.0',
     'uv',
     'pycodestyle',
-    'pytest-cov==2.7.1',
-    'pytest-timeout==1.4.2',
+    'pytest-cov>=2.7.1',
+    'pytest-timeout>=1.4.2',
     'pytest-xdist>=1.30.0',
-    'pytest>= 5.3.0, < 6.0.0',
+    'pytest>= 5.3.0, <8',
     'python-gitlab==2.10.1',
     'rope==0.21.0',
     'ruamel.yaml',
diff --git a/requirements-dev.txt b/requirements-dev.txt
index e9841e78c294d82ef783f41e389717e4441425f5..9bd5b8fc085b76d3e3e67532aff795356641e0cd 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,5 +1,5 @@
 # This file was autogenerated by uv via the following command:
-#    uv pip compile --annotation-style=line --extra=dev --extra=infrastructure --extra=parsing --output-file=requirements-dev.txt requirements.txt pyproject.toml
+#    uv pip compile -p 3.9 --annotation-style=line --extra=dev --extra=infrastructure --extra=parsing --output-file=requirements-dev.txt requirements.txt pyproject.toml
 aiosmtpd==1.4.6           # via nomad-lab (pyproject.toml)
 alabaster==0.7.16         # via sphinx, -r requirements.txt
 alembic==1.13.1           # via jupyterhub, -r requirements.txt
@@ -20,9 +20,9 @@ asttokens==2.4.1          # via devtools, stack-data, -r requirements.txt
 async-generator==1.10     # via jupyterhub, -r requirements.txt
 async-lru==2.0.4          # via jupyterlab, -r requirements.txt
 atpublic==4.1.0           # via aiosmtpd
-attrs==23.2.0             # via aiosmtpd, jsonschema, pytest, -r requirements.txt
+attrs==23.2.0             # via aiosmtpd, jsonschema, -r requirements.txt
 babel==2.15.0             # via jupyterlab-server, mkdocs-git-revision-date-localized-plugin, mkdocs-material, sphinx, -r requirements.txt
-backports-tarfile==1.1.1  # via jaraco-context
+backports-tarfile==1.2.0  # via jaraco-context
 bagit==1.8.1              # via -r requirements.txt, nomad-lab (pyproject.toml)
 basicauth==0.4.1          # via -r requirements.txt, nomad-lab (pyproject.toml)
 beautifulsoup4==4.12.3    # via nbconvert, -r requirements.txt, nomad-lab (pyproject.toml)
@@ -50,7 +50,7 @@ colorama==0.4.6           # via mkdocs-material, twine
 comm==0.2.2               # via ipykernel, ipywidgets, -r requirements.txt
 commonmark==0.9.1         # via recommonmark, -r requirements.txt
 contourpy==1.2.1          # via matplotlib, -r requirements.txt
-coverage==7.5.2           # via pytest-cov
+coverage==7.5.3           # via pytest-cov
 cryptography==42.0.7      # via jwcrypto, pyjwt, pyopenssl, rfc3161ng, secretstorage, -r requirements.txt
 cycler==0.12.1            # via matplotlib, -r requirements.txt
 dask==2024.5.1            # via hyperspy, kikuchipy, orix, pyxem, rosettasciio, -r requirements.txt
@@ -74,7 +74,7 @@ entrypoints==0.4          # via ipyparallel, -r requirements.txt
 escapism==1.0.1           # via dockerspawner, -r requirements.txt
 essential-generators==1.0  # via nomad-lab (pyproject.toml)
 et-xmlfile==1.1.0         # via openpyxl, -r requirements.txt
-exceptiongroup==1.2.1     # via anyio, ipython, -r requirements.txt
+exceptiongroup==1.2.1     # via anyio, ipython, pytest, -r requirements.txt
 execnet==2.1.1            # via pytest-xdist
 executing==2.0.1          # via devtools, stack-data, -r requirements.txt
 f90wrap==0.2.14           # via quippy-ase, -r requirements.txt
@@ -86,7 +86,7 @@ filelock==3.3.1           # via -r requirements.txt, nomad-lab (pyproject.toml)
 findiff==0.10.0           # via pynxtools-stm, -r requirements.txt
 flask==3.0.3              # via asr, -r requirements.txt
 flatdict==4.0.1           # via ifes-apt-tc-data-modeling, pynxtools-em, -r requirements.txt
-fonttools==4.52.1         # via matplotlib, -r requirements.txt
+fonttools==4.52.4         # via matplotlib, -r requirements.txt
 fqdn==1.5.1               # via jsonschema, -r requirements.txt
 fsspec==2024.5.0          # via dask, hyperspy, -r requirements.txt
 future==1.0.0             # via uncertainties, -r requirements.txt
@@ -95,7 +95,7 @@ gitdb==4.0.11             # via gitpython, -r requirements.txt
 gitpython==3.1.43         # via mkdocs-git-revision-date-localized-plugin, -r requirements.txt, nomad-lab (pyproject.toml)
 greenlet==3.0.3           # via sqlalchemy, -r requirements.txt
 griddataformats==1.0.2    # via mdanalysis, -r requirements.txt
-gsd==3.2.1                # via mdanalysis, -r requirements.txt
+gsd==3.3.0                # via mdanalysis, -r requirements.txt
 gunicorn==21.2.0          # via -r requirements.txt, nomad-lab (pyproject.toml)
 h11==0.14.0               # via httpcore, uvicorn, -r requirements.txt
 h5grove==1.3.0            # via jupyterlab-h5web, -r requirements.txt, nomad-lab (pyproject.toml)
@@ -115,10 +115,11 @@ imagesize==1.4.1          # via sphinx, -r requirements.txt
 importlib-metadata==7.1.0  # via build, dask, flask, hyperspy, jupyter-client, jupyter-lsp, jupyterhub, jupyterlab, jupyterlab-server, keyring, markdown, mkdocs, mkdocs-get-deps, nbconvert, pynxtools, sphinx, twine, -r requirements.txt, nomad-lab (pyproject.toml)
 importlib-resources==6.4.0  # via matplotlib, spglib, -r requirements.txt
 inflection==0.5.1         # via -r requirements.txt, nomad-lab (pyproject.toml)
+iniconfig==2.0.0          # via pytest
 ipykernel==6.29.4         # via ipyparallel, jupyter, jupyter-console, jupyterlab, qtconsole, -r requirements.txt
 ipyparallel==8.8.0        # via hyperspy, -r requirements.txt
 ipython==8.18.1           # via hyperspy, ipykernel, ipyparallel, ipywidgets, jupyter-console, pynxtools-xrd, -r requirements.txt
-ipywidgets==8.1.2         # via jupyter, -r requirements.txt
+ipywidgets==8.1.3         # via jupyter, -r requirements.txt
 isodate==0.6.1            # via rdflib, -r requirements.txt
 isoduration==20.11.0      # via jsonschema, -r requirements.txt
 itsdangerous==2.2.0       # via flask, -r requirements.txt, nomad-lab (pyproject.toml)
@@ -147,7 +148,7 @@ jupyterlab==4.1.6         # via ifes-apt-tc-data-modeling, notebook, -r requirem
 jupyterlab-h5web==11.1.0  # via ifes-apt-tc-data-modeling, -r requirements.txt
 jupyterlab-pygments==0.3.0  # via nbconvert, -r requirements.txt
 jupyterlab-server==2.24.0  # via jupyterlab, notebook, -r requirements.txt
-jupyterlab-widgets==3.0.10  # via ipywidgets, -r requirements.txt
+jupyterlab-widgets==3.0.11  # via ipywidgets, -r requirements.txt
 jwcrypto==1.5.6           # via python-keycloak, -r requirements.txt
 keyring==25.2.1           # via twine
 kikuchipy==0.9.0          # via pynxtools-em, -r requirements.txt
@@ -185,7 +186,7 @@ mmtf-python==1.1.3        # via mdanalysis, -r requirements.txt
 mongoengine==0.28.2       # via -r requirements.txt, nomad-lab (pyproject.toml)
 mongomock==4.1.2          # via optimade, -r requirements.txt
 monty==2024.5.24          # via pymatgen, -r requirements.txt
-more-itertools==10.2.0    # via jaraco-classes, jaraco-functools, pytest
+more-itertools==10.2.0    # via jaraco-classes, jaraco-functools
 mpmath==1.3.0             # via sympy, -r requirements.txt
 mrcfile==1.5.0            # via griddataformats, -r requirements.txt
 msgpack==1.0.8            # via blosc2, mmtf-python, -r requirements.txt, nomad-lab (pyproject.toml)
@@ -216,7 +217,7 @@ numpy==1.22.4             # via ase, biopython, blosc2, cftime, contourpy, dask,
 numpy-quaternion==2023.0.3  # via orix, -r requirements.txt
 oauthenticator==15.1.0    # via -r requirements.txt, nomad-lab (pyproject.toml)
 oauthlib==3.2.2           # via jupyterhub, -r requirements.txt
-openpyxl==3.1.2           # via -r requirements.txt, nomad-lab (pyproject.toml)
+openpyxl==3.1.3           # via -r requirements.txt, nomad-lab (pyproject.toml)
 optimade==0.22.1          # via -r requirements.txt, nomad-lab (pyproject.toml)
 orix==0.12.1.post0        # via diffsims, kikuchipy, pyxem, -r requirements.txt
 orjson==3.10.3            # via h5grove, -r requirements.txt, nomad-lab (pyproject.toml)
@@ -238,16 +239,15 @@ pint==0.17                # via hyperspy, pynxtools-xps, rosettasciio, -r requir
 pkginfo==1.10.0           # via twine
 platformdirs==4.2.2       # via jupyter-core, mkdocs-get-deps, pooch, xraydb, -r requirements.txt
 plotly==5.22.0            # via asr, pymatgen, -r requirements.txt
-pluggy==0.13.1            # via pytest
+pluggy==1.5.0             # via pytest
 ply==3.11                 # via pycifrw, -r requirements.txt
 pooch==1.8.1              # via kikuchipy, orix, -r requirements.txt
 prettytable==3.10.0       # via hyperspy, -r requirements.txt
 prometheus-client==0.20.0  # via jupyter-server, jupyterhub, -r requirements.txt
-prompt-toolkit==3.0.43    # via click-repl, ipython, jupyter-console, -r requirements.txt
+prompt-toolkit==3.0.45    # via click-repl, ipython, jupyter-console, -r requirements.txt
 psutil==5.9.8             # via diffsims, ipykernel, ipyparallel, pyxem, -r requirements.txt
 ptyprocess==0.7.0         # via pexpect, terminado, -r requirements.txt
 pure-eval==0.2.2          # via stack-data, -r requirements.txt
-py==1.11.0                # via pytest, pytest-forked
 py-cpuinfo==9.0.0         # via blosc2, tables, -r requirements.txt
 pyasn1==0.6.0             # via pyasn1-modules, rfc3161ng, -r requirements.txt
 pyasn1-modules==0.4.0     # via rfc3161ng, -r requirements.txt
@@ -276,11 +276,10 @@ pyopenssl==24.1.0         # via certipy, -r requirements.txt
 pyparsing==3.1.2          # via matplotlib, rdflib, -r requirements.txt
 pyproject-hooks==1.1.0    # via build
 pyrsistent==0.20.0        # via jsonschema, -r requirements.txt
-pytest==5.4.3             # via pytest-cov, pytest-forked, pytest-timeout, pytest-xdist, nomad-lab (pyproject.toml)
-pytest-cov==2.7.1         # via nomad-lab (pyproject.toml)
-pytest-forked==1.6.0      # via pytest-xdist
-pytest-timeout==1.4.2     # via nomad-lab (pyproject.toml)
-pytest-xdist==1.34.0      # via nomad-lab (pyproject.toml)
+pytest==7.4.4             # via pytest-cov, pytest-timeout, pytest-xdist, nomad-lab (pyproject.toml)
+pytest-cov==5.0.0         # via nomad-lab (pyproject.toml)
+pytest-timeout==2.3.1     # via nomad-lab (pyproject.toml)
+pytest-xdist==3.6.1       # via nomad-lab (pyproject.toml)
 python-box==6.1.0         # via rosettasciio, -r requirements.txt
 python-dateutil==2.9.0.post0  # via arrow, celery, elasticsearch-dsl, ghp-import, hyperspy, ipyparallel, jupyter-client, jupyterhub, matplotlib, mkdocs-macros-plugin, pandas, pybis, rfc3161ng, rosettasciio, -r requirements.txt
 python-dotenv==1.0.1      # via uvicorn, -r requirements.txt
@@ -317,7 +316,7 @@ rope==0.21.0              # via nomad-lab (pyproject.toml)
 rosettasciio==0.4         # via pynxtools-em, -r requirements.txt
 ruamel-yaml==0.18.6       # via jupyter-telemetry, oauthenticator, pymatgen, -r requirements.txt, nomad-lab (pyproject.toml)
 ruamel-yaml-clib==0.2.8   # via ruamel-yaml, -r requirements.txt
-ruff==0.4.5               # via nomad-lab (pyproject.toml)
+ruff==0.4.6               # via nomad-lab (pyproject.toml)
 runstats==2.0.0           # via -r requirements.txt, nomad-lab (pyproject.toml)
 scikit-image==0.19.3      # via hyperspy, kikuchipy, pyxem, -r requirements.txt
 scikit-learn==1.5.0       # via kikuchipy, matid, pyxem, -r requirements.txt, nomad-lab (pyproject.toml)
@@ -327,7 +326,7 @@ send2trash==1.8.3         # via jupyter-server, -r requirements.txt
 sentinels==1.0.0          # via mongomock, -r requirements.txt
 setuptools==70.0.0        # via radioactivedecay, -r requirements.txt
 silx==2.1.0               # via pyfai, -r requirements.txt
-six==1.16.0               # via anytree, asttokens, basicauth, bleach, diffpy-structure, elasticsearch-dsl, html5lib, isodate, pybtex, pytest-xdist, python-dateutil, rdflib, rfc3339-validator, validators, -r requirements.txt
+six==1.16.0               # via anytree, asttokens, basicauth, bleach, diffpy-structure, elasticsearch-dsl, html5lib, isodate, pybtex, python-dateutil, rdflib, rfc3339-validator, validators, -r requirements.txt
 smmap==5.0.1              # via gitdb, -r requirements.txt
 sniffio==1.3.1            # via anyio, httpx, -r requirements.txt
 snowballstemmer==2.2.0    # via sphinx, -r requirements.txt
@@ -344,8 +343,8 @@ sphinxcontrib-serializinghtml==1.1.10  # via sphinx, -r requirements.txt
 sqlalchemy==2.0.30        # via alembic, jupyterhub, xraydb, -r requirements.txt
 stack-data==0.6.3         # via ipython, -r requirements.txt
 starlette==0.27.0         # via fastapi, -r requirements.txt
-structlog==24.1.0         # via -r requirements.txt, nomad-lab (pyproject.toml)
-sympy==1.12               # via findiff, hyperspy, pymatgen, radioactivedecay, -r requirements.txt
+structlog==24.2.0         # via -r requirements.txt, nomad-lab (pyproject.toml)
+sympy==1.12.1             # via findiff, hyperspy, pymatgen, radioactivedecay, -r requirements.txt
 tables==3.9.2             # via ifes-apt-tc-data-modeling, -r requirements.txt
 tabulate==0.8.9           # via pybis, pymatgen, -r requirements.txt, nomad-lab (pyproject.toml)
 tenacity==8.3.0           # via plotly, -r requirements.txt
@@ -355,7 +354,7 @@ texttable==1.7.0          # via pybis, -r requirements.txt
 threadpoolctl==3.5.0      # via mdanalysis, scikit-learn, -r requirements.txt
 tifffile==2024.5.22       # via h5grove, hyperspy, scikit-image, -r requirements.txt
 tinycss2==1.3.0           # via nbconvert, -r requirements.txt
-tomli==2.0.1              # via build, jupyterlab, mypy, sphinx, -r requirements.txt
+tomli==2.0.1              # via build, coverage, jupyterlab, mypy, pytest, sphinx, -r requirements.txt
 toolz==0.12.1             # via dask, hyperspy, partd, -r requirements.txt
 toposort==1.10            # via -r requirements.txt, nomad-lab (pyproject.toml)
 tornado==6.4              # via ipykernel, ipyparallel, jupyter-client, jupyter-server, jupyterhub, jupyterlab, notebook, terminado, -r requirements.txt
@@ -374,20 +373,20 @@ uncertainties==3.1.7      # via lmfit, pymatgen, -r requirements.txt
 unidecode==1.3.2          # via -r requirements.txt, nomad-lab (pyproject.toml)
 uri-template==1.3.0       # via jsonschema, -r requirements.txt
 urllib3==1.26.18          # via docker, elasticsearch, pybis, requests, -r requirements.txt
-uv==0.2.4                 # via nomad-lab (pyproject.toml)
-uvicorn==0.29.0           # via h5grove, -r requirements.txt, nomad-lab (pyproject.toml)
+uv==0.2.5                 # via nomad-lab (pyproject.toml)
+uvicorn==0.30.0           # via h5grove, -r requirements.txt, nomad-lab (pyproject.toml)
 uvloop==0.19.0            # via uvicorn, -r requirements.txt
 validators==0.18.2        # via -r requirements.txt, nomad-lab (pyproject.toml)
 vine==5.1.0               # via amqp, celery, kombu, -r requirements.txt
 watchdog==4.0.1           # via mkdocs
 watchfiles==0.22.0        # via uvicorn, -r requirements.txt
-wcwidth==0.2.13           # via prettytable, prompt-toolkit, pytest, -r requirements.txt
+wcwidth==0.2.13           # via prettytable, prompt-toolkit, -r requirements.txt
 webcolors==1.13           # via jsonschema, -r requirements.txt
 webencodings==0.5.1       # via bleach, html5lib, tinycss2, -r requirements.txt
 websocket-client==1.8.0   # via jupyter-server, -r requirements.txt
 websockets==12.0          # via uvicorn, -r requirements.txt
 werkzeug==3.0.3           # via flask, -r requirements.txt
-widgetsnbextension==4.0.10  # via ipywidgets, -r requirements.txt
+widgetsnbextension==4.0.11  # via ipywidgets, -r requirements.txt
 wrapt==1.16.0             # via -r requirements.txt, nomad-lab (pyproject.toml)
 xarray==2023.12.0         # via pynxtools, pynxtools-mpes, pynxtools-xps, -r requirements.txt, nomad-lab (pyproject.toml)
 xmltodict==0.13.0         # via ifes-apt-tc-data-modeling, pynxtools-em, -r requirements.txt
diff --git a/requirements.txt b/requirements.txt
index edf2fa30f00b713925909bb0aae0a044c15861a7..30decbfbf9e0b77ca01b40fd906026b41e43d452 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 # This file was autogenerated by uv via the following command:
-#    uv pip compile --annotation-style=line --extra=infrastructure --extra=parsing --output-file=requirements.txt dependencies/nomad-dos-fingerprints/pyproject.toml dependencies/parsers/eelsdb/pyproject.toml pyproject.toml
+#    uv pip compile -p 3.9 --annotation-style=line --extra=infrastructure --extra=parsing --output-file=requirements.txt dependencies/nomad-dos-fingerprints/pyproject.toml dependencies/parsers/eelsdb/pyproject.toml pyproject.toml
 alabaster==0.7.16         # via sphinx
 alembic==1.13.1           # via jupyterhub
 amqp==5.2.0               # via kombu
@@ -76,7 +76,7 @@ filelock==3.3.1           # via nomad-lab (pyproject.toml)
 findiff==0.10.0           # via pynxtools-stm
 flask==3.0.3              # via asr
 flatdict==4.0.1           # via ifes-apt-tc-data-modeling, pynxtools-em
-fonttools==4.52.1         # via matplotlib
+fonttools==4.52.4         # via matplotlib
 fqdn==1.5.1               # via jsonschema
 fsspec==2024.5.0          # via dask, hyperspy
 future==1.0.0             # via uncertainties
@@ -84,7 +84,7 @@ gitdb==4.0.11             # via gitpython
 gitpython==3.1.43         # via nomad-lab (pyproject.toml)
 greenlet==3.0.3           # via sqlalchemy
 griddataformats==1.0.2    # via mdanalysis
-gsd==3.2.1                # via mdanalysis
+gsd==3.3.0                # via mdanalysis
 gunicorn==21.2.0          # via nomad-lab (pyproject.toml)
 h11==0.14.0               # via httpcore, uvicorn
 h5grove==1.3.0            # via jupyterlab-h5web, nomad-lab (pyproject.toml)
@@ -107,7 +107,7 @@ inflection==0.5.1         # via nomad-lab (pyproject.toml)
 ipykernel==6.29.4         # via ipyparallel, jupyter, jupyter-console, jupyterlab, qtconsole
 ipyparallel==8.8.0        # via hyperspy
 ipython==8.18.1           # via hyperspy, ipykernel, ipyparallel, ipywidgets, jupyter-console, pynxtools-xrd
-ipywidgets==8.1.2         # via jupyter
+ipywidgets==8.1.3         # via jupyter
 isodate==0.6.1            # via rdflib
 isoduration==20.11.0      # via jsonschema
 itsdangerous==2.2.0       # via flask, nomad-lab (pyproject.toml)
@@ -132,7 +132,7 @@ jupyterlab==4.1.6         # via ifes-apt-tc-data-modeling, notebook
 jupyterlab-h5web==11.1.0  # via ifes-apt-tc-data-modeling
 jupyterlab-pygments==0.3.0  # via nbconvert
 jupyterlab-server==2.24.0  # via jupyterlab, notebook
-jupyterlab-widgets==3.0.10  # via ipywidgets
+jupyterlab-widgets==3.0.11  # via ipywidgets
 jwcrypto==1.5.6           # via python-keycloak
 kikuchipy==0.9.0          # via pynxtools-em
 kiwisolver==1.4.5         # via matplotlib
@@ -185,7 +185,7 @@ numpy==1.22.4             # via ase, biopython, blosc2, cftime, contourpy, dask,
 numpy-quaternion==2023.0.3  # via orix
 oauthenticator==15.1.0    # via nomad-lab (pyproject.toml)
 oauthlib==3.2.2           # via jupyterhub
-openpyxl==3.1.2           # via nomad-lab (pyproject.toml)
+openpyxl==3.1.3           # via nomad-lab (pyproject.toml)
 optimade==0.22.1          # via nomad-lab (pyproject.toml)
 orix==0.12.1.post0        # via diffsims, kikuchipy, pyxem
 orjson==3.10.3            # via h5grove, nomad-lab (pyproject.toml)
@@ -208,7 +208,7 @@ ply==3.11                 # via pycifrw
 pooch==1.8.1              # via kikuchipy, orix
 prettytable==3.10.0       # via hyperspy
 prometheus-client==0.20.0  # via jupyter-server, jupyterhub
-prompt-toolkit==3.0.43    # via click-repl, ipython, jupyter-console
+prompt-toolkit==3.0.45    # via click-repl, ipython, jupyter-console
 psutil==5.9.8             # via diffsims, ipykernel, ipyparallel, pyxem
 ptyprocess==0.7.0         # via pexpect, terminado
 pure-eval==0.2.2          # via stack-data
@@ -258,7 +258,7 @@ radioactivedecay==0.5.0   # via ifes-apt-tc-data-modeling
 rdflib==5.0.0             # via nomad-lab (pyproject.toml)
 rdkit==2023.9.5           # via nomad-lab (pyproject.toml)
 recommonmark==0.7.1       # via nomad-lab (pyproject.toml)
-requests==2.32.2          # via docker, hyperspy, jupyterhub, jupyterlab-server, oauthenticator, optimade, pooch, pybis, pymatgen, python-keycloak, requests-toolbelt, rfc3161ng, sphinx, eelsdbconverter (dependencies/parsers/eelsdb/pyproject.toml), nomad-lab (pyproject.toml)
+requests==2.31.0          # via docker, hyperspy, jupyterhub, jupyterlab-server, oauthenticator, optimade, pooch, pybis, pymatgen, python-keycloak, requests-toolbelt, rfc3161ng, sphinx, eelsdbconverter (dependencies/parsers/eelsdb/pyproject.toml), nomad-lab (pyproject.toml)
 requests-toolbelt==1.0.0  # via python-keycloak
 rfc3161ng==2.1.3          # via nomad-lab (pyproject.toml)
 rfc3339-validator==0.1.4  # via jsonschema
@@ -292,8 +292,8 @@ sphinxcontrib-serializinghtml==1.1.10  # via sphinx
 sqlalchemy==2.0.30        # via alembic, jupyterhub, xraydb
 stack-data==0.6.3         # via ipython
 starlette==0.27.0         # via fastapi
-structlog==24.1.0         # via nomad-lab (pyproject.toml)
-sympy==1.12               # via findiff, hyperspy, pymatgen, radioactivedecay
+structlog==24.2.0         # via nomad-lab (pyproject.toml)
+sympy==1.12.1             # via findiff, hyperspy, pymatgen, radioactivedecay
 tables==3.9.2             # via ifes-apt-tc-data-modeling
 tabulate==0.8.9           # via pybis, pymatgen, nomad-lab (pyproject.toml)
 tenacity==8.3.0           # via plotly
@@ -319,7 +319,7 @@ uncertainties==3.1.7      # via lmfit, pymatgen
 unidecode==1.3.2          # via nomad-lab (pyproject.toml)
 uri-template==1.3.0       # via jsonschema
 urllib3==1.26.18          # via docker, elasticsearch, pybis, requests
-uvicorn==0.29.0           # via h5grove, nomad-lab (pyproject.toml)
+uvicorn==0.30.0           # via h5grove, nomad-lab (pyproject.toml)
 uvloop==0.19.0            # via uvicorn
 validators==0.18.2        # via nomad-lab (pyproject.toml)
 vine==5.1.0               # via amqp, celery, kombu
@@ -330,7 +330,7 @@ webencodings==0.5.1       # via bleach, html5lib, tinycss2
 websocket-client==1.8.0   # via jupyter-server
 websockets==12.0          # via uvicorn
 werkzeug==3.0.3           # via flask
-widgetsnbextension==4.0.10  # via ipywidgets
+widgetsnbextension==4.0.11  # via ipywidgets
 wrapt==1.16.0             # via nomad-lab (pyproject.toml)
 xarray==2023.12.0         # via pynxtools, pynxtools-mpes, pynxtools-xps, nomad-lab (pyproject.toml)
 xmltodict==0.13.0         # via ifes-apt-tc-data-modeling, pynxtools-em
diff --git a/scripts/generate_python_dependencies.sh b/scripts/generate_python_dependencies.sh
index 02890282fd762c6f6c305a2a7ce0e90f8cdf48a2..a08c88fdb9e7eb849a84b967a896a7fb1b382c75 100755
--- a/scripts/generate_python_dependencies.sh
+++ b/scripts/generate_python_dependencies.sh
@@ -15,14 +15,14 @@ project_dir=$(dirname $(dirname $(realpath $0)))
 cd $project_dir
 
 
-uv pip compile -U --annotation-style=line \
+uv pip compile -U -p 3.9 --annotation-style=line \
     --extra=infrastructure --extra=parsing \
     --output-file=requirements.txt \
     dependencies/nomad-dos-fingerprints/pyproject.toml \
     dependencies/parsers/eelsdb/pyproject.toml \
     pyproject.toml
 
-uv pip compile -U --annotation-style=line \
+uv pip compile -U -p 3.9 --annotation-style=line \
     --extra=dev --extra=infrastructure --extra=parsing \
     --output-file=requirements-dev.txt \
     requirements.txt \
diff --git a/tests/conftest.py b/tests/conftest.py
index 5c6579ce029f814a57bb95c00d393804548f20d7..97b280296e0c35c8839283b9ab5daeb32f38c411 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -70,6 +70,31 @@ pytest_plugins = (
 )
 
 
+from structlog.testing import LogCapture
+import structlog
+
+
+@pytest.fixture(scope='function')
+def log_output():
+    cap = LogCapture()
+    # Modify `_Configuration.default_processors` set via `configure` but always
+    # keep the list instance intact to not break references held by bound
+    # loggers.
+    processors = structlog.get_config()['processors']
+    old_processors = processors.copy()
+    try:
+        # clear processors list and use LogCapture for testing
+        processors.clear()
+        processors.append(cap)
+        structlog.configure(processors=processors)
+        yield cap
+    finally:
+        # remove LogCapture and restore original processors
+        processors.clear()
+        processors.extend(old_processors)
+        structlog.configure(processors=processors)
+
+
 def pytest_addoption(parser):
     help = 'Set this < 1.0 to speed up worker cleanup. May leave tasks running.'
     parser.addoption('--celery-inspect-timeout', type=float, default=1.0, help=help)
@@ -141,35 +166,30 @@ def nomad_logging(monkeysession):
 
 
 @pytest.fixture(scope='function')
-def no_warn(caplog):
-    caplog.handler.formatter = structlogging.ConsoleFormatter()
-    yield caplog
-    for record in caplog.get_records(when='call'):
-        if record.levelname in ['WARNING', 'ERROR', 'CRITICAL']:
-            try:
-                msg = structlogging.ConsoleFormatter.serialize(json.loads(record.msg))
-            except Exception:
-                msg = record.msg
-            assert False, msg
+def no_warn(log_output):
+    yield log_output
+    for record in log_output.entries:
+        if record['log_level'] in ['error', 'critical', 'warning']:
+            assert False, record
 
 
 @pytest.fixture(scope='function')
-def with_error(caplog):
-    yield caplog
+def with_error(log_output):
+    yield log_output
     count = 0
-    for record in caplog.get_records(when='call'):
-        if record.levelname in ['ERROR', 'CRITICAL']:
+    for record in log_output.entries:
+        if record['log_level'] in ['error', 'critical']:
             count += 1
 
     assert count > 0
 
 
 @pytest.fixture(scope='function')
-def with_warn(caplog):
-    yield caplog
+def with_warn(log_output):
+    yield log_output
     count = 0
-    for record in caplog.get_records(when='call'):
-        if record.levelname in ['WARNING']:
+    for record in log_output.entries:
+        if record['log_level'] in ['warning']:
             count += 1
 
     assert count > 0
diff --git a/tests/datamodel/test_schema.py b/tests/datamodel/test_schema.py
index 4cffe1ec40eef917373d8b8149a87de86d960ac1..e8710fe3e0431df304df9456ec8593f937135ae5 100644
--- a/tests/datamodel/test_schema.py
+++ b/tests/datamodel/test_schema.py
@@ -55,7 +55,7 @@ def test_schema_processing(raw_files_function, no_warn):
     assert test_archive.metadata.entry_type == 'Schema'
 
 
-def test_eln_annotation_validation_parsing(raw_files_function, caplog):
+def test_eln_annotation_validation_parsing(raw_files_function, log_output):
     mainfile = os.path.join(
         os.path.dirname(__file__), '../data/datamodel/eln.archive.yaml'
     )
@@ -67,8 +67,8 @@ def test_eln_annotation_validation_parsing(raw_files_function, caplog):
         parser.parse(mainfile, test_archive, get_logger(__name__))
 
     has_error = False
-    for record in caplog.get_records(when='call'):
-        if record.levelname == 'ERROR':
+    for record in log_output.entries:
+        if record['log_level'] == 'error':
             has_error = True
 
     assert has_error
diff --git a/tests/fixtures/group_uploads.py b/tests/fixtures/group_uploads.py
index 9d0e7d3734ab02b5dc3d38497fd8dde4f39e9eb5..9b03ae9e69db4eab8df5d639c96cb4cd848a252c 100644
--- a/tests/fixtures/group_uploads.py
+++ b/tests/fixtures/group_uploads.py
@@ -16,7 +16,7 @@ import pytest
 from nomad.utils.exampledata import ExampleData
 
 
-@pytest.fixture('session')
+@pytest.fixture(scope='session')
 def group_upload_molds(
     convert_user_labels_to_ids, convert_group_labels_to_ids, user1, user2
 ):
@@ -59,7 +59,7 @@ def group_upload_molds(
     return molds
 
 
-@pytest.fixture('session')
+@pytest.fixture(scope='session')
 def create_group_uploads_from_molds(group_upload_molds):
     """Returned function creates and returns uploads with given labels.
 
diff --git a/tests/metainfo/test_metainfo.py b/tests/metainfo/test_metainfo.py
index 565c46944116f5de058ef429d4705f6f8bbd2e05..a4f780ed1d234246ed16a2a43b55cbb8ca91db49 100644
--- a/tests/metainfo/test_metainfo.py
+++ b/tests/metainfo/test_metainfo.py
@@ -848,7 +848,7 @@ class TestM1:
         section.f32 = -200
         section.f64 = -200
 
-    def test_np_allow_wrong_shape(self, caplog):
+    def test_np_allow_wrong_shape(self, log_output):
         class MyContext(Context):
             def warning(self, event, **kwargs):
                 utils.get_logger(__name__).warn(event, **kwargs)
@@ -856,7 +856,7 @@ class TestM1:
         scc = SCC(m_context=MyContext())
         scc.energy_total_0 = np.array([1.0, 1.0, 1.0])
         scc.m_to_dict()
-        test_utils.assert_log(caplog, 'WARNING', 'numpy quantity has wrong shape')
+        test_utils.assert_log(log_output, 'WARNING', 'numpy quantity has wrong shape')
 
     def test_copy(self):
         run = Run()
diff --git a/tests/parsing/test_parsing.py b/tests/parsing/test_parsing.py
index db25370daac7be412687ffbb1d391bf45d5e5302..3f339d622377d9f58597844da57f3698f2a9bcf6 100644
--- a/tests/parsing/test_parsing.py
+++ b/tests/parsing/test_parsing.py
@@ -112,16 +112,16 @@ def create_reference(data, pretty):
 
 
 @pytest.fixture(scope='function')
-def assert_parser_result(caplog):
+def assert_parser_result(log_output):
     def _assert(
         entry_archive: EntryArchive, has_errors: bool = False, has_warnings: bool = None
     ):
         errors_exist = False
         warnings_exist = False
-        for record in caplog.get_records(when='call'):
-            if record.levelname in ['ERROR', 'CRITICAL']:
+        for record in log_output.entries:
+            if record['log_level'] in ['error', 'critical']:
                 errors_exist = True
-            if record.levelname in ['WARNING']:
+            if record['log_level'] in ['warning']:
                 warnings_exist = True
         assert has_errors == errors_exist
         if has_warnings is not None:
diff --git a/tests/processing/test_base.py b/tests/processing/test_base.py
index 664845942043ea7ae64cac96e2d59a5bfc1d5cac..03d8c6bb1bbd80aadf471c073422c32df57917c8 100644
--- a/tests/processing/test_base.py
+++ b/tests/processing/test_base.py
@@ -138,10 +138,10 @@ def test_failing_process(worker, mongo_function, with_error):
     assert_proc(p, process, ProcessStatus.FAILURE, errors=1)
 
     has_log = False
-    for record in with_error.get_records(when='call'):
-        if record.levelname == 'ERROR':
+    for record in with_error.entries:
+        if record['log_level'] == 'error':
             has_log = True
-            assert json.loads(record.msg)['event'] == event
+            assert record['event'] == event
     assert has_log
 
 
diff --git a/tests/test_config.py b/tests/test_config.py
index e6103fc7b63b1f2baeed93993d4764214bcda197..8938afe3b0624849a2cf2316499174a808201172 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -111,12 +111,20 @@ def test_config_success(config_dict, format, mockopen, monkeypatch):
 )
 @pytest.mark.parametrize('format', ['yaml', 'env'])
 def test_config_warning(
-    config_dict, format, warning, formats_with_warning, caplog, mockopen, monkeypatch
+    config_dict,
+    format,
+    warning,
+    formats_with_warning,
+    log_output,
+    mockopen,
+    monkeypatch,
 ):
     """Tests that extra fields create a warning message."""
     conf_yaml, conf_env = load_format(config_dict, format)
     load_test_config(conf_yaml, conf_env, mockopen, monkeypatch)
-    assert_log(caplog, 'WARNING', warning, negate=format not in formats_with_warning)
+    assert_log(
+        log_output, 'WARNING', warning, negate=format not in formats_with_warning
+    )
 
 
 @pytest.mark.parametrize(
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 9d14b43ae5b7eb9d2696506c4fa094f5103bb80f..91ca1f2c6aca9cbef408007c0045507c052f8d23 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -43,11 +43,11 @@ def test_decode_handle_id():
         utils.decode_handle_id('zz')
 
 
-def test_timer(caplog):
+def test_timer(log_output):
     with utils.timer(utils.get_logger('test_logger'), 'test measure'):
         time.sleep(0.1)
 
-    assert json.loads(caplog.record_tuples[0][2])['event'] == 'test measure'
+    assert log_output.entries[0]['event'] == 'test measure'
 
 
 def test_sleep_timer():
@@ -75,11 +75,10 @@ def test_logging(no_warn):
     utils.get_logger(__name__).info('test msg')
 
     received_test_event = False
-    for record in no_warn.get_records(when='call'):
-        assert record.levelname == 'INFO'
-        data = json.loads(record.msg)
-        assert 'event' in data
-        assert data['event'] == 'test msg'
+    for record in no_warn.entries:
+        assert record['log_level'] == 'info'
+        assert 'event' in record
+        assert record['event'] == 'test msg'
         received_test_event = True
     assert received_test_event
 
diff --git a/tests/utils.py b/tests/utils.py
index 4a4b347d90980bd79452c3612eb1a0ec4557a4e9..4ae5c223df4abd17cb377c0825046a1012656c49 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -24,17 +24,19 @@ import urllib.parse
 import zipfile
 from logging import LogRecord
 from typing import Any, Dict, List, Union
-
+from structlog import get_logger
 import pytest
 
 
-def assert_log(caplog, level: str, event_part: str, negate: bool = False) -> LogRecord:
+def assert_log(
+    log_output, level: str, event_part: str, negate: bool = False
+) -> LogRecord:
     """
     Assert whether a log message exists in the logs of the tests at a certain level.
 
     Parameters
     ----------
-    caplog : pytest fixture
+    log_output : pytest fixture
         This informs pytest that we want to access the logs from a pytest test.
     level : str
         The level of type of log for which we will search (e.g. 'WARN',
@@ -45,14 +47,9 @@ def assert_log(caplog, level: str, event_part: str, negate: bool = False) -> Log
     negate: Instead asserting that the log exist, assert that it does not exist.
     """
     match = None
-    for record in caplog.get_records(when='call'):
-        if record.levelname == level:
-            try:
-                event_data = json.loads(record.msg)
-                present = event_part in event_data['event']
-            except Exception:
-                present = event_part in record.msg
-
+    for record in log_output.entries:
+        if record['log_level'] == level.lower():
+            present = event_part in record['event']
             if present:
                 match = record
                 # No need to look for more matches since we aren't counting matches.