89 lines
2.6 KiB
Python
89 lines
2.6 KiB
Python
import json
|
|
import logging
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Dict, Optional
|
|
|
|
|
|
class StructuredJsonFormatter(logging.Formatter):
|
|
"""
|
|
JSON log formatter emitting structured log records with standard telecom pipeline fields:
|
|
- timestamp (ISO 8601 UTC)
|
|
- level (INFO, WARN, ERROR, DEBUG)
|
|
- service (service name)
|
|
- raw_id (record identifier or None)
|
|
- event (message string)
|
|
"""
|
|
|
|
def __init__(self, service_name: str):
|
|
super().__init__()
|
|
self.service_name = service_name
|
|
|
|
def format(self, record: logging.LogRecord) -> str:
|
|
log_entry: Dict[str, Any] = {
|
|
"timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
"level": record.levelname,
|
|
"service": getattr(record, "service", self.service_name),
|
|
"raw_id": getattr(record, "raw_id", None),
|
|
"event": record.getMessage(),
|
|
}
|
|
|
|
# Include exception info if present
|
|
if record.exc_info:
|
|
log_entry["exception"] = self.formatException(record.exc_info)
|
|
|
|
# Include additional extra context fields if present
|
|
for key, value in record.__dict__.items():
|
|
if key not in {
|
|
"name",
|
|
"msg",
|
|
"args",
|
|
"levelname",
|
|
"levelno",
|
|
"pathname",
|
|
"filename",
|
|
"module",
|
|
"exc_info",
|
|
"exc_text",
|
|
"stack_info",
|
|
"lineno",
|
|
"funcName",
|
|
"created",
|
|
"msecs",
|
|
"relativeCreated",
|
|
"thread",
|
|
"threadName",
|
|
"processName",
|
|
"process",
|
|
"raw_id",
|
|
"service",
|
|
}:
|
|
log_entry[key] = value
|
|
|
|
return json.dumps(log_entry, default=str)
|
|
|
|
|
|
def setup_logger(
|
|
service_name: str, level: Optional[str] = None
|
|
) -> logging.Logger:
|
|
"""
|
|
Configures and returns a logger configured with StructuredJsonFormatter on stdout.
|
|
"""
|
|
if level is None:
|
|
from app.common.config import config
|
|
|
|
level = config.log_level
|
|
|
|
logger = logging.getLogger(service_name)
|
|
logger.setLevel(getattr(logging, level.upper(), logging.INFO))
|
|
logger.propagate = False
|
|
|
|
# Avoid duplicate handlers if setup is called multiple times
|
|
if not logger.handlers:
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler.setFormatter(StructuredJsonFormatter(service_name=service_name))
|
|
logger.addHandler(handler)
|
|
|
|
return logger
|
|
|