Skip to content

Commit

Permalink
feat: Add a configuration option to enable/disable logging uncaught e…
Browse files Browse the repository at this point in the history
…xceptions
  • Loading branch information
nfelt14 committed Jan 16, 2025
1 parent cde2458 commit 38c3cbf
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 7 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Things to be included in the next release go here.

- Removed the `traceback-with-variables` package as a dependency. If users would like to maintain the functionality, they will need to install this package separately and activate it within their code.

### Added

- Added a new configuration option `log_uncaught_exceptions` to enable/disable logging uncaught exceptions in the log file that is created. The default behavior is to enable logging uncaught exceptions to the log file.

### Changed

- Updated the error messages when VISA connections fail so that the messages are more informative and are logged to the main log file.
Expand Down
8 changes: 8 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ options:
log_file_name: tm_devices_<timestamp>.log
log_colored_output: false
log_pyvisa_messages: false
log_uncaught_exceptions: true
```

These are all `false` by default if not defined, set to `true` to modify the
Expand Down Expand Up @@ -288,6 +289,11 @@ runtime behavior configuration.
- This config option is used to enable or disable logging of PyVISA messages within the
configured log file. The default value of this config option is false. See the
[`configure_logging()`][tm_devices.helpers.logging.configure_logging] function for more information.
- `log_uncaught_exceptions`
- This config option is used to enable or disable logging uncaught exceptions in the log file.
The default value of this config option is true. Setting the `log_file_level` parameter
to "NONE" will disable this feature regardless of the value of `log_uncaught_exceptions`. See the
[`configure_logging()`][tm_devices.helpers.logging.configure_logging] function for more information.

### Sample Config File

Expand Down Expand Up @@ -355,6 +361,7 @@ options:
log_file_name: custom_logfile.log # customize the log file name
log_colored_output: false
log_pyvisa_messages: true # log PyVISA messages in the log file
log_uncaught_exceptions: true
```

#### TOML
Expand Down Expand Up @@ -436,6 +443,7 @@ log_file_directory = "./logs"
log_file_name = "custom_logfile.log" # customize the log file name
log_colored_output = false
log_pyvisa_messages = true # log PyVISA messages in the log file
log_uncaught_exceptions = true
```

---
Expand Down
1 change: 1 addition & 0 deletions examples/miscellaneous/customize_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
log_file_directory="./log_files", # save the log file in the "./log_files" directory
log_file_name="custom_log_filename.log", # customize the filename
log_pyvisa_messages=True, # include all the pyvisa debug messages in the same log file
log_uncaught_exceptions=True, # log uncaught exceptions (this is the default behavior)
)

with DeviceManager(verbose=False) as dm:
Expand Down
10 changes: 10 additions & 0 deletions src/tm_devices/helpers/constants_and_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,16 @@ class DMConfigOptions(AsDictionaryMixin):
Defaults to False. See the [`configure_logging()`][tm_devices.helpers.logging.configure_logging]
function for more information and default values.
"""
log_uncaught_exceptions: Optional[bool] = None
"""Whether to log uncaught exceptions to the log file with full tracebacks.
This behavior also reduces the traceback size of exceptions in the console. Setting
`log_file_level` to `"NONE"` will disable this feature regardless of the value of
`log_uncaught_exceptions`.
Defaults to True. See the [`configure_logging()`][tm_devices.helpers.logging.configure_logging]
function for more information and default values.
"""

def __post_init__(self) -> None:
"""Validate data after creation.
Expand Down
24 changes: 17 additions & 7 deletions src/tm_devices/helpers/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,15 @@ class LoggingLevels(CustomStrEnum):
"""An enum member indicating no logging messages should be captured."""


def configure_logging(
def configure_logging( # pylint: disable=too-many-locals
*,
log_console_level: Union[str, LoggingLevels] = LoggingLevels.INFO,
log_file_level: Union[str, LoggingLevels] = LoggingLevels.DEBUG,
log_file_directory: Optional[Union[str, os.PathLike[str], Path]] = None,
log_file_name: Optional[str] = None,
log_colored_output: bool = False,
log_pyvisa_messages: bool = False,
log_uncaught_exceptions: bool = True,
) -> logging.Logger:
"""Configure the logging for this package.
Expand All @@ -79,10 +80,13 @@ def configure_logging(
!!! important
This function will overwrite the `sys.excepthook` function in order to log uncaught
exceptions. The custom hook function used by this package will call `sys.__excepthook__`
after the custom code is run, so exceptions and tracebacks will still get printed to
the console. If you have a custom `sys.excepthook` function you need to use, you will
need to overwrite the `sys.excepthook` function after this package's logging is configured.
exceptions if logging to a log file is enabled. The custom hook function used by this
package will call `sys.__excepthook__` after the custom code is run, so exceptions and
tracebacks will still get printed to the console. If you have a custom `sys.excepthook`
function you need to use, you will need to overwrite the `sys.excepthook` function after
this package's logging is configured. To opt out of this behavior and keep Python's default
exception handling (which means exceptions will not be logged to the log file), set the
`log_uncaught_exceptions` parameter to `False`.
Args:
log_console_level: The logging level to set for the console. Defaults to INFO. Set to
Expand All @@ -99,6 +103,10 @@ def configure_logging(
console. Defaults to False.
log_pyvisa_messages: Whether to include logs from the `pyvisa` package in the log file. The
logging level will match the `file_logging_level`. Defaults to False.
log_uncaught_exceptions: Whether to log uncaught exceptions to the log file with full
tracebacks and reduce the traceback size of exceptions in the console. Defaults to True.
Setting the `log_file_level` parameter to `LoggingLevels.NONE` will
disable this feature regardless of the value of `log_uncaught_exceptions`.
Returns:
The base logger for the package, this base logger can also be accessed using
Expand Down Expand Up @@ -170,8 +178,10 @@ def configure_logging(
console_handler.setFormatter(console_formatter)
_logger.addHandler(console_handler)

# TODO: error handling: Consider adding an opt-out flag for this behavior
sys.excepthook = __exception_handler
if log_uncaught_exceptions and log_file_level != LoggingLevels.NONE:
sys.excepthook = __exception_handler
else:
sys.excepthook = sys.__excepthook__
_logger_initialized = True
return _logger

Expand Down
4 changes: 4 additions & 0 deletions tests/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ def _reset_package_logger() -> Generator[None, None, None]: # pyright: ignore[r
for handler in handlers_copy:
logger.removeHandler(handler)
tm_devices_logging._logger_initialized = False # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
temp_excepthook = sys.excepthook
yield
# Reset the handlers back to what they were
for handler in logger.handlers.copy():
logger.removeHandler(handler)
for handler in handlers_copy:
logger.addHandler(handler)
sys.excepthook = temp_excepthook


def test_configure_logger_full(reset_package_logger: None) -> None: # noqa: ARG001
Expand All @@ -90,6 +92,7 @@ def test_configure_logger_full(reset_package_logger: None) -> None: # noqa: ARG
log_file_name=log_name,
log_colored_output=False,
log_pyvisa_messages=True,
log_uncaught_exceptions=False,
)
assert len(logger.handlers) == 3
assert any(isinstance(handler, logging.FileHandler) for handler in pyvisa.logger.handlers)
Expand All @@ -104,6 +107,7 @@ def test_configure_logger_full(reset_package_logger: None) -> None: # noqa: ARG
logging.FileHandler,
logging.StreamHandler,
]
assert sys.excepthook == sys.__excepthook__ # pylint: disable=comparison-with-callable


def test_configure_logger_no_file(reset_package_logger: None) -> None: # noqa: ARG001
Expand Down

0 comments on commit 38c3cbf

Please sign in to comment.