Logging and Error Handling Strategies in the MiroFish Backend: A Layered Observability Approach
The MiroFish backend implements a four-layer observability stack featuring unified UTF-8-safe logging, structured JSON-L platform action logs, exponential backoff retry decorators, and granular try/except blocks that isolate failures while maintaining simulation continuity.
The MiroFish repository employs sophisticated logging and error handling strategies to ensure resilient multi-agent simulations across platforms like Twitter and Reddit. This article examines the concrete implementation patterns found in the backend services, from centralized logger configuration in backend/app/utils/logger.py to defensive programming techniques that prevent individual agent failures from crashing entire simulation runs.
Unified Logger Configuration with Dual Output Streams
Centralized Setup in logger.py
The foundation of MiroFish's observability starts in backend/app/utils/logger.py, where the setup_logger() function creates a centralized logging.Logger instance. This configuration implements a dual-handler approach: a detailed file handler capturing DEBUG-level messages to rotating daily files, and a concise console handler restricted to INFO-level output. Propagation is explicitly disabled to prevent duplicate log entries across the hierarchy.
Global Shortcut Functions
To streamline logging across modules, the file defines global shortcut functions at lines 11-27. These helpers—debug(), info(), warning(), error(), and critical()—allow any module to emit logs without repeatedly importing logger instances:
from backend.app.utils.logger import get_logger
log = get_logger(__name__)
log.info("Backend service started")
Source: [backend/app/utils/logger.py – helper functions](https://github.com/666ghj/mirofish/blob/main/backend/app/utils/logger.py#L11-L27)
Structured Platform-Specific Logging
JSON-L Action Logging
For multi-platform simulations, MiroFish implements structured logging through backend/scripts/action_logger.py. The PlatformActionLogger class writes newline-delimited JSON (JSON-L) entries that capture every simulation step, round transitions, and individual agent actions. This structured approach enables downstream log analysis and replay capabilities.
Simulation Log Manager
The SimulationLogManager class aggregates platform-specific loggers while maintaining a high-level human-readable log. Its _setup_main_logger() method configures file and console handlers specifically for the simulation context, with helper methods like info(), warning(), and error() forwarding to the underlying logging.Logger:
log_mgr = SimulationLogManager(simulation_dir)
log_mgr.info("Simulation completed successfully")
log_mgr.error("Unexpected exception in simulation loop")
Source: [backend/scripts/action_logger.py – SimulationLogManager methods](https://github.com/666ghj/mirofish/blob/main/backend/scripts/action_logger.py#L40-L67)
Resilient Error Handling with Exponential Backoff
Retry Decorators for External APIs
External API calls—particularly to LLM services—are protected by decorators defined in backend/app/utils/retry.py. The retry_with_backoff and retry_with_backoff_async functions implement exponential backoff with optional jitter, logging each retry attempt via logger.warning and emitting a final logger.error before propagating the exception after exhausting all attempts.
Implementation Example
from backend.app.utils.retry import retry_with_backoff
import requests
@retry_with_backoff(max_retries=5, initial_delay=2)
def call_llm_api(payload):
resp = requests.post("https://api.example.com/llm", json=payload, timeout=10)
resp.raise_for_status()
return resp.json()
Source: [backend/app/utils/retry.py – decorator definition](https://github.com/666ghj/mirofish/blob/main/backend/app/utils/retry.py#L15-L73)
Defensive Programming and Failure Isolation
Granular Try/Except Blocks
Service modules throughout backend/app/services/ employ granular exception handling to isolate failures. In simulation_runner.py (lines 248-260), critical simulation steps are wrapped in try/except blocks that log errors through the simulation manager before re-raising, ensuring that individual agent failures don't crash the entire simulation run.
Pattern Implementation
The consistent pattern across services like zep_tools.py and run_parallel_simulation.py follows: catch → log → re-raise. This approach maintains system continuity while preserving error context for debugging:
# From backend/app/services/simulation_runner.py
try:
await env.step(actions)
except Exception as e:
self.log_manager.error(f"Simulation step failed: {e}")
raise
Summary
The MiroFish backend implements a comprehensive observability and resilience strategy through four interconnected layers:
- Unified UTF-8 logging with dual console and rotating file handlers via
backend/app/utils/logger.py - Structured JSON-L platform logging capturing every agent action through
backend/scripts/action_logger.py - Exponential backoff retry logic protecting external API calls in
backend/app/utils/retry.py - Defensive try/except patterns throughout service modules that isolate failures while maintaining simulation continuity
Frequently Asked Questions
How does MiroFish prevent duplicate log entries?
The setup_logger() function in backend/app/utils/logger.py explicitly disables propagation on the logger instance. This prevents log messages from bubbling up to parent loggers and being handled multiple times, ensuring each event appears exactly once in both the console and file outputs.
What format does the platform-specific logging use?
Platform-specific actions are logged in newline-delimited JSON (JSON-L) format through the PlatformActionLogger class in backend/scripts/action_logger.py. Each line represents a discrete event—such as an agent action or round transition—enabling efficient parsing and analysis by external log processing tools.
How are external API failures handled in MiroFish?
External API calls are protected by the retry_with_backoff and retry_with_backoff_async decorators defined in backend/app/utils/retry.py. These implement exponential backoff with optional jitter, logging each retry attempt and ultimately raising the exception after exhausting the configured maximum retries, preventing indefinite hangs on flaky services.
What happens when a simulation step encounters an error?
When a simulation step fails, the error is caught in a try/except block within the service layer—such as in backend/app/services/simulation_runner.py—logged via the SimulationLogManager, and then re-raised. This pattern ensures the error is recorded for debugging while preventing individual agent failures from crashing the entire multi-agent simulation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →