# Logging and Error Handling Strategies in the MiroFish Backend: A Layered Observability Approach

> Discover MiroFish backend logging and error handling strategies using a four-layer observability approach. Learn about unified logging, JSON-L logs, retries, and granular error handling.

- Repository: [BaiFu/mirofish](https://github.com/666ghj/mirofish)
- Tags: best-practices
- Published: 2026-02-23

---

**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`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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:

```python
from backend.app.utils.logger import get_logger

log = get_logger(__name__)
log.info("Backend service started")

```

*Source:* [[`backend/app/utils/logger.py`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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`:

```python
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`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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

```python
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`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/zep_tools.py) and [`run_parallel_simulation.py`](https://github.com/666ghj/mirofish/blob/main/run_parallel_simulation.py) follows: **catch → log → re-raise**. This approach maintains system continuity while preserving error context for debugging:

```python

# 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`](https://github.com/666ghj/mirofish/blob/main/backend/app/utils/logger.py)
- **Structured JSON-L platform logging** capturing every agent action through [`backend/scripts/action_logger.py`](https://github.com/666ghj/mirofish/blob/main/backend/scripts/action_logger.py)
- **Exponential backoff retry logic** protecting external API calls in [`backend/app/utils/retry.py`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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`](https://github.com/666ghj/mirofish/blob/main/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.