Skip to content

Logging

masa.common.metrics.BaseLogger

BaseLogger(stdout: bool = True, tqdm: bool = True, tensorboard: bool = False, summary_writer: Optional[SummaryWriter] = None, wandb: bool = False, stats_window_size: int = 100, stats_window_overrides: Dict[str, int] = {}, prefix: str = '')

Base class for logging scalar statistics and distributions.

A logger ingests objects via add and produces aggregated outputs via log. Concrete subclasses define how they ingest and summarise data.

TensorBoard logging uses a tf.summary.SummaryWriter. If tensorboard is True then a writer must be provided.

Parameters:

Name Type Description Default
stdout bool

If True, print logs to stdout via print or tqdm.write.

True
tqdm bool

If True, use tqdm.write for stdout printing.

True
tensorboard bool

If True, emit TensorBoard summaries.

False
summary_writer Optional[SummaryWriter]

TensorBoard writer. Required when tensorboard=True.

None
stats_window_size int

Maximum number of recent scalar values retained per metric.

100
stats_window_overrides Dict[str, int]

A dictionary for overriding logged values with a different stats_window_size.

{}
prefix str

Optional string prefix for TensorBoard tag names and stdout display. If non-empty, a trailing "/" is ensured.

''

Attributes:

Name Type Description
stdout bool

Whether stdout logging is enabled.

tqdm bool

Whether tqdm-compatible printing is enabled.

tensorboard bool

Whether TensorBoard logging is enabled.

summary_writer Optional[SummaryWriter]

TensorBoard summary writer (may be None).

stats_window_size int

Window size for scalar smoothing.

prefix str

Namespace prefix ending with "/" (or empty).

stats Dict[str, Deque[float]]

Mapping from metric key to a deque of recent values.

Source code in masa/common/metrics.py
def __init__(
    self,
    stdout: bool = True,
    tqdm: bool = True,
    tensorboard: bool = False,
    summary_writer: Optional[tf.summary.SummaryWriter] = None,
    wandb: bool = False,
    stats_window_size: int = 100,
    stats_window_overrides: Dict[str, int] = {},
    prefix: str = "",
):
    self.stdout: bool = stdout
    self.tqdm: bool = tqdm
    self.tensorboard: bool = tensorboard
    self.summary_writer: Optional[tf.summary.SummaryWriter] = summary_writer
    self.wandb: bool = wandb

    if self.tensorboard:
        assert self.summary_writer is not None, "tensorboard=True requires a summary_writer"
    if (self.summary_writer is not None) and (not self.tensorboard):
        warnings.warn(
            "tensorboard is set to False but summary writer is provided; this may produce unexpected behaviour",
            stacklevel=2,
        )

    self.stats_window_size: int = int(stats_window_size)
    self.stats_window_overrides: Dict[str, int] = {str(k): int(v) for k, v in stats_window_overrides.items()}
    self.prefix: str = (prefix if not prefix else (prefix if prefix.endswith("/") else prefix + "/"))
    self.stats: Dict[str, Deque[float]] = {}

stdout instance-attribute

stdout: bool = stdout

tqdm instance-attribute

tqdm: bool = tqdm

tensorboard instance-attribute

tensorboard: bool = tensorboard

summary_writer instance-attribute

summary_writer: Optional[SummaryWriter] = summary_writer

wandb instance-attribute

wandb: bool = wandb

stats_window_size instance-attribute

stats_window_size: int = int(stats_window_size)

stats_window_overrides instance-attribute

stats_window_overrides: Dict[str, int] = {str(k): int(v) for k, v in stats_window_overrides.items()}

prefix instance-attribute

prefix: str = prefix if not prefix else prefix if prefix.endswith('/') else prefix + '/'

stats instance-attribute

stats: Dict[str, Deque[float]] = {}

reset

reset()

Clear all buffered statistics.

Source code in masa/common/metrics.py
def reset(self):
    """Clear all buffered statistics."""
    self.stats = {}

add

add(new: Any)

Ingest a new object into the logger.

Concrete subclasses define the supported input types.

Parameters:

Name Type Description Default
new Any

Object to ingest.

required

Raises:

Type Description
NotImplementedError

Always, in the base class.

Source code in masa/common/metrics.py
def add(self, new: Any):
    """Ingest a new object into the logger.

    Concrete subclasses define the supported input types.

    Args:
        new: Object to ingest.

    Raises:
        NotImplementedError: Always, in the base class.
    """
    raise NotImplementedError

log

log(step: int)

Emit logs for a given global step.

Parameters:

Name Type Description Default
step int

Global step index used for TensorBoard summary steps.

required

Raises:

Type Description
NotImplementedError

Always, in the base class.

Source code in masa/common/metrics.py
def log(self, step: int):
    """Emit logs for a given global step.

    Args:
        step: Global step index used for TensorBoard summary steps.

    Raises:
        NotImplementedError: Always, in the base class.
    """
    raise NotImplementedError

masa.common.metrics.StatsLogger

StatsLogger(stdout: bool = True, tqdm: bool = True, tensorboard: bool = False, summary_writer: Optional[SummaryWriter] = None, wandb: bool = False, stats_window_size: int = 100, stats_window_overrides: Dict[str, int] = {}, prefix: str = '')

Bases: BaseLogger

Logger for streaming scalar stats (Stats) and distributions (Dist).

The add method accepts a mapping whose values are one of:

  • Stats: expanded into multiple scalar keys (mean/std/min/max/mag).
  • Dist: captured for histogram logging.
  • float / int / numpy scalar: treated as a scalar time series.
Aggregation
  • Scalars are smoothed by taking the mean of the most recent stats_window_size values.
  • Distributions are logged as histograms using the stored reservoir.
Notes

This class creates internal dictionaries stats_to_log and dists_to_log during log.

Source code in masa/common/metrics.py
def __init__(
    self,
    stdout: bool = True,
    tqdm: bool = True,
    tensorboard: bool = False,
    summary_writer: Optional[tf.summary.SummaryWriter] = None,
    wandb: bool = False,
    stats_window_size: int = 100,
    stats_window_overrides: Dict[str, int] = {},
    prefix: str = "",
):
    super().__init__(
        stdout=stdout,
        tqdm=tqdm,
        tensorboard=tensorboard,
        summary_writer=summary_writer,
        wandb=wandb,
        stats_window_size=stats_window_size,
        stats_window_overrides=stats_window_overrides,
        prefix=prefix,
    )
    self.dists: Dict[str, np.ndarray] = {}
    self.stats_to_log: Dict[str, float] = {}
    self.dists_to_log: Dict[str, np.ndarray] = {}

dists instance-attribute

dists: Dict[str, ndarray] = {}

stats_to_log instance-attribute

stats_to_log: Dict[str, float] = {}

dists_to_log instance-attribute

dists_to_log: Dict[str, ndarray] = {}

reset

reset()

Clear all buffered scalar and distribution values.

Source code in masa/common/metrics.py
def reset(self):
    """Clear all buffered scalar and distribution values."""
    super().reset()
    self.dists = {}
    self.stats_to_log = {}
    self.dists_to_log = {}

add

add(new: Mapping[str, Union['Stats', 'Dist', float, int, floating]])

Add a batch of metrics to the logger.

Parameters:

Name Type Description Default
new Mapping[str, Union['Stats', 'Dist', float, int, floating]]

Mapping from metric name to a supported metric object.

required

Raises:

Type Description
NotImplementedError

If a value type is unsupported.

Source code in masa/common/metrics.py
def add(self, new: Mapping[str, Union["Stats", "Dist", float, int, np.floating]]):
    """Add a batch of metrics to the logger.

    Args:
        new: Mapping from metric name to a supported metric object.

    Raises:
        NotImplementedError: If a value type is unsupported.
    """

    for key, val in new.items():
        if isinstance(val, Stats):
            met = val.get()
            for k, v in met.items():
                if k in self.stats:
                    self.stats[k].append(float(v))
                else:
                    maxlen = self.stats_window_size if k not in self.stats_window_overrides else self.stats_window_overrides[k]
                    self.stats[k] = deque([float(v)], maxlen=maxlen)
        elif isinstance(val, Dist):
            # Store a snapshot of the reservoir for later histogram logging.
            self.dists[key] = val.get()
        elif isinstance(val, (float, int, np.floating)):
            if key in self.stats:
                self.stats[key].append(float(val))
            else:
                maxlen = self.stats_window_size if key not in self.stats_window_overrides else self.stats_window_overrides[key]
                self.stats[key] = deque([float(val)], maxlen=maxlen)
        else:
            raise NotImplementedError(
                "StatsLogger.add() only supports types: Stats, Dist, and numeric scalars"
            )

log

log(step: int)

Aggregate buffered values and emit logs.

Parameters:

Name Type Description Default
step int

Global step index used for TensorBoard summary steps.

required
Source code in masa/common/metrics.py
def log(self, step: int):
    """Aggregate buffered values and emit logs.

    Args:
        step: Global step index used for TensorBoard summary steps.
    """
    self._create_logs()
    if self.tensorboard:
        self._log_to_tensorboard(step)
    if self.stdout:
        self._log_to_stdout(step)

_create_logs

_create_logs()

Create stats_to_log and dists_to_log from buffers.

Source code in masa/common/metrics.py
def _create_logs(self):
    """Create :attr:`stats_to_log` and :attr:`dists_to_log` from buffers."""
    self._create_stats_to_log()
    self._create_dists_to_log()

_create_stats_to_log

_create_stats_to_log()

Compute smoothed scalar values to emit.

Source code in masa/common/metrics.py
def _create_stats_to_log(self):
    """Compute smoothed scalar values to emit."""
    self.stats_to_log = {}
    for key, val in self.stats.items():
        if len(val) > 0:
            if key.endswith("max") or key.endswith("mag"):
                agg = np.max(val)
            elif key.endswith("min"):
                agg = np.min(val)
            else:
                agg = np.mean(val)
        else:
            agg = last
        self.stats_to_log[key] = float(agg)

_create_dists_to_log

_create_dists_to_log()

Collect distributions to emit as histograms.

Source code in masa/common/metrics.py
def _create_dists_to_log(self):
    """Collect distributions to emit as histograms."""
    self.dists_to_log = {}
    for key, val in self.dists.items():
        if len(val) > 0:
            self.dists_to_log[key] = val

_log_to_tensorboard

_log_to_tensorboard(step: int)

Write scalars and histograms to TensorBoard.

Parameters:

Name Type Description Default
step int

Global step index used for TensorBoard summary steps.

required
Source code in masa/common/metrics.py
def _log_to_tensorboard(self, step: int):
    """Write scalars and histograms to TensorBoard.

    Args:
        step: Global step index used for TensorBoard summary steps.
    """
    assert self.summary_writer is not None, (
        "You're trying to log to tensorboard without a summary writer setup!"
    )
    with self.summary_writer.as_default():
        for key, value in self.stats_to_log.items():
            tf.summary.scalar(self.prefix + key, value, step=step)
        for key, values in self.dists_to_log.items():
            tf.summary.histogram(self.prefix + key, data=values, step=step)

_get_wandb_payload

_get_wandb_payload() -> Dict[str, Any]

Return the current W&B payload dict without logging it.

Used by TrainLogger to batch all sub-logger payloads into a single wandb.log call.

Returns:

Type Description
Dict[str, Any]

Mapping from metric name to scalar or wandb.Histogram.

Source code in masa/common/metrics.py
def _get_wandb_payload(self) -> Dict[str, Any]:
    """Return the current W&B payload dict without logging it.

    Used by :class:`TrainLogger` to batch all sub-logger payloads into a
    single :func:`wandb.log` call.

    Returns:
        Mapping from metric name to scalar or :class:`wandb.Histogram`.
    """
    import wandb as _wandb
    payload: Dict[str, Any] = {self.prefix + k: v for k, v in self.stats_to_log.items()}
    for k, v in self.dists_to_log.items():
        payload[self.prefix + k] = _wandb.Histogram(v)
    return payload

_log_to_stdout

_log_to_stdout(step: int)

Print the current scalar log table to stdout.

Parameters:

Name Type Description Default
step int

Global step index (unused; included for API symmetry).

required
Source code in masa/common/metrics.py
def _log_to_stdout(self, step: int):
    """Print the current scalar log table to stdout.

    Args:
        step: Global step index (unused; included for API symmetry).
    """
    stats_to_print = {key: "{0:.4g}".format(val) for key, val in self.stats_to_log.items()}
    if not stats_to_print:
        return

    max_key_len = max([len(key) for key in stats_to_print] + [max(0, len(self.prefix) - 2)])
    max_val_len = max([len(val) for val in stats_to_print.values()])

    stdout = ""
    max_len = 1 + 4 + max_key_len + 2 + 1 + 2 + max_val_len + 2 + 1
    stdout += ("-" * max_len + "\n")
    stdout += (
        "|  "
        + self.prefix
        + " " * (2 + max_key_len - len(self.prefix) + 2)
        + "|"
        + " " * (2 + max_val_len + 2)
        + "|\n"
    )
    for key, val in stats_to_print.items():
        stdout += (
            "|    "
            + key
            + " " * (max_key_len - len(key) + 2)
            + "|  "
            + val
            + " " * (max_val_len - len(val) + 2)
            + "|\n"
        )
    stdout += ("-" * max_len + "\n")

    if self.tqdm:
        tqdm.write(stdout)
    else:
        print(stdout)

masa.common.metrics.RolloutLogger

RolloutLogger(stdout: bool = True, tqdm: bool = True, tensorboard: bool = False, summary_writer: Optional[SummaryWriter] = None, wandb: bool = False, stats_window_size: int = 100, stats_window_overrides: Dict[str, int] = {}, prefix: str = '')

Bases: BaseLogger

Logger for episodic metrics produced during environment rollouts.

This logger is designed for per-episode summaries that arrive via an info dict (e.g., from Gymnasium environments). It looks for:

  • info["constraint"]["episode"]: constraint-related episode metrics
  • info["metrics"]["episode"]: generic episode metrics

and treats the values as scalars.

It also reports simple runtime diagnostics to stdout:

  • fps: \(\frac{\text{timesteps}}{\text{wall-clock seconds}}\)
  • time_elapsed: wall-clock seconds since the first add
  • total_timesteps: the provided global step
Notes

The most recent value in each deque is treated as the "current episode" and excluded from the mean shown in stdout/TensorBoard (so the displayed mean reflects completed episodes only).

Source code in masa/common/metrics.py
def __init__(
    self,
    stdout: bool = True,
    tqdm: bool = True,
    tensorboard: bool = False,
    summary_writer: Optional[tf.summary.SummaryWriter] = None,
    wandb: bool = False,
    stats_window_size: int = 100,
    stats_window_overrides: Dict[str, int] = {},
    prefix: str = "",
):
    super().__init__(
        stdout=stdout,
        tqdm=tqdm,
        tensorboard=tensorboard,
        summary_writer=summary_writer,
        wandb=wandb,
        stats_window_size=stats_window_size,
        stats_window_overrides=stats_window_overrides,
        prefix=prefix,
    )
    self.start_time: Optional[float] = None
    self.stats_to_log: Dict[str, float] = {}

start_time instance-attribute

start_time: Optional[float] = None

stats_to_log instance-attribute

stats_to_log: Dict[str, float] = {}

add

add(info: Mapping[str, Any], verbose: int = 0)

Ingest an info dict and extract episodic scalars.

Parameters:

Name Type Description Default
info Mapping[str, Any]

Rollout info mapping (typically from environment step). If present, the logger reads: info["constraint"]["episode"] and/or info["metrics"]["episode"].

required
verbose int

Reserved for compatibility; currently unused.

0
Source code in masa/common/metrics.py
def add(self, info: Mapping[str, Any], verbose: int = 0):
    """Ingest an ``info`` dict and extract episodic scalars.

    Args:
        info: Rollout ``info`` mapping (typically from environment step).
            If present, the logger reads:
            ``info["constraint"]["episode"]`` and/or
            ``info["metrics"]["episode"]``.
        verbose: Reserved for compatibility; currently unused.
    """
    if self.start_time is None:
        self.start_time = time.time()

    constraint = info.get("constraint", {})
    if isinstance(constraint, Mapping) and "episode" in constraint:
        ep_metrics = constraint["episode"]
        if isinstance(ep_metrics, Mapping):
            self._add_scalars(ep_metrics)

    metrics = info.get("metrics", {})
    if isinstance(metrics, Mapping) and "episode" in metrics:
        ep_metrics = metrics["episode"]
        if isinstance(ep_metrics, Mapping):
            self._add_scalars(ep_metrics)

log

log(step: int)

Aggregate buffered episode metrics and emit logs.

Parameters:

Name Type Description Default
step int

Global step index used for TensorBoard summary steps.

required
Source code in masa/common/metrics.py
def log(self, step: int):
    """Aggregate buffered episode metrics and emit logs.

    Args:
        step: Global step index used for TensorBoard summary steps.
    """
    self._create_logs()
    if self.tensorboard:
        self._log_to_tensorboard(step)
    if self.stdout:
        self._log_to_stdout(step)

_create_logs

_create_logs()

Create stats_to_log from episode buffers.

Source code in masa/common/metrics.py
def _create_logs(self):
    """Create :attr:`stats_to_log` from episode buffers."""
    self._create_stats_to_log()

_add_scalars

_add_scalars(scalars: Mapping[str, Union[float, int, floating]])

Append scalar episode metrics into rolling windows.

Parameters:

Name Type Description Default
scalars Mapping[str, Union[float, int, floating]]

Mapping from metric names to numeric values.

required
Source code in masa/common/metrics.py
def _add_scalars(self, scalars: Mapping[str, Union[float, int, np.floating]]):
    """Append scalar episode metrics into rolling windows.

    Args:
        scalars: Mapping from metric names to numeric values.
    """
    for k, v in scalars.items():
        if k in self.stats:
            self.stats[k].append(float(v))
        else:
            # +1 because we keep the most recent value as "current episode".
            maxlen = self.stats_window_size if k not in self.stats_window_overrides else self.stats_window_overrides[k]
            self.stats[k] = deque([float(v)], maxlen=maxlen + 1)

_create_stats_to_log

_create_stats_to_log()

Compute per-metric mean over completed episodes.

Source code in masa/common/metrics.py
def _create_stats_to_log(self):
    """Compute per-metric mean over *completed* episodes."""
    self.stats_to_log = {}
    for key, val in self.stats.items():
        if len(val) > 1:
            # Temporarily drop last (current) value from the mean.
            last = val.pop()
            self.stats_to_log[key] = float(np.mean(val)) if len(val) > 0 else float(last)
            val.append(last)

_log_to_tensorboard

_log_to_tensorboard(step: int)

Write episode scalar summaries to TensorBoard.

Parameters:

Name Type Description Default
step int

Global step index used for TensorBoard summary steps.

required
Source code in masa/common/metrics.py
def _log_to_tensorboard(self, step: int):
    """Write episode scalar summaries to TensorBoard.

    Args:
        step: Global step index used for TensorBoard summary steps.
    """
    assert self.summary_writer is not None, (
        "You're trying to log to tensorboard without a summary writer setup!"
    )
    with self.summary_writer.as_default():
        for key, value in self.stats_to_log.items():
            tf.summary.scalar(self.prefix + key, value, step=step)

_get_wandb_payload

_get_wandb_payload() -> Dict[str, Any]

Return the current W&B payload dict without logging it.

Used by TrainLogger to batch all sub-logger payloads into a single wandb.log call.

Returns:

Type Description
Dict[str, Any]

Mapping from metric name to scalar value.

Source code in masa/common/metrics.py
def _get_wandb_payload(self) -> Dict[str, Any]:
    """Return the current W&B payload dict without logging it.

    Used by :class:`TrainLogger` to batch all sub-logger payloads into a
    single :func:`wandb.log` call.

    Returns:
        Mapping from metric name to scalar value.
    """
    return {self.prefix + k: v for k, v in self.stats_to_log.items()}

_log_to_stdout

_log_to_stdout(step: int)

Print episode summaries and runtime diagnostics to stdout.

Parameters:

Name Type Description Default
step int

Global step index used for fps and total timestep display.

required
Source code in masa/common/metrics.py
def _log_to_stdout(self, step: int):
    """Print episode summaries and runtime diagnostics to stdout.

    Args:
        step: Global step index used for fps and total timestep display.
    """
    stats_to_print: Dict[str, str] = {
        key: "{0:.4g}".format(val) for key, val in self.stats_to_log.items()
    }
    if self.start_time is not None:
        current_time = time.time()
        elapsed = current_time - self.start_time
        if elapsed > 0:
            stats_to_print["fps"] = "{0:.4g}".format(step / elapsed)
        stats_to_print["time_elapsed"] = "{0:.4g}".format(elapsed)
    stats_to_print["total_timesteps"] = str(step)

    if not stats_to_print:
        return

    max_key_len = max([len(key) for key in stats_to_print] + [max(0, len(self.prefix) - 2)])
    max_val_len = max([len(val) for val in stats_to_print.values()])

    stdout = ""
    max_len = 1 + 4 + max_key_len + 2 + 1 + 2 + max_val_len + 2 + 1
    stdout += ("-" * max_len + "\n")
    stdout += (
        "|  "
        + self.prefix
        + " " * (2 + max_key_len - len(self.prefix) + 2)
        + "|"
        + " " * (2 + max_val_len + 2)
        + "|\n"
    )
    for key, val in stats_to_print.items():
        stdout += (
            "|    "
            + key
            + " " * (max_key_len - len(key) + 2)
            + "|  "
            + val
            + " " * (max_val_len - len(val) + 2)
            + "|\n"
        )
    stdout += ("-" * max_len + "\n")

    if self.tqdm:
        tqdm.write(stdout)
    else:
        print(stdout)

masa.common.metrics.TrainLogger

TrainLogger(loggers: List[Tuple[str, Any]], stdout: bool = True, tqdm: bool = True, tensorboard: bool = False, summary_writer: Optional[SummaryWriter] = None, wandb: bool = False, stats_window_size: Union[int, List[int]] = 100, stats_window_overrides: Dict[str, int] = {}, prefix: str = '')

Bases: BaseLogger

Orchestrate multiple loggers for a training run.

A TrainLogger is a thin wrapper around a set of sub-loggers (e.g. StatsLogger, RolloutLogger). It forwards add calls to the appropriate sub-logger and aggregates stdout/TensorBoard output.

Parameters:

Name Type Description Default
loggers List[Tuple[str, Any]]

A list of (name, ctor) pairs. ctor must be a BaseLogger subclass (or compatible callable) that can be constructed with the same keyword arguments as BaseLogger.

required
stdout bool

If True, print a combined stdout table for all sub-loggers.

True
tqdm bool

If True, use tqdm.write when printing.

True
tensorboard bool

If True, forward TensorBoard logging to sub-loggers.

False
summary_writer Optional[SummaryWriter]

TensorBoard writer passed to each sub-logger when tensorboard=True.

None
stats_window_size Union[int, List[int]]

Either a single window size used for all sub-loggers, or a list of per-logger window sizes aligned with loggers.

100
stats_window_overrides Dict[str, int]

A dictionary of string integer pairs for overriding specific logged metrics with a different stats_window_size.

{}
prefix str

Optional display prefix for stdout tables.

''

Attributes:

Name Type Description
loggers Dict[str, BaseLogger]

Mapping from logger key to instantiated BaseLogger.

start_time Optional[float]

Wall-clock time at which the first add occurred, used for runtime diagnostics.

Source code in masa/common/metrics.py
def __init__(
    self,
    loggers: List[Tuple[str, Any]],
    stdout: bool = True,
    tqdm: bool = True,
    tensorboard: bool = False,
    summary_writer: Optional[tf.summary.SummaryWriter] = None,
    wandb: bool = False,
    stats_window_size: Union[int, List[int]] = 100,
    stats_window_overrides: Dict[str, int] = {},
    prefix: str = "",
):
    # Note: TrainLogger is a coordinator and intentionally does not call
    # BaseLogger.__init__ (it does not maintain its own rolling buffers).
    self.loggers: Dict[str, BaseLogger] = {}
    self.stdout: bool = stdout
    self.tqdm: bool = tqdm
    self.tensorboard: bool = tensorboard
    self.summary_writer: Optional[tf.summary.SummaryWriter] = summary_writer
    self.wandb: bool = wandb
    self.prefix: str = prefix

    if isinstance(stats_window_size, int):
        window_sizes = [stats_window_size] * len(loggers)
    elif isinstance(stats_window_size, list):
        window_sizes = stats_window_size
    else:
        raise RuntimeError("Expected type int or List[int] for stats_window_size")

    if len(window_sizes) != len(loggers):
        raise ValueError("stats_window_size list must match number of loggers")

    for idx, (key, ctor) in enumerate(loggers):
        # ctor is expected to be a BaseLogger subclass or callable returning one.
        overrides = {k.removeprefix(key+"/"): v for k, v in stats_window_overrides.items() if k.startswith(key+"/")}
        self.loggers[key] = ctor(
            stdout=self.stdout,
            tqdm=self.tqdm,
            tensorboard=self.tensorboard,
            summary_writer=self.summary_writer,
            wandb=False,  # TrainLogger drives W&B centrally; sub-loggers do not call wandb.log() independently
            stats_window_size=window_sizes[idx],
            stats_window_overrides=overrides,
            prefix=key,
        )

    self.start_time: Optional[float] = None

loggers instance-attribute

loggers: Dict[str, BaseLogger] = {}

stdout instance-attribute

stdout: bool = stdout

tqdm instance-attribute

tqdm: bool = tqdm

tensorboard instance-attribute

tensorboard: bool = tensorboard

summary_writer instance-attribute

summary_writer: Optional[SummaryWriter] = summary_writer

wandb instance-attribute

wandb: bool = wandb

prefix instance-attribute

prefix: str = prefix

start_time instance-attribute

start_time: Optional[float] = None

add

add(key: str, obj: Any)

Add an object to a named sub-logger.

Parameters:

Name Type Description Default
key str

The sub-logger key as provided in loggers during construction.

required
obj Any

The object to forward to self.loggers[key].add(...).

required

Raises:

Type Description
KeyError

If key is not a configured sub-logger.

Source code in masa/common/metrics.py
def add(self, key: str, obj: Any):
    """Add an object to a named sub-logger.

    Args:
        key: The sub-logger key as provided in ``loggers`` during
            construction.
        obj: The object to forward to ``self.loggers[key].add(...)``.

    Raises:
        KeyError: If ``key`` is not a configured sub-logger.
    """
    if self.start_time is None:
        self.start_time = time.time()
    self.loggers[key].add(obj)

log

log(step: int)

Emit TensorBoard logs (per sub-logger) and a combined stdout table.

Parameters:

Name Type Description Default
step int

Global step index used for TensorBoard summary steps.

required
Source code in masa/common/metrics.py
def log(self, step: int):
    """Emit TensorBoard logs (per sub-logger) and a combined stdout table.

    Args:
        step: Global step index used for TensorBoard summary steps.
    """
    for key in self.loggers.keys():
        # Rely on sub-logger internal API (common to StatsLogger/RolloutLogger).
        self.loggers[key]._create_logs()  # type: ignore[attr-defined]
        if self.tensorboard:
            self.loggers[key]._log_to_tensorboard(step)  # type: ignore[attr-defined]

    if self.wandb:
        # Batch all sub-logger payloads into a single wandb.log() call to
        # keep all metrics on the same step without step-alignment warnings.
        import wandb as _wandb
        payload: Dict[str, Any] = {}
        for logger in self.loggers.values():
            payload.update(logger._get_wandb_payload())  # type: ignore[attr-defined]
        if payload:
            _wandb.log(payload, step=step)

    if self.stdout:
        self._log_to_stdout(step)

_log_to_stdout

_log_to_stdout(step: int) -> None

Print a combined stdout table for all sub-loggers.

Parameters:

Name Type Description Default
step int

Global step index used for runtime diagnostics.

required
Source code in masa/common/metrics.py
def _log_to_stdout(self, step: int) -> None:
    """Print a combined stdout table for all sub-loggers.

    Args:
        step: Global step index used for runtime diagnostics.
    """
    stats_to_print: Dict[str, Dict[str, str]] = {}
    stats_to_print["run"] = {}

    if self.start_time is not None:
        current_time = time.time()
        elapsed = current_time - self.start_time
        if elapsed > 0:
            stats_to_print["run"]["fps"] = "{0:.4g}".format(step / elapsed)
        stats_to_print["run"]["time_elapsed"] = "{0:.4g}".format(elapsed)
    stats_to_print["run"]["total_timesteps"] = str(step)

    for key, logger in self.loggers.items():
        # Sub-loggers populate stats_to_log in _create_logs().
        stats = getattr(logger, "stats_to_log", {})
        stats_to_print[key] = {k: "{0:.4g}".format(v) for k, v in stats.items()}

    max_key_len = 0
    max_val_len = 0
    for group in stats_to_print.values():
        if not group:
            continue
        max_key_len = max(max_key_len, max([len(k) for k in group.keys()] + [max(0, len(self.prefix) - 2)]))
        max_val_len = max(max_val_len, max([len(v) for v in group.values()]))

    stdout = ""
    max_len = 1 + 4 + max_key_len + 2 + 1 + 2 + max_val_len + 2 + 1
    stdout += ("-" * max_len + "\n")
    if self.prefix:
        stdout += (
            "|  "
            + self.prefix
            + " " * (2 + max_key_len - len(self.prefix) + 2)
            + "|"
            + " " * (2 + max_val_len + 2)
            + "|\n"
        )
        stdout += ("-" * max_len + "\n")

    for group_key, group in stats_to_print.items():
        if not group:
            continue
        group_prefix = group_key + "/"
        stdout += (
            "|  "
            + group_prefix
            + " " * (2 + max_key_len - len(group_prefix) + 2)
            + "|"
            + " " * (2 + max_val_len + 2)
            + "|\n"
        )
        for k, v in group.items():
            stdout += (
                "|    "
                + k
                + " " * (max_key_len - len(k) + 2)
                + "|  "
                + v
                + " " * (max_val_len - len(v) + 2)
                + "|\n"
            )
        stdout += ("-" * max_len + "\n")

    if self.tqdm:
        tqdm.write(stdout)
    else:
        print(stdout)