Merge branch 'dev' into feat/otel-logger-handler

This commit is contained in:
Tim Jaeryang Baek 2025-08-02 14:52:16 +04:00 committed by GitHub
commit 49926f06ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 80 additions and 27 deletions

View file

@ -644,12 +644,19 @@ AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
ENABLE_OTEL = os.environ.get("ENABLE_OTEL", "False").lower() == "true" ENABLE_OTEL = os.environ.get("ENABLE_OTEL", "False").lower() == "true"
ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "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" ENABLE_OTEL_LOGS = os.environ.get("ENABLE_OTEL_LOGS", "False").lower() == "true"
OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get( OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get(
"OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317" "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"
) )
OTEL_METRICS_EXPORTER_OTLP_ENDPOINT = os.environ.get(
"OTEL_METRICS_EXPORTER_OTLP_ENDPOINT", OTEL_EXPORTER_OTLP_ENDPOINT
)
OTEL_EXPORTER_OTLP_INSECURE = ( OTEL_EXPORTER_OTLP_INSECURE = (
os.environ.get("OTEL_EXPORTER_OTLP_INSECURE", "False").lower() == "true" os.environ.get("OTEL_EXPORTER_OTLP_INSECURE", "False").lower() == "true"
) )
OTEL_METRICS_EXPORTER_OTLP_INSECURE = (
os.environ.get("OTEL_METRICS_EXPORTER_OTLP_INSECURE", "False").lower() == "true"
)
OTEL_SERVICE_NAME = os.environ.get("OTEL_SERVICE_NAME", "open-webui") OTEL_SERVICE_NAME = os.environ.get("OTEL_SERVICE_NAME", "open-webui")
OTEL_RESOURCE_ATTRIBUTES = os.environ.get( OTEL_RESOURCE_ATTRIBUTES = os.environ.get(
"OTEL_RESOURCE_ATTRIBUTES", "" "OTEL_RESOURCE_ATTRIBUTES", ""
@ -660,11 +667,21 @@ OTEL_TRACES_SAMPLER = os.environ.get(
OTEL_BASIC_AUTH_USERNAME = os.environ.get("OTEL_BASIC_AUTH_USERNAME", "") OTEL_BASIC_AUTH_USERNAME = os.environ.get("OTEL_BASIC_AUTH_USERNAME", "")
OTEL_BASIC_AUTH_PASSWORD = os.environ.get("OTEL_BASIC_AUTH_PASSWORD", "") OTEL_BASIC_AUTH_PASSWORD = os.environ.get("OTEL_BASIC_AUTH_PASSWORD", "")
OTEL_METRICS_BASIC_AUTH_USERNAME = os.environ.get(
"OTEL_METRICS_BASIC_AUTH_USERNAME", OTEL_BASIC_AUTH_USERNAME
)
OTEL_METRICS_BASIC_AUTH_PASSWORD = os.environ.get(
"OTEL_METRICS_BASIC_AUTH_PASSWORD", OTEL_BASIC_AUTH_PASSWORD
)
OTEL_OTLP_SPAN_EXPORTER = os.environ.get( OTEL_OTLP_SPAN_EXPORTER = os.environ.get(
"OTEL_OTLP_SPAN_EXPORTER", "grpc" "OTEL_OTLP_SPAN_EXPORTER", "grpc"
).lower() # grpc or http ).lower() # grpc or http
OTEL_METRICS_OTLP_SPAN_EXPORTER = os.environ.get(
"OTEL_METRICS_OTLP_SPAN_EXPORTER", OTEL_OTLP_SPAN_EXPORTER
).lower() # grpc or http
#################################### ####################################
# TOOLS/FUNCTIONS PIP OPTIONS # TOOLS/FUNCTIONS PIP OPTIONS

View file

@ -19,37 +19,69 @@ from __future__ import annotations
import time import time
from typing import Dict, List, Sequence, Any from typing import Dict, List, Sequence, Any
from base64 import b64encode
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from opentelemetry import metrics from opentelemetry import metrics
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
OTLPMetricExporter, OTLPMetricExporter,
) )
from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
OTLPMetricExporter as OTLPHttpMetricExporter,
)
from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.view import View from opentelemetry.sdk.metrics.view import View
from opentelemetry.sdk.metrics.export import ( from opentelemetry.sdk.metrics.export import (
PeriodicExportingMetricReader, PeriodicExportingMetricReader,
) )
from opentelemetry.sdk.resources import SERVICE_NAME, Resource from opentelemetry.sdk.resources import Resource
from open_webui.env import OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT
from open_webui.env import (
OTEL_SERVICE_NAME,
OTEL_METRICS_EXPORTER_OTLP_ENDPOINT,
OTEL_METRICS_BASIC_AUTH_USERNAME,
OTEL_METRICS_BASIC_AUTH_PASSWORD,
OTEL_METRICS_OTLP_SPAN_EXPORTER,
OTEL_METRICS_EXPORTER_OTLP_INSECURE,
)
from open_webui.socket.main import get_active_user_ids from open_webui.socket.main import get_active_user_ids
from open_webui.models.users import Users from open_webui.models.users import Users
_EXPORT_INTERVAL_MILLIS = 10_000 # 10 seconds _EXPORT_INTERVAL_MILLIS = 10_000 # 10 seconds
def _build_meter_provider() -> MeterProvider: def _build_meter_provider(resource: Resource) -> MeterProvider:
"""Return a configured MeterProvider.""" """Return a configured MeterProvider."""
headers = []
if OTEL_METRICS_BASIC_AUTH_USERNAME and OTEL_METRICS_BASIC_AUTH_PASSWORD:
auth_string = (
f"{OTEL_METRICS_BASIC_AUTH_USERNAME}:{OTEL_METRICS_BASIC_AUTH_PASSWORD}"
)
auth_header = b64encode(auth_string.encode()).decode()
headers = [("authorization", f"Basic {auth_header}")]
# Periodic reader pushes metrics over OTLP/gRPC to collector # Periodic reader pushes metrics over OTLP/gRPC to collector
readers: List[PeriodicExportingMetricReader] = [ if OTEL_METRICS_OTLP_SPAN_EXPORTER == "http":
PeriodicExportingMetricReader( readers: List[PeriodicExportingMetricReader] = [
OTLPMetricExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT), PeriodicExportingMetricReader(
export_interval_millis=_EXPORT_INTERVAL_MILLIS, OTLPHttpMetricExporter(
) endpoint=OTEL_METRICS_EXPORTER_OTLP_ENDPOINT, headers=headers
] ),
export_interval_millis=_EXPORT_INTERVAL_MILLIS,
)
]
else:
readers: List[PeriodicExportingMetricReader] = [
PeriodicExportingMetricReader(
OTLPMetricExporter(
endpoint=OTEL_METRICS_EXPORTER_OTLP_ENDPOINT,
insecure=OTEL_METRICS_EXPORTER_OTLP_INSECURE,
headers=headers,
),
export_interval_millis=_EXPORT_INTERVAL_MILLIS,
)
]
# Optional view to limit cardinality: drop user-agent etc. # Optional view to limit cardinality: drop user-agent etc.
views: List[View] = [ views: List[View] = [
@ -70,17 +102,17 @@ def _build_meter_provider() -> MeterProvider:
] ]
provider = MeterProvider( provider = MeterProvider(
resource=Resource.create({SERVICE_NAME: OTEL_SERVICE_NAME}), resource=resource,
metric_readers=list(readers), metric_readers=list(readers),
views=views, views=views,
) )
return provider return provider
def setup_metrics(app: FastAPI) -> None: def setup_metrics(app: FastAPI, resource: Resource) -> None:
"""Attach OTel metrics middleware to *app* and initialise provider.""" """Attach OTel metrics middleware to *app* and initialise provider."""
metrics.set_meter_provider(_build_meter_provider()) metrics.set_meter_provider(_build_meter_provider(resource))
meter = metrics.get_meter(__name__) meter = metrics.get_meter(__name__)
# Instruments # Instruments

View file

@ -26,11 +26,8 @@ from open_webui.env import (
def setup(app: FastAPI, db_engine: Engine): def setup(app: FastAPI, db_engine: Engine):
# set up trace # set up trace
trace.set_tracer_provider( resource = Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME})
TracerProvider( trace.set_tracer_provider(TracerProvider(resource=resource))
resource=Resource.create(attributes={SERVICE_NAME: OTEL_SERVICE_NAME})
)
)
# Add basic auth header only if both username and password are not empty # Add basic auth header only if both username and password are not empty
headers = [] headers = []
@ -56,4 +53,4 @@ def setup(app: FastAPI, db_engine: Engine):
# set up metrics only if enabled # set up metrics only if enabled
if ENABLE_OTEL_METRICS: if ENABLE_OTEL_METRICS:
setup_metrics(app) setup_metrics(app, resource)

View file

@ -520,6 +520,8 @@ async def get_tool_servers_data(
openapi_data = response.get("openapi", {}) openapi_data = response.get("openapi", {})
if info and isinstance(openapi_data, dict): if info and isinstance(openapi_data, dict):
openapi_data["info"] = openapi_data.get("info", {})
if "name" in info: if "name" in info:
openapi_data["info"]["title"] = info.get("name", "Tool Server") openapi_data["info"]["title"] = info.get("name", "Tool Server")

View file

@ -9,7 +9,7 @@ passlib[bcrypt]==1.7.4
cryptography cryptography
requests==2.32.4 requests==2.32.4
aiohttp==3.11.11 aiohttp==3.12.15
async-timeout async-timeout
aiocache aiocache
aiofiles aiofiles
@ -42,14 +42,14 @@ asgiref==3.8.1
# AI libraries # AI libraries
openai openai
anthropic anthropic
google-genai==1.15.0 google-genai==1.28.0
google-generativeai==0.8.5 google-generativeai==0.8.5
tiktoken tiktoken
langchain==0.3.26 langchain==0.3.26
langchain-community==0.3.26 langchain-community==0.3.26
fake-useragent==2.1.0 fake-useragent==2.2.0
chromadb==0.6.3 chromadb==0.6.3
posthog==5.4.0 posthog==5.4.0
pymilvus==2.5.0 pymilvus==2.5.0
@ -75,7 +75,7 @@ docx2txt==0.8
python-pptx==1.0.2 python-pptx==1.0.2
unstructured==0.16.17 unstructured==0.16.17
nltk==3.9.1 nltk==3.9.1
Markdown==3.7 Markdown==3.8.2
pypandoc==1.15 pypandoc==1.15
pandas==2.2.3 pandas==2.2.3
openpyxl==3.1.5 openpyxl==3.1.5
@ -97,7 +97,7 @@ onnxruntime==1.20.1
faster-whisper==1.1.1 faster-whisper==1.1.1
PyJWT[crypto]==2.10.1 PyJWT[crypto]==2.10.1
authlib==1.4.1 authlib==1.6.1
black==25.1.0 black==25.1.0
langfuse==2.44.0 langfuse==2.44.0

View file

@ -122,6 +122,11 @@
{:else if token.text.includes(`<source_id`)} {:else if token.text.includes(`<source_id`)}
<Source {id} {token} onClick={onSourceClick} /> <Source {id} {token} onClick={onSourceClick} />
{:else} {:else}
{token.text} {@const br = token.text.match(/<br\s*\/?>/)}
{#if br}
<br />
{:else}
{token.text}
{/if}
{/if} {/if}
{/if} {/if}

View file

@ -82,7 +82,7 @@
const initPinnedModelsSortable = () => { const initPinnedModelsSortable = () => {
const pinnedModelsList = document.getElementById('pinned-models-list'); const pinnedModelsList = document.getElementById('pinned-models-list');
if (pinnedModelsList) { if (pinnedModelsList && !$mobile) {
new Sortable(pinnedModelsList, { new Sortable(pinnedModelsList, {
animation: 150, animation: 150,
onUpdate: async (event) => { onUpdate: async (event) => {

View file

@ -1072,7 +1072,7 @@
"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "به خود به عنوان \"کاربر\" اشاره کنید (مثلاً، \"کاربر در حال یادگیری اسپانیایی است\")", "Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "به خود به عنوان \"کاربر\" اشاره کنید (مثلاً، \"کاربر در حال یادگیری اسپانیایی است\")",
"References from": "مراجع از", "References from": "مراجع از",
"Refused when it shouldn't have": "رد شده زمانی که باید نباشد", "Refused when it shouldn't have": "رد شده زمانی که باید نباشد",
"Regenerate": "ری\u200cسازی", "Regenerate": "تولید مجدد",
"Reindex": "فهرست\u200cبندی مجدد", "Reindex": "فهرست\u200cبندی مجدد",
"Reindex Knowledge Base Vectors": "فهرست\u200cبندی مجدد بردارهای پایگاه دانش", "Reindex Knowledge Base Vectors": "فهرست\u200cبندی مجدد بردارهای پایگاه دانش",
"Release Notes": "یادداشت\u200cهای انتشار", "Release Notes": "یادداشت\u200cهای انتشار",