Implementation:Bentoml BentoML MonitorBase
| Knowledge Sources | |
|---|---|
| Domains | Monitoring, Observability, Data Logging, Abstract Base Class |
| Last Updated | 2026-02-13 15:00 GMT |
Overview
Defines the MonitorBase abstract base class and NoOpMonitor implementation for the BentoML monitoring system, providing the data logging and schema export interface.
Description
This module contains the foundational classes for BentoML's monitoring framework. It uses contextvars to maintain thread-safe, request-scoped state for column schemas and logged data.
MonitorBase[DT] is a generic abstract class that all monitor implementations must subclass. It manages two context variables:
- MON_COLUMN_VAR stores the column schema (name, role, type) discovered during the first record
- MON_DATAS_VAR stores the logged data as a dict of collections.deque objects keyed by column name
The recording lifecycle:
- start_record() initializes context variables; sets up the column schema dict on the first call
- log(data, name, role, data_type) records a single data point for a named column, validating the role (feature, prediction, target) and data type (numerical, categorical, numerical_sequence) against BENTOML_MONITOR_ROLES and BENTOML_MONITOR_TYPES. Reserved column names are auto-renamed with a trailing underscore
- log_batch(data_batch, name, role, data_type) iterates over the batch and calls log() for each element
- log_table() is a placeholder that logs a warning and returns (not yet implemented)
- stop_record() finalizes the record: on the first call, it captures the column schema and calls export_schema(); it validates all columns have equal length, then calls export_data()
Subclasses must implement export_schema() and export_data() to persist the schema and data to their chosen backend.
NoOpMonitor is a complete no-op implementation where all methods do nothing, used when monitoring is disabled.
Usage
Subclass MonitorBase to create custom monitoring backends. The DefaultMonitor and OTLPMonitor are built-in subclasses. The NoOpMonitor is used automatically when monitoring is disabled in configuration.
Code Reference
Source Location
- Repository: Bentoml_BentoML
- File: src/bentoml/_internal/monitoring/base.py
- Lines: 1-196
Signature
BENTOML_MONITOR_ROLES = {"feature", "prediction", "target"}
BENTOML_MONITOR_TYPES = {"numerical", "categorical", "numerical_sequence"}
class MonitorBase(t.Generic[DT]):
PRESERVED_COLUMNS: tuple[str, ...] = ()
def __init__(self, name: str, **_: t.Any) -> None: ...
def start_record(self): ...
def stop_record(self) -> None: ...
def export_schema(self, columns_schema: dict[str, dict[str, str]]) -> None: ...
def export_data(self, datas: dict[str, collections.deque[DT]]) -> None: ...
def log(self, data: DT, name: str, role: str, data_type: str) -> None: ...
def log_batch(
self, data_batch: t.Iterable[DT], name: str, role: str, data_type: str,
) -> None: ...
def log_table(
self, data: t.Iterable[t.Iterable[DT]], schema: dict[str, str],
) -> None: ...
class NoOpMonitor(MonitorBase[t.Any]):
# All methods are no-ops
...
Import
from bentoml._internal.monitoring.base import MonitorBase
from bentoml._internal.monitoring.base import NoOpMonitor
from bentoml._internal.monitoring.base import BENTOML_MONITOR_ROLES
from bentoml._internal.monitoring.base import BENTOML_MONITOR_TYPES
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| name | str | Yes (constructor) | Name identifying this monitor instance |
| data | DT | Yes (log) | A single data value to record |
| name (log) | str | Yes (log) | Column name for the data point; reserved names are auto-suffixed with "_" |
| role | str | Yes (log) | Data role: "feature", "prediction", or "target" (others logged with warning) |
| data_type | str | Yes (log) | Data type: "numerical", "categorical", or "numerical_sequence" (others logged with warning) |
| data_batch | Iterable[DT] | Yes (log_batch) | Iterable of data values to log as a single column |
Outputs
| Name | Type | Description |
|---|---|---|
| (side effect: export_schema) | None | Called once on first stop_record() with the discovered column schema dict |
| (side effect: export_data) | None | Called on each stop_record() with the logged data as dict of deques |
Usage Examples
from bentoml._internal.monitoring.base import MonitorBase
import collections
import typing as t
# Implementing a custom monitor backend
class MyCustomMonitor(MonitorBase[float]):
PRESERVED_COLUMNS = ("timestamp", "request_id")
def __init__(self, name: str, endpoint: str = "", **kwargs):
super().__init__(name)
self.endpoint = endpoint
def export_schema(self, columns_schema: dict[str, dict[str, str]]) -> None:
# Send schema to external system
print(f"Schema for {self.name}: {columns_schema}")
def export_data(self, datas: dict[str, collections.deque[float]]) -> None:
# Send data to external system
for col_name, values in datas.items():
print(f" {col_name}: {list(values)}")
# Using the monitor
monitor = MyCustomMonitor("my_monitor", endpoint="http://localhost:9090")
monitor.start_record()
monitor.log(1.0, "feature_x", "feature", "numerical")
monitor.log(2.0, "feature_y", "feature", "numerical")
monitor.log(0.95, "confidence", "prediction", "numerical")
monitor.stop_record()
# Output:
# Schema for my_monitor: {
# "feature_x": {"name": "feature_x", "role": "feature", "type": "numerical"},
# "feature_y": {"name": "feature_y", "role": "feature", "type": "numerical"},
# "confidence": {"name": "confidence", "role": "prediction", "type": "numerical"},
# }
# feature_x: [1.0]
# feature_y: [2.0]
# confidence: [0.95]
# Using NoOpMonitor (all operations are silently ignored)
from bentoml._internal.monitoring.base import NoOpMonitor
noop = NoOpMonitor("disabled")
noop.start_record()
noop.log(42, "x", "feature", "numerical")
noop.stop_record() # Nothing happens