Skip to content
Closed
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
4 changes: 4 additions & 0 deletions src/lb_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@

if is_mothership:
# Mothership mode: Import user's FastAPI application
logger.info("=" * 60)
logger.info("IMPORTING APPLICATION")
logger.info("=" * 60)
try:
main_file = os.getenv("FLASH_MAIN_FILE", "main.py")
app_variable = os.getenv("FLASH_APP_VARIABLE", "app")
Expand All @@ -73,6 +76,7 @@
)

logger.info(f"Successfully imported FastAPI app '{app_variable}' from {main_file}")
logger.info("=" * 60)

# Add /ping endpoint if not already present
# Check if /ping route already exists to avoid adding a duplicate health check endpoint
Expand Down
12 changes: 12 additions & 0 deletions src/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ def setup_logging(
handler.setFormatter(logging.Formatter(fmt))
root_logger.addHandler(handler)

# Configure uvicorn loggers to use same format
formatter = logging.Formatter(fmt)
for logger_name in ["uvicorn", "uvicorn.error", "uvicorn.access"]:
uvicorn_logger = logging.getLogger(logger_name)
uvicorn_logger.setLevel(level)
# Suppress access logs unless DEBUG
if logger_name == "uvicorn.access" and level != logging.DEBUG:
uvicorn_logger.setLevel(logging.WARNING)
# Ensure handlers use our formatter
Comment on lines +61 to +68
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uvicorn loggers likely have no handlers at the time setup_logging() is called during module import, so this loop won't apply the formatter to any handlers. Uvicorn creates its handlers later when the server starts. Consider either adding handlers if they don't exist, or configuring uvicorn's logging via uvicorn's configuration options when starting the server.

Suggested change
formatter = logging.Formatter(fmt)
for logger_name in ["uvicorn", "uvicorn.error", "uvicorn.access"]:
uvicorn_logger = logging.getLogger(logger_name)
uvicorn_logger.setLevel(level)
# Suppress access logs unless DEBUG
if logger_name == "uvicorn.access" and level != logging.DEBUG:
uvicorn_logger.setLevel(logging.WARNING)
# Ensure handlers use our formatter
formatter = logging.Formter(fmt)
for logger_name in ["uvicorn", "uvicorn.error", "uvicorn.access"]:
uvicorn_logger = logging.getLogger(logger_name)
uvicorn_logger.setLevel(level)
# Suppress access logs unless DEBUG
if logger_name == "uvicorn.access" and level != logging.DEBUG:
uvicorn_logger.setLevel(logging.WARNING)
# Ensure each uvicorn logger has at least one handler so the formatter is applied
if not uvicorn_logger.handlers:
uvicorn_handler = logging.StreamHandler(stream)
uvicorn_handler.setFormatter(formatter)
uvicorn_logger.addHandler(uvicorn_handler)
# Ensure all handlers use our formatter

Copilot uses AI. Check for mistakes.
for uvicorn_handler in uvicorn_logger.handlers:
uvicorn_handler.setFormatter(formatter)

# When DEBUG is requested, silence the noisy module
if level == logging.DEBUG:
logging.getLogger("filelock").setLevel(logging.INFO)
42 changes: 33 additions & 9 deletions src/unpack_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,24 +128,48 @@ def maybe_unpack():

_UNPACKED = True
logger.info("unpacking app from volume")
logger.info("=" * 60)

last_error: Exception | None = None
for attempt in range(DEFAULT_TARBALL_UNPACK_ATTEMPTS):
attempt_num = attempt + 1 # 1-indexed for display
try:
logger.info(
"attempting to extract tarball (attempt %s of %s)...",
attempt_num,
DEFAULT_TARBALL_UNPACK_ATTEMPTS,
)
unpack_app_from_volume()
logger.info("=" * 60)
_UNPACKED = True
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _UNPACKED flag is set to True here, but it was already set to True on line 129 at the start of the critical section. This assignment is redundant and can be removed.

Suggested change
_UNPACKED = True

Copilot uses AI. Check for mistakes.
return
except (FileNotFoundError, RuntimeError) as e:
last_error = e
logger.error(
"failed to unpack app from volume (attempt %s/%s): %s",
attempt,
DEFAULT_TARBALL_UNPACK_ATTEMPTS,
e,
exc_info=True,
)
if attempt < DEFAULT_TARBALL_UNPACK_ATTEMPTS:
is_final_attempt = attempt == DEFAULT_TARBALL_UNPACK_ATTEMPTS - 1

if is_final_attempt:
# Final attempt failed - log as ERROR with traceback
logger.error(
"failed to unpack app from volume (final attempt %s of %s): %s",
attempt_num,
DEFAULT_TARBALL_UNPACK_ATTEMPTS,
e,
exc_info=True,
)
else:
# Expected retry - log as WARNING without traceback
logger.warning(
"unpack failed (attempt %s of %s): %s",
attempt_num,
DEFAULT_TARBALL_UNPACK_ATTEMPTS,
e,
)
logger.info(
"waiting %s seconds before retry...", DEFAULT_TARBALL_UNPACK_INTERVAL
)
sleep(DEFAULT_TARBALL_UNPACK_INTERVAL)

logger.info("=" * 60)
raise RuntimeError(
f"failed to unpack app from volume after retries: {last_error}"
f"failed to unpack app from volume after {DEFAULT_TARBALL_UNPACK_ATTEMPTS} attempts: {last_error}"
) from last_error
21 changes: 15 additions & 6 deletions tests/unit/test_unpack_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,14 @@ def test_maybe_unpack_logs_info_on_start(self, mock_logger, mock_unpack, mock_sh

maybe_unpack()

mock_logger.info.assert_called_once_with("unpacking app from volume")
# Verify the unpacking start message is logged
info_calls = [call[0][0] for call in mock_logger.info.call_args_list]
assert "unpacking app from volume" in info_calls
# Verify section separators and attempt message are present
assert "=" * 60 in info_calls
assert any(
"attempting to extract tarball" in str(call) for call in mock_logger.info.call_args_list
)

@patch("unpack_volume.sleep")
@patch("unpack_volume._should_unpack_from_volume")
Expand All @@ -434,8 +441,10 @@ def test_maybe_unpack_logs_error_on_failure(
with pytest.raises(RuntimeError, match="failed to unpack app from volume"):
maybe_unpack()

# Error should be logged once per retry attempt (3 total)
assert mock_logger.error.call_count == 3
# Verify all error calls include the expected message
for call in mock_logger.error.call_args_list:
assert "failed to unpack app from volume" in call[0][0]
# First 2 attempts should be logged as WARNING (not ERROR)
assert mock_logger.warning.call_count == 2
# Final attempt should be logged as ERROR with traceback
assert mock_logger.error.call_count == 1
# Verify error call includes expected message
assert "failed to unpack app from volume" in mock_logger.error.call_args[0][0]
assert "final attempt" in mock_logger.error.call_args[0][0]
Loading