Files
3cloud-backend/logger.py
T

106 lines
3.3 KiB
Python

import logging
from colorlog import ColoredFormatter
import os
import threading
from contextvars import ContextVar
# Context for non-Flask usage (Celery tasks, scripts, background jobs, etc.)
_request_id_ctx = ContextVar("request_id", default=None)
def set_request_id(request_id: str) -> None:
"""Set a request/correlation id for the current execution context."""
_request_id_ctx.set(request_id)
def clear_request_id() -> None:
"""Clear any request/correlation id set for the current execution context."""
_request_id_ctx.set(None)
class RequestFilter(logging.Filter):
"""Populate record.request_id from ContextVar, Flask, Celery, or fallbacks."""
def filter(self, record):
record.funcName = record.funcName if hasattr(record, 'funcName') else '<unknown>'
# Default when no explicit context is found
record.request_id = 'no-context'
# 1) Prefer an explicit ContextVar if the app set it
try:
ctx_id = _request_id_ctx.get()
if ctx_id:
record.request_id = ctx_id
return True
except Exception:
pass
# 2) Flask request context (if available)
try:
from flask import g, has_request_context
if has_request_context() and hasattr(g, 'request_id'):
record.request_id = g.request_id
return True
except Exception:
pass
# 3) Celery current task id (if running inside a Celery worker/task)
try:
from celery import current_task
task = current_task
if task and getattr(task, "request", None):
cid = getattr(task.request, "id", None) or getattr(task.request, "correlation_id", None)
if cid:
record.request_id = cid
return True
except Exception:
pass
# 4) Fallback to process/thread identifiers to provide some context
try:
record.request_id = f"pid:{os.getpid()}|thr:{threading.current_thread().name}"
except Exception:
# Keep the default 'no-context' if something goes wrong
pass
return True
# Define the log format with request_id
log_format = (
"%(log_color)s%(asctime)s - %(levelname)s - RequestID:[%(request_id)s] - %(funcName)s - %(message)s"
)
date_format = "%Y-%m-%d %H:%M:%S"
# Configure the formatter with colors
formatter = ColoredFormatter(
log_format,
datefmt=date_format,
log_colors={
"DEBUG": "cyan",
"INFO": "green",
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "bold_red",
},
)
# Configure the handler
handler = logging.StreamHandler()
handler.setFormatter(formatter)
# Configure the logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.addFilter(RequestFilter())
logger.addHandler(handler)
# Ensure the logs directory exists
log_dir = "logs"
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# Configure the file handler
file_handler = logging.FileHandler("logs/app.log")
file_handler.setFormatter(formatter)
file_handler.setLevel(logging.WARNING)
logger.addHandler(file_handler)
# Prevent logs from propagating to parent loggers (e.g., Celery's logger)
logger.propagate = False