diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 8cddfc6b08..3af3e328df 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -643,6 +643,7 @@ AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS] ENABLE_OTEL = os.environ.get("ENABLE_OTEL", "False").lower() == "true" ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "False").lower() == "true" +ENABLE_OTEL_LOGS = os.environ.get("ENABLE_OTEL_LOGS", "False").lower() == "true" OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get( "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317" ) diff --git a/backend/open_webui/utils/logger.py b/backend/open_webui/utils/logger.py index ff7d5c4546..236aaf62ea 100644 --- a/backend/open_webui/utils/logger.py +++ b/backend/open_webui/utils/logger.py @@ -5,8 +5,6 @@ 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, @@ -14,6 +12,7 @@ from open_webui.env import ( AUDIT_LOGS_FILE_PATH, GLOBAL_LOG_LEVEL, ENABLE_OTEL, + ENABLE_OTEL_LOGS, ) @@ -65,6 +64,10 @@ class InterceptHandler(logging.Handler): 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: @@ -126,7 +129,6 @@ def start_logger(): format=stdout_format, filter=lambda record: "auditable" not in record["extra"], ) - if AUDIT_LOG_LEVEL != "NONE": try: logger.add( diff --git a/backend/open_webui/utils/telemetry/logs.py b/backend/open_webui/utils/telemetry/logs.py new file mode 100644 index 0000000000..992c839411 --- /dev/null +++ b/backend/open_webui/utils/telemetry/logs.py @@ -0,0 +1,53 @@ +import logging +from base64 import b64encode +from opentelemetry.sdk._logs import ( + LoggingHandler, + LoggerProvider, +) +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter +from opentelemetry.exporter.otlp.proto.http._log_exporter import ( + OTLPLogExporter as HttpOTLPLogExporter, +) +from opentelemetry.sdk._logs.export import BatchLogRecordProcessor +from opentelemetry._logs import set_logger_provider +from opentelemetry.sdk.resources import SERVICE_NAME, Resource +from open_webui.env import ( + OTEL_SERVICE_NAME, + OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_INSECURE, + OTEL_BASIC_AUTH_USERNAME, + OTEL_BASIC_AUTH_PASSWORD, + OTEL_OTLP_SPAN_EXPORTER, +) + + +def setup_logging(): + headers = [] + if OTEL_BASIC_AUTH_USERNAME and OTEL_BASIC_AUTH_PASSWORD: + auth_string = f"{OTEL_BASIC_AUTH_USERNAME}:{OTEL_BASIC_AUTH_PASSWORD}" + auth_header = b64encode(auth_string.encode()).decode() + headers = [("authorization", f"Basic {auth_header}")] + resource = Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME}) + + if OTEL_OTLP_SPAN_EXPORTER == "http": + exporter = HttpOTLPLogExporter( + endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, + headers=headers, + ) + else: + exporter = OTLPLogExporter( + endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, + insecure=OTEL_EXPORTER_OTLP_INSECURE, + headers=headers, + ) + logger_provider = LoggerProvider(resource=resource) + set_logger_provider(logger_provider) + + logger_provider.add_log_record_processor(BatchLogRecordProcessor(exporter)) + + otel_handler = LoggingHandler(logger_provider=logger_provider) + + return otel_handler + + +otel_handler = setup_logging()