# How to Set Up Logging with LogRecorder and WandbRecorder in EvoRL

> Learn to set up logging in EvoRL with LogRecorder and WandbRecorder. Effortlessly track local metrics and visualize progress on Weights & Biases dashboards. Get started now.

- Repository: [EMI-Group/evorl](https://github.com/emi-group/evorl)
- Tags: how-to-guide
- Published: 2026-03-01

---

**To set up logging in EvoRL, instantiate `LogRecorder` for local file output and `WandbRecorder` for Weights & Biases dashboards, then wrap them in a `ChainRecorder` to broadcast metrics to both backends simultaneously.**

EvoRL implements a lightweight, extensible **Recorder** framework that standardizes how training metrics are captured and stored. The `emi-group/evorl` repository provides two production-ready implementations—`LogRecorder` for human-readable files and `WandbRecorder` for cloud-based experiment tracking—both designed to work interchangeably through a unified interface defined in [`evorl/recorders/recorder.py`](https://github.com/emi-group/evorl/blob/main/evorl/recorders/recorder.py).

## Understanding the Recorder Architecture

The logging system centers on an abstract base class that enforces a consistent lifecycle across all recording backends.

In [`evorl/recorders/recorder.py`](https://github.com/emi-group/evorl/blob/main/evorl/recorders/recorder.py), the `Recorder` class defines three abstract methods:

- `init()` – Prepares the recording backend (opens files, initializes API connections).
- `write(data, step)` – Persists a dictionary of metrics at a specific training step.
- `close()` – Finalizes and flushes buffers, terminates connections.

The `ChainRecorder` class implements the same interface while acting as a container. It accepts a list of `Recorder` instances and forwards every lifecycle call to each contained recorder. This design allows a single `write()` call to propagate to multiple destinations—file logs and WandB dashboards—without modifying training loop code.

## Configuring LogRecorder for Local File Output

`LogRecorder` provides durable, human-readable logging to disk with optional console output.

Located in [`evorl/recorders/log_recorder.py`](https://github.com/emi-group/evorl/blob/main/evorl/recorders/log_recorder.py), this recorder:

- Creates a dedicated Python logger named `"LogRecorder"`.
- Attaches a `FileHandler` pointing to the user-specified `log_path`.
- Serializes data as **YAML** after converting JAX-native structures (`np.ndarray`, `np.generic`, `pd.Series/DataFrame`) to standard Python types.

This conversion ensures that JAX tensors and pandas objects—common in EvoRL workflows—serialize cleanly without raising type errors.

```python
from pathlib import Path
from evorl.recorders import LogRecorder

# Initialize file-based logging with console mirroring

log_recorder = LogRecorder(
    log_path=Path("./experiments/run_001.log"),
    console=True
)
log_recorder.init()

```

## Configuring WandbRecorder for Weights & Biases

`WandbRecorder` streams metrics to Weights & Biases for real-time visualization and experiment comparison.

Implemented in [`evorl/recorders/wandb_recorder.py`](https://github.com/emi-group/evorl/blob/main/evorl/recorders/wandb_recorder.py), this recorder:

- Invokes `wandb.init(**self.wandb_kwargs)` using the supplied project name, run name, configuration dictionary, tags, and output directory.
- Converts pandas objects into WandB-native visualizations (`Histogram`, `Table`).
- Calls `wandb.log(data, step=step)` to transmit scalar metrics and media.

```python
from pathlib import Path
from evorl.recorders import WandbRecorder

wandb_recorder = WandbRecorder(
    project="evorl-experiments",
    name="ppo-cartpole-v1",
    config={"lr": 3e-4, "gamma": 0.99},
    tags=["ppo", "cartpole"],
    path=Path("./wandb_outputs")
)
wandb_recorder.init()

```

## Integrating Recorders via Hydra Configuration

The standard EvoRL training pipeline automates recorder setup through Hydra configuration. The `setup_recorders` function in [`scripts/train.py`](https://github.com/emi-group/evorl/blob/main/scripts/train.py) reads `config.recorders`—a list of strings specifying which backends to activate—and constructs the appropriate instances.

```python

# scripts/train.py

def setup_recorders(config: DictConfig, workflow_name: str):
    output_dir = Path(config.output_dir)
    recorders = []
    
    exp_name = "_".join([
        workflow_name,
        config.env.env_name,
        config.env.env_type
    ])
    
    for rec in config.recorders:
        match rec:
            case "wandb":
                wandb_recorder = WandbRecorder(
                    project=config.project,
                    name=exp_name,
                    group="dev",
                    config=OmegaConf.to_container(config, resolve=True),
                    tags=[workflow_name, config.env.env_name],
                    path=output_dir,
                )
                recorders.append(wandb_recorder)
            
            case "log":
                log_recorder = LogRecorder(
                    log_path=output_dir / f"{exp_name}.log",
                    console=True,
                )
                recorders.append(log_recorder)
    
    return recorders

```

The workflow wraps the returned list in a `ChainRecorder` via `workflow.add_recorders(recorders)`, enabling unified logging across all configured backends.

To activate both recorders, modify your Hydra configuration:

```yaml

# configs/config.yaml

recorders: ["log", "wandb"]
project: evorl-demo
tags: ["benchmark", "v1"]

```

Then launch training:

```bash
python -m evorl.scripts.train \
    env.env_name=CartPole \
    env.env_type=classic \
    workflow_cls=evorl.workflows.RLWorkflow

```

## Manual Setup Without Hydra

For custom training scripts outside the Hydra ecosystem, instantiate and link recorders manually:

```python
from pathlib import Path
from evorl.recorders import LogRecorder, WandbRecorder, ChainRecorder

# 1. Create individual recorders

log = LogRecorder(log_path=Path("./local.log"), console=True)
wandb = WandbRecorder(
    project="custom-project",
    name="manual-run",
    config={"batch_size": 256},
    path=Path("./outputs")
)

# 2. Bundle into ChainRecorder

recorders = ChainRecorder([log, wandb])
recorders.init()

# 3. Log training metrics

for step in range(100):
    metrics = {"reward": step * 0.1, "loss": 1.0 / (step + 1)}
    recorders.write(metrics, step=step)

# 4. Cleanup

recorders.close()

```

This pattern gives you full control over initialization timing and resource management while maintaining compatibility with EvoRL's standardized logging interface.

## Summary

- **Recorder** is the abstract base class in [`evorl/recorders/recorder.py`](https://github.com/emi-group/evorl/blob/main/evorl/recorders/recorder.py) that defines `init()`, `write()`, and `close()` for all logging backends.
- **LogRecorder** writes YAML-formatted logs to disk, automatically converting JAX and pandas structures to Python-native types.
- **WandbRecorder** initializes Weights & Biases runs and streams metrics via `wandb.log()`, supporting rich visualizations for pandas data.
- **ChainRecorder** aggregates multiple recorders, allowing simultaneous file and cloud logging through a single interface.
- **Hydra integration** via [`scripts/train.py`](https://github.com/emi-group/evorl/blob/main/scripts/train.py) automatically constructs recorders from the `config.recorders` list, handling experiment naming and path resolution.

## Frequently Asked Questions

### What file format does LogRecorder use to store metrics?

LogRecorder serializes metrics as **YAML** in the specified log file. Before writing, it converts JAX arrays (`np.ndarray`, `np.generic`) and pandas objects (`Series`, `DataFrame`) to standard Python lists and dictionaries to ensure compatibility with the YAML serializer.

### Can I use both LogRecorder and WandbRecorder simultaneously?

Yes. Pass both recorders to a `ChainRecorder` instance, or list both `"log"` and `"wandb"` in your Hydra `config.recorders` list. The `ChainRecorder` forwards `write()` calls to both backends, ensuring local file persistence and cloud dashboard updates occur at every logging step.

### How does WandbRecorder handle experiment configuration?

During initialization, `WandbRecorder` passes the `config` dictionary directly to `wandb.init()` via the `wandb_kwargs` parameter. According to the source code in [`evorl/recorders/wandb_recorder.py`](https://github.com/emi-group/evorl/blob/main/evorl/recorders/wandb_recorder.py), this configuration populates the run's hyperparameter panel in the Weights & Biases dashboard, enabling filtering and grouping across experiments.

### Where is the log file saved when using the Hydra training script?

The `setup_recorders` function in [`scripts/train.py`](https://github.com/emi-group/evorl/blob/main/scripts/train.py) constructs the log path by combining `config.output_dir` with an experiment name derived from the workflow type, environment name, and tags. The file follows the pattern `{output_dir}/{workflow_name}_{env_name}_{env_type}.log`.