|
| 1 | +import logging |
| 2 | +import os |
| 3 | +import threading |
| 4 | +from datetime import datetime, timezone |
| 5 | +from typing import Optional, Any, Dict, List |
| 6 | + |
| 7 | +import requests |
| 8 | + |
| 9 | + |
| 10 | +class FireworksTracingHttpHandler(logging.Handler): |
| 11 | + """Logging handler that posts structured logs to tracing.fireworks gateway /logs endpoint.""" |
| 12 | + |
| 13 | + def __init__(self, gateway_base_url: Optional[str] = None, rollout_id_env: str = "EP_ROLLOUT_ID") -> None: |
| 14 | + super().__init__() |
| 15 | + self.gateway_base_url = gateway_base_url or os.getenv("FW_TRACING_GATEWAY_BASE_URL") |
| 16 | + self.rollout_id_env = rollout_id_env |
| 17 | + self._session = requests.Session() |
| 18 | + self._lock = threading.Lock() |
| 19 | + |
| 20 | + def emit(self, record: logging.LogRecord) -> None: |
| 21 | + try: |
| 22 | + if not self.gateway_base_url: |
| 23 | + return |
| 24 | + rollout_id = self._get_rollout_id(record) |
| 25 | + if not rollout_id: |
| 26 | + return |
| 27 | + payload = self._build_payload(record, rollout_id) |
| 28 | + url = f"{self.gateway_base_url.rstrip('/')}/logs" |
| 29 | + with self._lock: |
| 30 | + self._session.post(url, json=payload, timeout=5) |
| 31 | + except Exception: |
| 32 | + # Avoid raising exceptions from logging |
| 33 | + self.handleError(record) |
| 34 | + |
| 35 | + def _get_rollout_id(self, record: logging.LogRecord) -> Optional[str]: |
| 36 | + if hasattr(record, "rollout_id") and record.rollout_id is not None: # type: ignore |
| 37 | + return str(record.rollout_id) # type: ignore |
| 38 | + return os.getenv(self.rollout_id_env) |
| 39 | + |
| 40 | + def _build_payload(self, record: logging.LogRecord, rollout_id: str) -> Dict[str, Any]: |
| 41 | + timestamp = datetime.fromtimestamp(record.created, tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ") |
| 42 | + message = record.getMessage() |
| 43 | + tags: List[str] = [f"rollout_id:{rollout_id}"] |
| 44 | + # Optional additional tags |
| 45 | + if hasattr(record, "experiment_id") and record.experiment_id: |
| 46 | + tags.append(f"experiment_id:{record.experiment_id}") # type: ignore |
| 47 | + if hasattr(record, "run_id") and record.run_id: |
| 48 | + tags.append(f"run_id:{record.run_id}") # type: ignore |
| 49 | + program = getattr(record, "program", None) or "eval_protocol" |
| 50 | + status = getattr(record, "status", None) |
| 51 | + return { |
| 52 | + "program": program, |
| 53 | + "status": status if isinstance(status, str) else None, |
| 54 | + "message": message, |
| 55 | + "tags": tags, |
| 56 | + "metadata": getattr(record, "metadata", None), |
| 57 | + "extras": { |
| 58 | + "logger_name": record.name, |
| 59 | + "level": record.levelname, |
| 60 | + "timestamp": timestamp, |
| 61 | + }, |
| 62 | + } |
0 commit comments