backtrader.bokeh.tabs.log 源代码
#!/usr/bin/env python
"""
Log tab.
Displays strategy execution logs.
"""
import logging
from collections import deque
from ..tab import BokehTab
try:
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, DataTable, TableColumn
from bokeh.models.widgets import Div
BOKEH_AVAILABLE = True
except ImportError:
BOKEH_AVAILABLE = False
# Global log storage
_log_storage = {}
[文档]
class LogHandler(logging.Handler):
"""Log handler.
Captures log messages and stores them in specified storage.
"""
[文档]
def __init__(self, storage_key, max_records=1000):
"""Initialize log handler.
Args:
storage_key: Key to identify log storage in global dictionary.
max_records: Maximum number of log records to keep (default: 1000).
"""
super().__init__()
self.storage_key = storage_key
self.max_records = max_records
if storage_key not in _log_storage:
_log_storage[storage_key] = deque(maxlen=max_records)
[文档]
def emit(self, record):
"""Emit a log record.
Args:
record: Log record to process.
"""
log_entry = {
"time": self.format(record).split(" - ")[0] if " - " in self.format(record) else "",
"level": record.levelname,
"message": record.getMessage(),
}
_log_storage[self.storage_key].append(log_entry)
[文档]
def getlogger(name="backtrader", col=None):
"""Get logger with log handler.
Args:
name: Logger name
col: Custom columns (optional)
Returns:
logging.Logger instance
"""
logger = logging.getLogger(name)
# Check if LogHandler already added
has_handler = any(isinstance(h, LogHandler) for h in logger.handlers)
if not has_handler:
handler = LogHandler(name)
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
logger.addHandler(handler)
return logger
[文档]
class LogTab(BokehTab):
"""Log tab.
Displays log information during strategy execution.
Attributes:
cols: Column configuration for display
"""
cols = ["Time", "Level", "Message"] # Default columns
[文档]
def __init__(self, app, figurepage, client=None, cols=None):
"""Initialize log tab.
Args:
app: Bokeh application instance.
figurepage: Figure page instance.
client: Optional client instance.
cols: Custom column configuration for log display.
"""
super().__init__(app, figurepage, client)
if cols is not None:
self.cols = cols
def _is_useable(self):
"""Log tab is always useable."""
return BOKEH_AVAILABLE
def _get_panel(self):
"""Get panel content.
Returns:
tuple: (widget, title)
"""
scheme = self.scheme
text_color = scheme.text_color if scheme else "#333"
widgets = []
# Title
widgets.append(
Div(
text=f'<h3 style="color: {text_color};">Log Messages</h3>',
sizing_mode="stretch_width",
)
)
# Get log data
log_data = self._get_log_data()
if log_data:
# Create data source
source = ColumnDataSource(data=log_data)
# Create columns
columns = []
for col_name in log_data.keys():
columns.append(TableColumn(field=col_name, title=col_name.capitalize()))
# Create table
table = DataTable(
source=source, columns=columns, width=800, height=400, index_position=None
)
widgets.append(table)
else:
widgets.append(Div(text="<p>No log messages available</p>"))
content = column(*widgets, sizing_mode="stretch_width")
return content, "Log"
def _get_log_data(self):
"""Get log data.
Returns:
dict: Log data dictionary
"""
# Get data from global log storage
all_logs = []
for key, logs in _log_storage.items():
all_logs.extend(list(logs))
if not all_logs:
return None
# Sort by time (newest first)
all_logs = list(reversed(all_logs))
# Convert to column data format
return {
"time": [log.get("time", "") for log in all_logs],
"level": [log.get("level", "") for log in all_logs],
"message": [log.get("message", "") for log in all_logs],
}
[文档]
def LogTabs(cols):
"""Create log tab class with custom columns.
Args:
cols: Column configuration
Returns:
Custom LogTab class
"""
return type("LogTab", (LogTab,), {"cols": cols})