123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- import json
- import logging
- import sys
- from typing import TYPE_CHECKING
- from loguru import logger
- from opentelemetry import trace
- from open_webui.env import (
- AUDIT_UVICORN_LOGGER_NAMES,
- AUDIT_LOG_FILE_ROTATION_SIZE,
- AUDIT_LOG_LEVEL,
- AUDIT_LOGS_FILE_PATH,
- GLOBAL_LOG_LEVEL,
- ENABLE_OTEL,
- ENABLE_OTEL_LOGS,
- )
- if TYPE_CHECKING:
- from loguru import Record
- def stdout_format(record: "Record") -> str:
- """
- Generates a formatted string for log records that are output to the console. This format includes a timestamp, log level, source location (module, function, and line), the log message, and any extra data (serialized as JSON).
- Parameters:
- record (Record): A Loguru record that contains logging details including time, level, name, function, line, message, and any extra context.
- Returns:
- str: A formatted log string intended for stdout.
- """
- if record["extra"]:
- record["extra"]["extra_json"] = json.dumps(record["extra"])
- extra_format = " - {extra[extra_json]}"
- else:
- extra_format = ""
- return (
- "<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>" + extra_format + "\n{exception}"
- )
- class InterceptHandler(logging.Handler):
- """
- Intercepts log records from Python's standard logging module
- and redirects them to Loguru's logger.
- """
- def emit(self, record):
- """
- Called by the standard logging module for each log event.
- It transforms the standard `LogRecord` into a format compatible with Loguru
- and passes it to Loguru's logger.
- """
- try:
- level = logger.level(record.levelname).name
- except ValueError:
- level = record.levelno
- frame, depth = sys._getframe(6), 6
- while frame and frame.f_code.co_filename == logging.__file__:
- frame = frame.f_back
- depth += 1
- logger.opt(depth=depth, exception=record.exc_info).bind(
- **self._get_extras()
- ).log(level, record.getMessage())
- if ENABLE_OTEL and ENABLE_OTEL_LOGS:
- from open_webui.utils.telemetry.logs import otel_handler
- otel_handler.emit(record)
- def _get_extras(self):
- if not ENABLE_OTEL:
- return {}
- extras = {}
- context = trace.get_current_span().get_span_context()
- if context.is_valid:
- extras["trace_id"] = trace.format_trace_id(context.trace_id)
- extras["span_id"] = trace.format_span_id(context.span_id)
- return extras
- def file_format(record: "Record"):
- """
- Formats audit log records into a structured JSON string for file output.
- Parameters:
- record (Record): A Loguru record containing extra audit data.
- Returns:
- str: A JSON-formatted string representing the audit data.
- """
- audit_data = {
- "id": record["extra"].get("id", ""),
- "timestamp": int(record["time"].timestamp()),
- "user": record["extra"].get("user", dict()),
- "audit_level": record["extra"].get("audit_level", ""),
- "verb": record["extra"].get("verb", ""),
- "request_uri": record["extra"].get("request_uri", ""),
- "response_status_code": record["extra"].get("response_status_code", 0),
- "source_ip": record["extra"].get("source_ip", ""),
- "user_agent": record["extra"].get("user_agent", ""),
- "request_object": record["extra"].get("request_object", b""),
- "response_object": record["extra"].get("response_object", b""),
- "extra": record["extra"].get("extra", {}),
- }
- record["extra"]["file_extra"] = json.dumps(audit_data, default=str)
- return "{extra[file_extra]}\n"
- def start_logger():
- """
- Initializes and configures Loguru's logger with distinct handlers:
- A console (stdout) handler for general log messages (excluding those marked as auditable).
- An optional file handler for audit logs if audit logging is enabled.
- Additionally, this function reconfigures Python’s standard logging to route through Loguru and adjusts logging levels for Uvicorn.
- Parameters:
- enable_audit_logging (bool): Determines whether audit-specific log entries should be recorded to file.
- """
- logger.remove()
- logger.add(
- sys.stdout,
- level=GLOBAL_LOG_LEVEL,
- format=stdout_format,
- filter=lambda record: "auditable" not in record["extra"],
- )
- if AUDIT_LOG_LEVEL != "NONE":
- try:
- logger.add(
- AUDIT_LOGS_FILE_PATH,
- level="INFO",
- rotation=AUDIT_LOG_FILE_ROTATION_SIZE,
- compression="zip",
- format=file_format,
- filter=lambda record: record["extra"].get("auditable") is True,
- )
- except Exception as e:
- logger.error(f"Failed to initialize audit log file handler: {str(e)}")
- logging.basicConfig(
- handlers=[InterceptHandler()], level=GLOBAL_LOG_LEVEL, force=True
- )
- for uvicorn_logger_name in ["uvicorn", "uvicorn.error"]:
- uvicorn_logger = logging.getLogger(uvicorn_logger_name)
- uvicorn_logger.setLevel(GLOBAL_LOG_LEVEL)
- uvicorn_logger.handlers = []
- for uvicorn_logger_name in AUDIT_UVICORN_LOGGER_NAMES:
- uvicorn_logger = logging.getLogger(uvicorn_logger_name)
- uvicorn_logger.setLevel(GLOBAL_LOG_LEVEL)
- uvicorn_logger.handlers = [InterceptHandler()]
- logger.info(f"GLOBAL_LOG_LEVEL: {GLOBAL_LOG_LEVEL}")
|