50 lines
1.5 KiB
Python
50 lines
1.5 KiB
Python
"""结构化 JSON 日志 + trace_id。"""
|
|
import logging
|
|
import sys
|
|
import uuid
|
|
from contextvars import ContextVar
|
|
|
|
from pythonjsonlogger import jsonlogger
|
|
|
|
trace_id_var: ContextVar[str] = ContextVar("trace_id", default="")
|
|
|
|
|
|
def get_trace_id() -> str:
|
|
t = trace_id_var.get()
|
|
if not t:
|
|
t = str(uuid.uuid4())
|
|
trace_id_var.set(t)
|
|
return t
|
|
|
|
|
|
def set_trace_id(tid: str) -> None:
|
|
trace_id_var.set(tid)
|
|
|
|
|
|
class TraceIdFilter(logging.Filter):
|
|
def filter(self, record: logging.LogRecord) -> bool:
|
|
record.trace_id = get_trace_id()
|
|
return True
|
|
|
|
|
|
class JsonFormatter(jsonlogger.JsonFormatter):
|
|
def add_fields(self, log_record: dict, record: logging.LogRecord, message_dict: dict) -> None:
|
|
super().add_fields(log_record, record, message_dict)
|
|
log_record["trace_id"] = getattr(record, "trace_id", "")
|
|
log_record["level"] = record.levelname
|
|
if record.exc_info:
|
|
log_record["exception"] = self.formatException(record.exc_info)
|
|
|
|
|
|
def setup_logging(log_level: str = "INFO", log_json: bool = True) -> None:
|
|
root = logging.getLogger()
|
|
root.handlers.clear()
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
if log_json:
|
|
handler.setFormatter(JsonFormatter("%(timestamp)s %(level)s %(message)s %(trace_id)s"))
|
|
else:
|
|
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s [%(trace_id)s] %(message)s"))
|
|
handler.addFilter(TraceIdFilter())
|
|
root.addHandler(handler)
|
|
root.setLevel(getattr(logging, log_level.upper(), logging.INFO))
|