mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-04 14:12:58 +00:00
The backend import package is now docsgpt, the name it will carry on PyPI; application was far too generic to install into anyone's site-packages. git mv plus a mechanical rewrite of every import, dotted string and path reference: 734 Python files, the compose files, Dockerfile, workflows, docs, setup scripts, devcontainer, k8s manifests, vscode config, pytest and coverage config, .gitignore. Behaviour is unchanged. Kept for one release: - A top-level application package whose meta-path finder resolves application.x.y to the already-imported docsgpt.x.y object, so old imports and entry points (celery -A application.app.celery, uvicorn application.asgi:asgi_app) keep working with a FutureWarning. - Celery registers every application.* task name as an alias of its docsgpt.* task on start-up, so messages queued by the previous release still run. The redbeat key prefix moves to redbeat:docsgpt:v2: so schedule entries the previous release wrote are left unread instead of firing twice. The backend image builds from the repository root (docker build -f docsgpt/Dockerfile .) so it can ship the alias package; a root .dockerignore allow-lists docsgpt/ and application/ and keeps caches, local data, .env files, the sample index files and the Dockerfile out. Compose and the image workflows point at the new context.
113 lines
3.8 KiB
Python
113 lines
3.8 KiB
Python
import logging
|
|
import os
|
|
from logging.config import dictConfig
|
|
|
|
from docsgpt.core.log_context import snapshot as _ctx_snapshot
|
|
|
|
|
|
# Loggers with ``propagate=False`` don't share root's handlers, so the
|
|
# context filter has to be installed on their handlers directly.
|
|
_NON_PROPAGATING_LOGGERS: tuple[str, ...] = (
|
|
"uvicorn",
|
|
"uvicorn.access",
|
|
"uvicorn.error",
|
|
"celery.app.trace",
|
|
"celery.worker.strategy",
|
|
"gunicorn.error",
|
|
"gunicorn.access",
|
|
)
|
|
|
|
|
|
class _ContextFilter(logging.Filter):
|
|
"""Stamp the current ``log_context`` snapshot onto every ``LogRecord``.
|
|
|
|
Must be installed on **handlers**, not loggers: Python skips logger-level
|
|
filters when a child logger's record propagates up. The ``hasattr`` guard
|
|
keeps an explicit ``logger.info(..., extra={...})`` from being overwritten.
|
|
"""
|
|
|
|
def filter(self, record: logging.LogRecord) -> bool:
|
|
for key, value in _ctx_snapshot().items():
|
|
if not hasattr(record, key):
|
|
setattr(record, key, value)
|
|
return True
|
|
|
|
|
|
def _otlp_logs_enabled() -> bool:
|
|
"""Return True when the user has opted in to OTLP log export.
|
|
|
|
Gated by the standard OTEL env vars so no project-specific knob is needed:
|
|
set ``OTEL_LOGS_EXPORTER=otlp`` (and leave ``OTEL_SDK_DISABLED`` unset or
|
|
false) to flip it on. When false, ``setup_logging`` keeps its original
|
|
console-only behavior.
|
|
"""
|
|
exporter = os.getenv("OTEL_LOGS_EXPORTER", "").strip().lower()
|
|
disabled = os.getenv("OTEL_SDK_DISABLED", "false").strip().lower() == "true"
|
|
return exporter == "otlp" and not disabled
|
|
|
|
|
|
def setup_logging() -> None:
|
|
"""Configure the root logger with a stdout console handler.
|
|
|
|
When OTLP log export is enabled, ``opentelemetry-instrument`` attaches a
|
|
``LoggingHandler`` to the root logger before this function runs. The
|
|
``dictConfig`` call below replaces ``root.handlers`` with the console
|
|
handler, which would silently drop the OTEL handler. To make OTLP log
|
|
export work without forcing every contributor to opt in, snapshot the
|
|
OTEL handlers up front and re-attach them after ``dictConfig``.
|
|
"""
|
|
preserved_handlers: list[logging.Handler] = []
|
|
if _otlp_logs_enabled():
|
|
preserved_handlers = [
|
|
h
|
|
for h in logging.getLogger().handlers
|
|
if h.__class__.__module__.startswith("opentelemetry")
|
|
]
|
|
|
|
dictConfig({
|
|
"version": 1,
|
|
"disable_existing_loggers": False,
|
|
"formatters": {
|
|
"default": {
|
|
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
|
|
}
|
|
},
|
|
"handlers": {
|
|
"console": {
|
|
"class": "logging.StreamHandler",
|
|
"stream": "ext://sys.stdout",
|
|
"formatter": "default",
|
|
}
|
|
},
|
|
"root": {
|
|
"level": "INFO",
|
|
"handlers": ["console"],
|
|
},
|
|
})
|
|
|
|
if preserved_handlers:
|
|
root = logging.getLogger()
|
|
for handler in preserved_handlers:
|
|
if handler not in root.handlers:
|
|
root.addHandler(handler)
|
|
|
|
_install_context_filter()
|
|
|
|
|
|
def _install_context_filter() -> None:
|
|
"""Attach :class:`_ContextFilter` to root's handlers + every handler on
|
|
the known non-propagating loggers. Skipping handlers that already carry
|
|
one keeps repeat ``setup_logging`` calls from stacking filters.
|
|
"""
|
|
|
|
def _has_ctx_filter(handler: logging.Handler) -> bool:
|
|
return any(isinstance(f, _ContextFilter) for f in handler.filters)
|
|
|
|
for handler in logging.getLogger().handlers:
|
|
if not _has_ctx_filter(handler):
|
|
handler.addFilter(_ContextFilter())
|
|
for name in _NON_PROPAGATING_LOGGERS:
|
|
for handler in logging.getLogger(name).handlers:
|
|
if not _has_ctx_filter(handler):
|
|
handler.addFilter(_ContextFilter())
|