MEP / hub /logger.py
Clawdbot
Add enterprise JSON structured logging and Ledger Audit Trail
4301a95
import logging
import logging.handlers
import json
import os
from datetime import datetime, timezone
LOG_DIR = "logs"
os.makedirs(LOG_DIR, exist_ok=True)
class JSONFormatter(logging.Formatter):
"""Format logs as JSON for easy ingestion into Datadog, ELK, etc."""
def format(self, record):
log_data = {
"timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage()
}
# Add any extra kwargs passed to the log call
if hasattr(record, "extra_fields"):
log_data.update(record.extra_fields)
# Add exception info if present
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data)
def setup_logger(name: str, log_file: str, level=logging.INFO, json_format=True) -> logging.Logger:
"""Setup a rotating file logger."""
logger = logging.getLogger(name)
logger.setLevel(level)
# Avoid adding multiple handlers if setup is called multiple times
if logger.handlers:
return logger
# Rotate at 10MB, keep last 30 files
file_handler = logging.handlers.RotatingFileHandler(
os.path.join(LOG_DIR, log_file),
maxBytes=10*1024*1024,
backupCount=30
)
if json_format:
file_handler.setFormatter(JSONFormatter())
else:
# Standard text format for audit trails or simple logs
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# Also log to console for development
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
logger.addHandler(console_handler)
return logger
# --- Hub Loggers ---
hub_logger = setup_logger("mep.hub", "hub.json")
# --- Audit Logger ---
# The Audit Trail is strictly append-only text to reconstruct the ledger if needed
audit_logger = setup_logger("mep.audit", "ledger_audit.log", json_format=False)
def log_event(event: str, message: str, **kwargs):
"""Helper to log JSON events with extra fields."""
hub_logger.info(message, extra={"extra_fields": {"event": event, **kwargs}})
def log_audit(action: str, node_id: str, amount: float, new_balance: float, ref_id: str = ""):
"""Helper to strictly log SECONDS moving in the ledger."""
sign = "+" if amount >= 0 else ""
audit_logger.info(f"AUDIT | {action} | Node: {node_id} | Amount: {sign}{amount:.6f} | Balance: {new_balance:.6f} | Ref: {ref_id}")