Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions src/ipsdk/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def process_data(data):

if critical_error:
logging.fatal("Critical failure, cannot continue")
# This will log at FATAL level, print to console, and exit with code 1
# Logs at FATAL level and raises SystemExit(1)

Sensitive data filtering::

Expand Down Expand Up @@ -117,6 +117,7 @@ def process_data(data):
from functools import partial
from functools import wraps
from typing import Any
from typing import NoReturn
from typing import TypeVar

from . import heuristics
Expand Down Expand Up @@ -293,25 +294,23 @@ def exception(exc: Exception) -> None:
log(logging.ERROR, tb_text)


def fatal(msg: str) -> None:
def fatal(msg: str) -> NoReturn:
"""Log a fatal error and exit the application.

A fatal error will log the message using level 90 (FATAL) and print
an error message to stderr. It will then exit the application with
return code 1.
A fatal error logs the message at FATAL level (90) and raises
SystemExit(1). Equivalent to sys.exit(1) but explicit and testable.

Args:
msg (str): The message to print.
msg (str): The message to log.

Returns:
None
NoReturn

Raises:
SystemExit: Always raised with exit code 1 after logging the fatal error.
SystemExit: Always raised with exit code 1 after logging.
"""
log(FATAL, msg)
print(f"ERROR: {msg}", file=sys.stderr)
sys.exit(1)
raise SystemExit(1)


@cache
Expand Down
41 changes: 17 additions & 24 deletions tests/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,35 +261,28 @@ def test_exception_function_with_different_exceptions(self):
class TestFatalFunction:
"""Test the fatal logging function."""

def test_fatal_function_logs_and_exits(self):
"""Test that fatal function logs message and exits."""
with (
patch("ipsdk.logging.log") as mock_log,
patch("builtins.print") as mock_print,
patch("sys.exit") as mock_exit,
):
ipsdk_logging.fatal("fatal error")

def test_fatal_raises_system_exit(self):
"""Test that fatal raises SystemExit with code 1."""
with patch("ipsdk.logging.log"):
with pytest.raises(SystemExit) as exc_info:
ipsdk_logging.fatal("fatal error")
assert exc_info.value.code == 1

def test_fatal_logs_before_raising(self):
"""Test that fatal logs at FATAL level before raising."""
with patch("ipsdk.logging.log") as mock_log:
with pytest.raises(SystemExit):
ipsdk_logging.fatal("fatal error")
mock_log.assert_called_once_with(ipsdk_logging.FATAL, "fatal error")
mock_print.assert_called_once_with("ERROR: fatal error", file=sys.stderr)
mock_exit.assert_called_once_with(1)

def test_fatal_function_different_messages(self):
"""Test fatal function with different messages."""
def test_fatal_different_messages(self):
"""Test fatal logs correct message before raising."""
messages = ["critical failure", "system error", "cannot continue"]

for message in messages:
with (
patch("ipsdk.logging.log") as mock_log,
patch("builtins.print") as mock_print,
patch("sys.exit") as mock_exit,
):
ipsdk_logging.fatal(message)

with patch("ipsdk.logging.log") as mock_log:
with pytest.raises(SystemExit):
ipsdk_logging.fatal(message)
mock_log.assert_called_once_with(ipsdk_logging.FATAL, message)
expected_msg = f"ERROR: {message}"
mock_print.assert_called_once_with(expected_msg, file=sys.stderr)
mock_exit.assert_called_once_with(1)


class TestGetLogger:
Expand Down