diff --git a/pyproject.toml b/pyproject.toml index 99deb8d5f..08c0b6f9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "appdirs >=1,<2", "numpy >=2.0.1,<3", "nutils-poly >=1,<2", - "ags[yaml] >=0.3,<0.4", + "ags[yaml] >=0.4,<0.5", "stringly", "treelog >=2,<3", ] diff --git a/src/nutils/_util.py b/src/nutils/_util.py index 2097820ce..a594cf3a5 100644 --- a/src/nutils/_util.py +++ b/src/nutils/_util.py @@ -24,6 +24,7 @@ import re import linecache import hashlib +import traceback from typing import Iterable, Sequence, Tuple supports_outdirfd = os.open in os.supports_dir_fd and os.listdir in os.supports_fd @@ -384,7 +385,8 @@ def defaults_from_env(f): try: v = ucsl.loads(os.environ[envname], param.annotation) except Exception as e: - warnings.warn(f'ignoring environment variable {envname}: {e}') + warnings.warn(f'ignoring environment variable {envname}') + log_exception(e) else: param = param.replace(default=v) changed = True @@ -504,7 +506,7 @@ def log_arguments(*args, **kwargs): bound.apply_defaults() treelog.info(yaml.dumps(bound, sig).rstrip()) except Exception as e: - treelog.error("failed to serialize arguments:", e) + log_exception(e) return f(*args, **kwargs) return log_arguments @@ -525,6 +527,22 @@ def post_mortem(pdb: bool = False): # pragma: no cover raise +def log_exception(e): + tbexc = traceback.TracebackException.from_exception(e) + prefix = '' + while True: + treelog.error(prefix + ''.join(tbexc.format_exception_only()).rstrip()) + treelog.debug('Traceback (most recent call first):\n' + ''.join(reversed(tbexc.stack.format())).rstrip()) + if tbexc.__cause__ is not None: + tbexc = tbexc.__cause__ + prefix = '.. caused by ' + elif tbexc.__context__ is not None and not tbexc.__suppress_context__: + tbexc = tbexc.__context__ + prefix = '.. while handling ' + else: + break + + @contextlib.contextmanager @defaults_from_env def log_traceback(gracefulexit: bool = True): @@ -533,31 +551,15 @@ def log_traceback(gracefulexit: bool = True): Afterwards ``SystemExit`` is raised to avoid reprinting of the traceback by Python's default error handler.''' - import traceback - if not gracefulexit: yield return try: yield - except SystemExit: - raise - except: - exc = traceback.TracebackException(*sys.exc_info()) - prefix = '' - while True: - treelog.error(prefix + ''.join(exc.format_exception_only()).rstrip()) - treelog.debug('Traceback (most recent call first):\n' + ''.join(reversed(exc.stack.format())).rstrip()) - if exc.__cause__ is not None: - exc = exc.__cause__ - prefix = '.. caused by ' - elif exc.__context__ is not None and not exc.__suppress_context__: - exc = exc.__context__ - prefix = '.. while handling ' - else: - break - raise SystemExit(1) + except Exception as e: + log_exception(e) + raise SystemExit(1) from None @contextlib.contextmanager @@ -677,7 +679,7 @@ def add_htmllog(outrootdir: str = '~/public_html', outrooturi: str = '', scriptn yield except Exception as e: with treelog.set(htmllog): - treelog.error(f'{e.__class__.__name__}: {e}') + log_exception(e) raise finally: treelog.info(f'log written to: {loguri}') diff --git a/tests/test_util.py b/tests/test_util.py index 549ac958b..9ce7aab6d 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -230,7 +230,10 @@ def f(foo, bar): with self.assertLogs('nutils') as cm: f('x', 10) - self.assertEqual(cm.output, ['ERROR:nutils:arguments > failed to serialize arguments: in .foo: cannot establish type for parameter foo']) + if sys.version_info >= (3, 11): + self.assertEqual(cm.output, ['ERROR:nutils:arguments > TypeError: cannot establish type for parameter foo\nIn: .foo']) + else: + self.assertEqual(cm.output, ['ERROR:nutils:arguments > TypeError: cannot establish type for parameter foo']) class log_traceback(TestCase):