| 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() |
| } |
| |
| |
| if hasattr(record, "extra_fields"): |
| log_data.update(record.extra_fields) |
| |
| |
| 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) |
| |
| |
| if logger.handlers: |
| return logger |
| |
| |
| 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: |
| |
| formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
| file_handler.setFormatter(formatter) |
| |
| logger.addHandler(file_handler) |
| |
| |
| console_handler = logging.StreamHandler() |
| console_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) |
| logger.addHandler(console_handler) |
| |
| return logger |
|
|
| |
| hub_logger = setup_logger("mep.hub", "hub.json") |
|
|
| |
| |
| 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}") |
|
|