Monitoring¶
Generate HTML dashboards and reports from Python data structures with type-safe render components, Jinja2 integration, and automated delivery.
TL;DR¶
from kstlib.monitoring import Monitoring, MonitorKV, StatusCell, StatusLevel
# Create monitoring with inline template
mon = Monitoring(template="""
<h1>{{ title }}</h1>
{{ metrics | render }}
""")
# Register collectors via decorator
@mon.collector
def title():
return "Server Status"
@mon.collector
def metrics():
return MonitorKV(items={
"API": StatusCell("UP", StatusLevel.OK),
"DB": StatusCell("DEGRADED", StatusLevel.WARNING),
})
# Run: collect -> render -> deliver
result = mon.run_sync()
print(result.html)
Key Features¶
Render Types: Type-safe components (StatusCell, MonitorTable, MonitorKV, MonitorList, MonitorMetric, MonitorImage)
Jinja2 Integration:
| renderfilter for seamless template renderingDecorator API: Register collectors with
@mon.collectorConfig-Driven: Load from
kstlib.conf.ymlor*.monitor.ymlfilesDelivery Backends: Save to file or send via email (Gmail OAuth2)
Security: XSS prevention, SVG sanitization, size limits
Quick Start¶
Option 1: Inline Template¶
from kstlib.monitoring import Monitoring, MonitorTable, StatusCell, StatusLevel
mon = Monitoring(template="""
<html>
<body>
<h1>{{ title }}</h1>
{{ table | render }}
</body>
</html>
""")
@mon.collector
def title():
return "Service Status"
@mon.collector
def table():
t = MonitorTable(headers=["Service", "Status"])
t.add_row(["API", StatusCell("UP", StatusLevel.OK)])
t.add_row(["DB", StatusCell("DOWN", StatusLevel.CRITICAL)])
return t
result = mon.run_sync()
Option 2: Template File¶
mon = Monitoring(template_file="templates/dashboard.html")
@mon.collector
def metrics():
return collect_system_metrics()
result = mon.run_sync()
Option 3: Config-Driven¶
# kstlib.conf.yml
monitoring:
name: server-status
template_file: templates/dashboard.html
inline_css: true
delivery:
type: mail
sender: bot@example.com
recipients:
- team@example.com
mon = Monitoring.from_config()
@mon.collector
def metrics():
return collect_metrics()
mon.run_sync() # Renders and sends email
Render Types¶
StatusCell¶
Colored status badges with semantic levels:
from kstlib.monitoring import StatusCell, StatusLevel
# Four status levels
cell = StatusCell("HEALTHY", StatusLevel.OK) # Green
cell = StatusCell("DEGRADED", StatusLevel.WARNING) # Yellow
cell = StatusCell("DOWN", StatusLevel.ERROR) # Orange
cell = StatusCell("FAILURE", StatusLevel.CRITICAL) # Red
html = cell.render() # <span class="status-ok">HEALTHY</span>
MonitorTable¶
Striped HTML tables with typed headers:
from kstlib.monitoring import MonitorTable, StatusCell, StatusLevel
table = MonitorTable(headers=["Service", "Status", "Uptime"])
table.add_row([
"API Gateway",
StatusCell("UP", StatusLevel.OK),
"99.9%"
])
table.add_row([
"Database",
StatusCell("DEGRADED", StatusLevel.WARNING),
"98.5%"
])
html = table.render()
MonitorKV¶
Key-value stat panels as two-column grids:
from kstlib.monitoring import MonitorKV
kv = MonitorKV()
kv.add("CPU Usage", "75%")
kv.add("Memory", "8.2 GB / 16 GB")
kv.add("Disk", "120 GB free")
# Or from dict
kv = MonitorKV(items={"CPU": "75%", "Memory": "8GB"})
html = kv.render()
MonitorList¶
Ordered or unordered lists for events and alerts:
from kstlib.monitoring import MonitorList
events = MonitorList(ordered=False)
events.add("2026-01-28 10:30 - Service restarted")
events.add("2026-01-28 09:15 - High CPU alert")
# Or from list
events = MonitorList(items=["Event 1", "Event 2"], ordered=True)
html = events.render()
MonitorMetric¶
Hero-number display for KPIs:
from kstlib.monitoring import MonitorMetric
pnl = MonitorMetric(
value="+$1,234.56",
label="Today's P&L",
color="#00cc00"
)
html = pnl.render() # Large styled number with label
MonitorImage¶
Embedded images as Base64 data URIs (no external dependencies):
from kstlib.monitoring import MonitorImage
# From file (auto-detects format via magic bytes)
logo = MonitorImage.from_file("logo.png")
# From bytes
icon = MonitorImage.from_bytes(png_data, "image/png")
# With sizing
logo = MonitorImage(path="logo.png", width=128, height=64, alt="Logo")
html = logo.render() # <img src="data:image/png;base64,..." />
Supported formats: PNG, JPEG, GIF, WebP, SVG (sanitized), ICO.
Jinja2 Integration¶
The | render Filter¶
All render types work seamlessly with Jinja2 via the | render filter:
from kstlib.monitoring import create_environment
env = create_environment()
template = env.from_string("""
<div class="dashboard">
<h1>Status Dashboard</h1>
{{ table | render }}
<h2>Stats</h2>
{{ kv | render }}
</div>
""")
Inline CSS Mode¶
For email clients that strip <style> tags:
from kstlib.monitoring import render_template
# Styles embedded in each element
html = render_template(source, context, inline_css=True)
Custom Environment¶
Configure Jinja2 with your own loader:
from jinja2 import FileSystemLoader
from kstlib.monitoring import create_environment
env = create_environment(
loader=FileSystemLoader("templates/"),
trim_blocks=True,
lstrip_blocks=True,
)
Delivery Backends¶
FileDelivery¶
Save HTML reports to local files with automatic rotation:
from kstlib.monitoring import Monitoring, FileDelivery
mon = Monitoring(
template_file="dashboard.html",
delivery=FileDelivery(
output_dir="./reports",
max_files=7, # Keep last 7 reports
),
)
@mon.collector
def data():
return collect_data()
mon.run_sync() # Saves to ./reports/monitoring_YYYYMMDD_HHMMSS.html
MailDelivery¶
Send reports via email using kstlib.mail transports:
# kstlib.conf.yml
monitoring:
template_file: dashboard.html
inline_css: true # Required for email!
delivery:
type: mail
sender: bot@example.com
recipients:
- team@example.com
subject_template: "Daily Report: {name}"
mon = Monitoring.from_config()
@mon.collector
def metrics():
return collect_metrics()
mon.run_sync() # Sends email via Gmail OAuth2
Note
Mail delivery requires a valid Gmail OAuth2 token. Run kstlib auth login google first.
Configuration¶
In kstlib.conf.yml¶
monitoring:
# Dashboard name (used in delivery subject)
name: server-status
# Template (mutually exclusive)
template: "<p>{{ msg }}</p>" # Inline
template_file: dashboard.html # Or file path
# Rendering options
inline_css: true # Embed styles (required for email)
fail_fast: false # Continue on collector errors
# Delivery backend
delivery:
type: file # or "mail"
output_dir: ./reports
max_files: 100
Per-call Overrides¶
# Override at instantiation
mon = Monitoring(
template_file="dashboard.html",
inline_css=False, # Override default
fail_fast=True,
)
# Control delivery at runtime
result = mon.run_sync(deliver=False) # Skip delivery
Common Patterns¶
Server Monitoring Dashboard¶
import platform
from kstlib.monitoring import (
Monitoring, MonitorKV, MonitorTable,
StatusCell, StatusLevel, MonitorImage
)
mon = Monitoring(template_file="server.html")
@mon.collector
def system_info():
return MonitorKV(items={
"Hostname": platform.node(),
"OS": f"{platform.system()} {platform.release()}",
"Python": platform.python_version(),
})
@mon.collector
def services():
import psutil
cpu = psutil.cpu_percent()
table = MonitorTable(headers=["Metric", "Value", "Status"])
level = StatusLevel.OK if cpu < 80 else StatusLevel.WARNING
table.add_row(["CPU", f"{cpu}%", StatusCell("OK" if cpu < 80 else "HIGH", level)])
return table
@mon.collector
def logo():
return MonitorImage(path="logo.png", width=100)
mon.run_sync()
Trading Bot P&L Report¶
from kstlib.monitoring import Monitoring, MonitorMetric, MonitorTable, StatusCell, StatusLevel
mon = Monitoring(template_file="pnl.html")
@mon.collector
def daily_pnl():
pnl = calculate_pnl()
color = "#00cc00" if pnl >= 0 else "#cc0000"
return MonitorMetric(
value=f"${pnl:+,.2f}",
label="Today's P&L",
color=color,
)
@mon.collector
def positions():
table = MonitorTable(headers=["Symbol", "Side", "P&L", "Status"])
for pos in get_positions():
level = StatusLevel.OK if pos.pnl >= 0 else StatusLevel.ERROR
table.add_row([
pos.symbol,
pos.side,
f"${pos.pnl:+.2f}",
StatusCell("PROFIT" if pos.pnl >= 0 else "LOSS", level),
])
return table
mon.run_sync()
Conditional Sections¶
<!-- dashboard.html -->
<h1>{{ title }}</h1>
{{ metrics | render }}
{% if alerts %}
<div class="alerts">
<h2>Active Alerts</h2>
{{ alerts | render }}
</div>
{% endif %}
@mon.collector
def alerts():
items = get_active_alerts()
if not items:
return None # Section won't render
return MonitorList(items=items)
MonitoringService (Advanced)¶
For more control, use MonitoringService directly:
from kstlib.monitoring import MonitoringService, MonitorKV
service = MonitoringService(
template="<p>{{ data | render }}</p>",
collectors={
"data": lambda: MonitorKV(items={"key": "value"}),
},
inline_css=True,
fail_fast=False,
)
# Async
result = await service.run()
# Sync
result = service.run_sync()
print(result.html)
print(result.success)
print(result.errors) # Dict of collector errors
Config Loader (Advanced)¶
Load monitoring configs from *.monitor.yml files:
# dashboard.monitor.yml
name: daily-report
template: |
<h1>{{ title }}</h1>
<p>Version: {{ version }}</p>
collectors:
title: "Daily Report"
version:
type: env
env_var: APP_VERSION
default: "1.0.0"
from kstlib.monitoring import load_monitoring_config, discover_monitoring_configs
# Load single config
config = load_monitoring_config("dashboard.monitor.yml")
service = config.to_service()
result = service.run_sync()
# Discover all configs in directory
configs = discover_monitoring_configs("./monitoring", recursive=True)
for name, config in configs.items():
service = config.to_service()
service.run_sync()
Security¶
The monitoring module includes defense-in-depth protections:
Feature |
Protection |
|---|---|
XSS Prevention |
All non-Renderable values are HTML-escaped |
SVG Sanitization |
Scripts and dangerous elements removed |
Image Validation |
Magic bytes verification, format whitelist |
Size Limits |
512KB max per image, 50MB max output |
Module Blocklist |
Dangerous modules blocked in config loaders |
Path Traversal |
Output paths validated against base directory |
Autoescape |
Enabled by default in Jinja2 environment |
Troubleshooting¶
- Template not found
Ensure
template_filepath is relative tobase_dir(defaults to cwd).- Email not sending
Run
kstlib auth login googleto get OAuth2 token. Checkinline_css: true.- Collector errors
Check
result.errorsdict. Setfail_fast=Trueto raise on first error.- Images not displaying in email
Gmail may block external images. Use
MonitorImagefor Base64 embedding.- CSS not applied in email
Always use
inline_css=Truefor email delivery.
API Reference¶
See Monitoring API for complete API documentation.
See Monitoring Exceptions for exception handling patterns.