Spaces:
Running
Running
File size: 6,805 Bytes
9024ad9 7ab470d 9024ad9 2ed2bd7 7ab470d 9024ad9 7ab470d 29ed661 2ed2bd7 7ab470d 9024ad9 7ab470d 75fe59b 7ab470d 9024ad9 7ab470d 75fe59b 9024ad9 7ab470d 9024ad9 7ab470d 2ed2bd7 7ab470d 9024ad9 2ed2bd7 7ab470d 75fe59b 7ab470d 2ed2bd7 7ab470d 75fe59b 7ab470d 2ed2bd7 9024ad9 7ab470d 75fe59b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
"""
Logging configuration for the text summarizer backend using Loguru.
This module provides structured logging with automatic request ID tracking,
environment-aware formatting (JSON for production, colored text for development),
and backward-compatible API with the previous stdlib logging implementation.
"""
import os
import sys
from contextvars import ContextVar
from typing import Any
from loguru import logger
from app.core.config import settings
# Context variable for request ID (automatic propagation across async contexts)
request_id_var: ContextVar[str | None] = ContextVar("request_id", default=None)
def _serialize_record(record: dict) -> str:
"""
Custom serializer for JSON format that includes extra fields.
Args:
record: Loguru record dictionary
Returns:
JSON formatted log string
"""
import json
# Build structured log entry
log_entry = {
"timestamp": record["time"].isoformat(),
"level": record["level"].name,
"logger": record["name"],
"message": record["message"],
"function": record["function"],
"line": record["line"],
}
# Add request ID from context if available
request_id = request_id_var.get()
if request_id:
log_entry["request_id"] = request_id
# Add any extra fields bound to the logger
if record["extra"]:
log_entry.update(record["extra"])
# Add exception info if present
if record["exception"]:
log_entry["exception"] = {
"type": record["exception"].type.__name__
if record["exception"].type
else None,
"value": str(record["exception"].value),
}
return json.dumps(log_entry)
def _determine_log_format() -> str:
"""
Determine log format based on environment.
Returns:
"json" for production (HF Spaces), "text" for development
"""
# Check if LOG_FORMAT is explicitly set
log_format = getattr(settings, "log_format", "auto")
if log_format == "auto":
# Auto-detect: JSON if running on HuggingFace Spaces, text otherwise
is_hf_spaces = os.getenv("HF_SPACE_ROOT_PATH") is not None
return "json" if is_hf_spaces else "text"
return log_format
def setup_logging() -> None:
"""
Set up Loguru logging configuration.
Configures logging with environment-aware formatting:
- Production (HF Spaces): Structured JSON output for log aggregation
- Development: Colored, human-readable text output
The logger automatically includes request IDs from context variables.
"""
# Remove default handler
logger.remove()
# Determine format based on environment
log_format_type = _determine_log_format()
log_level = settings.log_level.upper()
if log_format_type == "json":
# Production: JSON structured logging
logger.add(
sys.stdout,
format=_serialize_record,
level=log_level,
serialize=False, # We handle serialization ourselves
backtrace=True,
diagnose=False, # Don't show local variables in production
)
else:
# Development: Colored text logging
logger.add(
sys.stdout,
format=(
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<level>{level: <8}</level> | "
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
"<level>{message}</level>"
),
level=log_level,
colorize=True,
backtrace=True,
diagnose=True, # Show local variables for debugging
)
# Log startup configuration
logger.info(f"Logging initialized with format={log_format_type}, level={log_level}")
def get_logger(name: str) -> Any:
"""
Get a logger instance (backward-compatible with stdlib logging).
Args:
name: Logger name (typically __name__ of the calling module)
Returns:
Loguru logger instance bound to the module name
"""
# Bind the logger to the module name for context
return logger.bind(module=name)
class RequestLogger:
"""
Logger for request/response logging with automatic request ID tracking.
This class provides a backward-compatible API with the previous
stdlib logging implementation, but uses Loguru with automatic
context variable propagation for request IDs.
"""
def __init__(self, base_logger: Any = None):
"""
Initialize request logger.
Args:
base_logger: Base logger (ignored, uses global Loguru logger)
"""
# Always use the global logger with automatic request ID binding
self.logger = logger
def log_request(
self, method: str, path: str, request_id: str, **kwargs: Any
) -> None:
"""
Log incoming request with structured fields.
Args:
method: HTTP method (GET, POST, etc.)
path: Request path
request_id: Unique request identifier
**kwargs: Additional fields to log
"""
# Get request ID from context var (fallback to parameter)
context_request_id = request_id_var.get() or request_id
self.logger.bind(
request_id=context_request_id, method=method, path=path, **kwargs
).info(f"Request {context_request_id}: {method} {path}")
def log_response(
self, request_id: str, status_code: int, duration_ms: float, **kwargs: Any
) -> None:
"""
Log response with structured fields.
Args:
request_id: Unique request identifier
status_code: HTTP status code
duration_ms: Request duration in milliseconds
**kwargs: Additional fields to log
"""
# Get request ID from context var (fallback to parameter)
context_request_id = request_id_var.get() or request_id
self.logger.bind(
request_id=context_request_id,
status_code=status_code,
duration_ms=duration_ms,
**kwargs,
).info(f"Response {context_request_id}: {status_code} ({duration_ms:.2f}ms)")
def log_error(self, request_id: str, error: str, **kwargs: Any) -> None:
"""
Log error with structured fields.
Args:
request_id: Unique request identifier
error: Error message
**kwargs: Additional fields to log
"""
# Get request ID from context var (fallback to parameter)
context_request_id = request_id_var.get() or request_id
self.logger.bind(request_id=context_request_id, error=error, **kwargs).error(
f"Error {context_request_id}: {error}"
)
|