"""Structured logging configuration for FusionAGI. Supports JSON and text output formats, configurable via environment variables: - ``FUSIONAGI_LOG_LEVEL``: DEBUG, INFO, WARNING, ERROR (default: INFO) - ``FUSIONAGI_LOG_FORMAT``: json, text (default: text) """ from __future__ import annotations import json import logging import os import sys from datetime import datetime, timezone from typing import Any class JsonFormatter(logging.Formatter): """JSON structured log formatter for log aggregation (ELK, Loki, Datadog).""" def format(self, record: logging.LogRecord) -> str: log_entry: dict[str, Any] = { "timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(), "level": record.levelname, "logger": record.name, "message": record.getMessage(), } if record.exc_info and record.exc_info[1]: log_entry["exception"] = self.formatException(record.exc_info) # Include extra fields extra_keys = set(record.__dict__) - { "name", "msg", "args", "created", "relativeCreated", "exc_info", "exc_text", "stack_info", "lineno", "funcName", "filename", "module", "pathname", "thread", "threadName", "process", "processName", "levelname", "levelno", "msecs", "message", "taskName", } for key in extra_keys: val = getattr(record, key, None) if val is not None: log_entry[key] = val return json.dumps(log_entry, default=str) def configure_logging() -> None: """Configure logging based on environment variables.""" level_name = os.environ.get("FUSIONAGI_LOG_LEVEL", "INFO").upper() log_format = os.environ.get("FUSIONAGI_LOG_FORMAT", "text").lower() level = getattr(logging, level_name, logging.INFO) root = logging.getLogger() root.setLevel(level) # Remove existing handlers for handler in root.handlers[:]: root.removeHandler(handler) handler = logging.StreamHandler(sys.stdout) handler.setLevel(level) if log_format == "json": handler.setFormatter(JsonFormatter()) else: handler.setFormatter(logging.Formatter( "%(asctime)s %(levelname)-8s %(name)s — %(message)s", datefmt="%Y-%m-%d %H:%M:%S", )) root.addHandler(handler) # Quiet noisy libraries for lib in ("uvicorn.access", "httpx", "httpcore"): logging.getLogger(lib).setLevel(logging.WARNING)