diff --git a/Dockerfile b/Dockerfile index 9371f564..4a2e07a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,14 +36,14 @@ COPY --from=builder /fba /fba COPY --from=builder /usr/local /usr/local -COPY deploy/backend/supervisord.conf /etc/supervisor/supervisord.conf +COPY deploy/backend/supervisor/supervisord.conf /etc/supervisor/supervisord.conf WORKDIR /fba/backend # === FastAPI server image === FROM base_server AS fba_server -COPY deploy/backend/fba_server.conf /etc/supervisor/conf.d/ +COPY deploy/backend/supervisor/fba_server.conf /etc/supervisor/conf.d/ RUN mkdir -p /var/log/fba @@ -54,7 +54,7 @@ CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"] # === Celery Worker image === FROM base_server AS fba_celery_worker -COPY deploy/backend/fba_celery_worker.conf /etc/supervisor/conf.d/ +COPY deploy/backend/supervisor/fba_celery_worker.conf /etc/supervisor/conf.d/ RUN mkdir -p /var/log/fba @@ -63,7 +63,7 @@ CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"] # === Celery Beat image === FROM base_server AS fba_celery_beat -COPY deploy/backend/fba_celery_beat.conf /etc/supervisor/conf.d/ +COPY deploy/backend/supervisor/fba_celery_beat.conf /etc/supervisor/conf.d/ RUN mkdir -p /var/log/fba @@ -72,7 +72,7 @@ CMD ["supervisord", "-c", "/etc/supervisor/supervisord.conf"] # === Celery Flower image === FROM base_server AS fba_celery_flower -COPY deploy/backend/fba_celery_flower.conf /etc/supervisor/conf.d/ +COPY deploy/backend/supervisor/fba_celery_flower.conf /etc/supervisor/conf.d/ RUN mkdir -p /var/log/fba diff --git a/backend/app/task/celery.py b/backend/app/task/celery.py index 24b62c35..8047bb5e 100644 --- a/backend/app/task/celery.py +++ b/backend/app/task/celery.py @@ -52,6 +52,8 @@ def init_celery() -> celery.Celery: task_track_started=True, enable_utc=False, timezone=settings.DATETIME_TIMEZONE, + worker_send_task_events=True, + task_send_sent_event=True, ) # 在 Celery 中设置此参数无效 diff --git a/backend/common/exception/exception_handler.py b/backend/common/exception/exception_handler.py index 575b26b5..ecdae0ca 100644 --- a/backend/common/exception/exception_handler.py +++ b/backend/common/exception/exception_handler.py @@ -2,6 +2,7 @@ from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from pydantic import ValidationError from starlette.exceptions import HTTPException +from starlette.middleware.cors import CORSMiddleware from uvicorn.protocols.http.h11_impl import STATUS_PHRASES from backend.common.context import ctx @@ -75,7 +76,7 @@ async def _validation_exception_handler(exc: RequestValidationError | Validation return MsgSpecJSONResponse(status_code=StandardResponseCode.HTTP_422, content=content) -def register_exception(app: FastAPI) -> None: +def register_exception(app: FastAPI) -> None: # noqa: C901 @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): """ @@ -194,3 +195,55 @@ def register_exception(app: FastAPI) -> None: status_code=StandardResponseCode.HTTP_500, content=content, ) + + if settings.MIDDLEWARE_CORS: + + @app.exception_handler(StandardResponseCode.HTTP_500) + async def cors_custom_code_500_exception_handler(request: Request, exc: BaseExceptionError | Exception): + """ + 跨域自定义 500 异常处理 + + :param request: FastAPI 请求对象 + :param exc: 自定义异常 + :return: + """ + if isinstance(exc, BaseExceptionError): + content = { + 'code': exc.code, + 'msg': exc.msg, + 'data': exc.data, + } + else: + if settings.ENVIRONMENT == 'dev': + content = { + 'code': StandardResponseCode.HTTP_500, + 'msg': str(exc), + 'data': None, + } + else: + res = response_base.fail(res=CustomResponseCode.HTTP_500) + content = res.model_dump() + content.update(trace_id=get_request_trace_id()) + response = MsgSpecJSONResponse( + status_code=exc.code if isinstance(exc, BaseExceptionError) else StandardResponseCode.HTTP_500, + content=content, + background=exc.background if isinstance(exc, BaseExceptionError) else None, + ) + origin = request.headers.get('origin') + if origin: + cors = CORSMiddleware( + app=app, + allow_origins=settings.CORS_ALLOWED_ORIGINS, + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], + expose_headers=settings.CORS_EXPOSE_HEADERS, + ) + response.headers.update(cors.simple_headers) + has_cookie = 'cookie' in request.headers + if cors.allow_all_origins and has_cookie: + response.headers['Access-Control-Allow-Origin'] = origin + elif not cors.allow_all_origins and cors.is_allowed_origin(origin=origin): + response.headers['Access-Control-Allow-Origin'] = origin + response.headers.add_vary_header('Origin') + return response diff --git a/backend/common/log.py b/backend/common/log.py index fe5be304..88a9bd0c 100644 --- a/backend/common/log.py +++ b/backend/common/log.py @@ -47,6 +47,13 @@ def default_formatter(record: logging.LogRecord) -> str: return settings.LOG_FORMAT if settings.LOG_FORMAT.endswith('\n') else f'{settings.LOG_FORMAT}\n' +def request_id_filter(record: logging.LogRecord) -> logging.LogRecord: + """请求 ID 过滤器""" + rid = get_request_trace_id() + record['request_id'] = rid[: settings.TRACE_ID_LOG_LENGTH] + return record + + def setup_logging() -> None: """ 设置日志处理器 @@ -75,12 +82,6 @@ def setup_logging() -> None: # 移除 loguru 默认处理器 logger.remove() - # request_id 过滤器 - def request_id_filter(record: logging.LogRecord) -> logging.LogRecord: - rid = get_request_trace_id() - record['request_id'] = rid[: settings.TRACE_ID_LOG_LENGTH] - return record - # 配置 loguru 处理器 logger.configure( handlers=[ diff --git a/backend/common/prometheus/__init__.py b/backend/common/prometheus/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/common/prometheus/instruments.py b/backend/common/prometheus/instruments.py new file mode 100644 index 00000000..9759ff6b --- /dev/null +++ b/backend/common/prometheus/instruments.py @@ -0,0 +1,35 @@ +from prometheus_client import Counter, Gauge, Histogram + +from backend.core.conf import settings + +PROMETHEUS_INFO_GAUGE = ( + Gauge(name='fba_app_info', documentation='fba 应用信息', labelnames=['app_name']) + .labels(app_name=settings.GRAFANA_APP_NAME) + .inc() +) + +PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE = Gauge( + 'fba_request_in_progress', + '按方法和路径统计请求的衡量', + ['app_name', 'method', 'path'], +) + +PROMETHEUS_REQUEST_COUNTER = Counter('fba_request_total', '按方法和路径统计请求总数', ['app_name', 'method', 'path']) + +PROMETHEUS_RESPONSE_COUNTER = Counter( + 'fba_response_total', + '按方法、路径和状态码统计响应总数', + ['app_name', 'method', 'path', 'status_code'], +) + +PROMETHEUS_EXCEPTION_COUNTER = Counter( + 'fba_exception_total', + '按方法,路径和异常类型统计异常总数', + ['app_name', 'method', 'path', 'exception_type'], +) + +PROMETHEUS_REQUEST_COST_TIME_HISTOGRAM = Histogram( + 'fba_request_cost_time', + '按方法和路径划分请求耗时的直方图(以 ms 为单位)', + ['app_name', 'method', 'path'], +) diff --git a/backend/core/conf.py b/backend/core/conf.py index 6b443576..1d03a0df 100644 --- a/backend/core/conf.py +++ b/backend/core/conf.py @@ -23,7 +23,7 @@ class Settings(BaseSettings): # FastAPI FASTAPI_API_V1_PATH: str = '/api/v1' - FASTAPI_TITLE: str = 'FastAPI' + FASTAPI_TITLE: str = 'fba' FASTAPI_DESCRIPTION: str = 'FastAPI Best Architecture' FASTAPI_DOCS_URL: str = '/docs' FASTAPI_REDOC_URL: str = '/redoc' @@ -215,6 +215,11 @@ class Settings(BaseSettings): # I18n 配置 I18N_DEFAULT_LANGUAGE: str = 'zh-CN' + # Grafana + GRAFANA_METRICS: bool = False + GRAFANA_APP_NAME: str = 'fba_server' + GRAFANA_OTLP_GRPC_ENDPOINT: str = 'fba_alloy:4317' + ################################################## # [ App ] task ################################################## diff --git a/backend/core/registrar.py b/backend/core/registrar.py index dd2408a9..d6cdd1f1 100644 --- a/backend/core/registrar.py +++ b/backend/core/registrar.py @@ -9,10 +9,10 @@ import socketio from fastapi import Depends, FastAPI from fastapi_limiter import FastAPILimiter from fastapi_pagination import add_pagination +from prometheus_client import make_asgi_app from starlette.middleware.authentication import AuthenticationMiddleware from starlette.middleware.cors import CORSMiddleware from starlette.staticfiles import StaticFiles -from starlette.types import ASGIApp from starlette_context.middleware import ContextMiddleware from starlette_context.plugins import RequestIdPlugin @@ -33,8 +33,10 @@ from backend.plugin.tools import build_final_router from backend.utils.demo_site import demo_site from backend.utils.health_check import ensure_unique_route_names, http_limit_callback from backend.utils.openapi import simplify_operation_ids +from backend.utils.otel import init_otel from backend.utils.serializers import MsgSpecJSONResponse from backend.utils.snowflake import snowflake +from backend.utils.trace_id import OtelTraceIdPlugin @asynccontextmanager @@ -76,22 +78,7 @@ async def register_init(app: FastAPI) -> AsyncGenerator[None, None]: def register_app() -> FastAPI: """注册 FastAPI 应用""" - class MyFastAPI(FastAPI): - if settings.MIDDLEWARE_CORS: - # Related issues - # https://github.com/fastapi/fastapi/discussions/7847 - # https://github.com/fastapi/fastapi/discussions/8027 - def build_middleware_stack(self) -> ASGIApp: - return CORSMiddleware( - super().build_middleware_stack(), - allow_origins=settings.CORS_ALLOWED_ORIGINS, - allow_credentials=True, - allow_methods=['*'], - allow_headers=['*'], - expose_headers=settings.CORS_EXPOSE_HEADERS, - ) - - app = MyFastAPI( + app = FastAPI( title=settings.FASTAPI_TITLE, version=__version__, description=settings.FASTAPI_DESCRIPTION, @@ -111,6 +98,9 @@ def register_app() -> FastAPI: register_page(app) register_exception(app) + if settings.GRAFANA_METRICS: + register_metrics(app) + return app @@ -164,15 +154,29 @@ def register_middleware(app: FastAPI) -> None: app.add_middleware(AccessMiddleware) # ContextVar + plugins = [OtelTraceIdPlugin()] if settings.GRAFANA_METRICS else [RequestIdPlugin(validate=True)] app.add_middleware( ContextMiddleware, - plugins=[RequestIdPlugin(validate=True)], + plugins=plugins, default_error_response=MsgSpecJSONResponse( content={'code': StandardResponseCode.HTTP_400, 'msg': 'BAD_REQUEST', 'data': None}, status_code=StandardResponseCode.HTTP_400, ), ) + # CORS + # https://github.com/fastapi-practices/fastapi_best_architecture/pull/789/changes + # https://github.com/open-telemetry/opentelemetry-python-contrib/issues/4031 + if settings.MIDDLEWARE_CORS: + app.add_middleware( + CORSMiddleware, + allow_origins=settings.CORS_ALLOWED_ORIGINS, + allow_credentials=True, + allow_methods=['*'], + allow_headers=['*'], + expose_headers=settings.CORS_EXPOSE_HEADERS, + ) + def register_router(app: FastAPI) -> None: """ @@ -218,3 +222,16 @@ def register_socket_app(app: FastAPI) -> None: socketio_path='/ws/socket.io', ) app.mount('/ws', socket_app) + + +def register_metrics(app: FastAPI) -> None: + """ + 注册指标 + + :param app: FastAPI 应用实例 + :return: + """ + metrics_app = make_asgi_app() + app.mount('/metrics', metrics_app) + + init_otel(app) diff --git a/backend/middleware/opera_log_middleware.py b/backend/middleware/opera_log_middleware.py index 2c8dea5f..765db869 100644 --- a/backend/middleware/opera_log_middleware.py +++ b/backend/middleware/opera_log_middleware.py @@ -14,6 +14,13 @@ from backend.app.admin.service.opera_log_service import opera_log_service from backend.common.context import ctx from backend.common.enums import OperaLogCipherType, StatusType from backend.common.log import log +from backend.common.prometheus.instruments import ( + PROMETHEUS_EXCEPTION_COUNTER, + PROMETHEUS_REQUEST_COST_TIME_HISTOGRAM, + PROMETHEUS_REQUEST_COUNTER, + PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE, + PROMETHEUS_RESPONSE_COUNTER, +) from backend.common.queue import batch_dequeue from backend.common.response.response_code import StandardResponseCode from backend.core.conf import settings @@ -43,6 +50,10 @@ class OperaLogMiddleware(BaseHTTPMiddleware): else: method = request.method args = await self.get_request_args(request) + PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE.labels( + app_name=settings.GRAFANA_APP_NAME, method=method, path=path + ).inc() + PROMETHEUS_REQUEST_COUNTER.labels(app_name=settings.GRAFANA_APP_NAME, method=method, path=path).inc() # 执行请求 code = 200 @@ -63,6 +74,12 @@ class OperaLogMiddleware(BaseHTTPMiddleware): code = exception.get('code') msg = exception.get('msg') log.error(f'请求异常: {msg}') + PROMETHEUS_EXCEPTION_COUNTER.labels( + app_name=settings.GRAFANA_APP_NAME, + method=method, + path=path, + exception_type=type(e).__name__, + ).inc() break except Exception as e: elapsed = round((time.perf_counter() - ctx.perf_time) * 1000, 3) @@ -71,6 +88,20 @@ class OperaLogMiddleware(BaseHTTPMiddleware): status = StatusType.disable error = e log.error(f'请求异常: {e!s}') + PROMETHEUS_EXCEPTION_COUNTER.labels( + app_name=settings.GRAFANA_APP_NAME, method=method, path=path, exception_type=type(e).__name__ + ).inc() + else: + PROMETHEUS_REQUEST_COST_TIME_HISTOGRAM.labels( + app_name=settings.GRAFANA_APP_NAME, method=method, path=path + ).observe(elapsed, exemplar={'TraceID': get_request_trace_id()}) + finally: + PROMETHEUS_RESPONSE_COUNTER.labels( + app_name=settings.GRAFANA_APP_NAME, method=method, path=path, status_code=code + ).inc() + PROMETHEUS_REQUEST_IN_PROGRESS_GAUGE.labels( + app_name=settings.GRAFANA_APP_NAME, method=method, path=path + ).dec() # 此信息只能在请求后获取 route = request.scope.get('route') diff --git a/backend/utils/otel.py b/backend/utils/otel.py new file mode 100644 index 00000000..48dcd775 --- /dev/null +++ b/backend/utils/otel.py @@ -0,0 +1,59 @@ +from fastapi import FastAPI +from opentelemetry import trace +from opentelemetry._logs import set_logger_provider +from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor +from opentelemetry.instrumentation.logging import LoggingInstrumentor +from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor +from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler +from opentelemetry.sdk._logs._internal.export import BatchLogRecordProcessor +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor + +from backend.common.log import log, request_id_filter +from backend.core.conf import settings +from backend.database.db import async_engine + + +def init_otel(app: FastAPI) -> None: + """ + 初始化 OpenTelemetry + + :param app: FastAPI 应用实例 + :return: + """ + from backend import __version__ + + resource = Resource( + attributes={ + 'service.name': settings.GRAFANA_APP_NAME, + 'service.version': __version__, + 'deployment.environment': settings.ENVIRONMENT, + }, + ) + + tracer_provider = TracerProvider(resource=resource) + trace.set_tracer_provider(tracer_provider) + + span_exporter = OTLPSpanExporter(endpoint=settings.GRAFANA_OTLP_GRPC_ENDPOINT, insecure=True) + tracer_provider.add_span_processor(BatchSpanProcessor(span_exporter)) + + logger_provider = LoggerProvider(resource=resource) + set_logger_provider(logger_provider) + + log_exporter = OTLPLogExporter(endpoint=settings.GRAFANA_OTLP_GRPC_ENDPOINT, insecure=True) + logger_provider.add_log_record_processor(BatchLogRecordProcessor(log_exporter)) + + otel_logging_handler = LoggingHandler(logger_provider=logger_provider) + log.add( # type: ignore + otel_logging_handler, + level=settings.LOG_STD_LEVEL, + format=settings.LOG_FORMAT, + filter=lambda record: request_id_filter(record), + ) + + LoggingInstrumentor().instrument(set_logging_format=True) + SQLAlchemyInstrumentor().instrument(engine=async_engine.sync_engine) + FastAPIInstrumentor.instrument_app(app, tracer_provider=tracer_provider) diff --git a/backend/utils/trace_id.py b/backend/utils/trace_id.py index 3d753dd7..e07114d5 100644 --- a/backend/utils/trace_id.py +++ b/backend/utils/trace_id.py @@ -1,9 +1,31 @@ +from typing import Any + +from opentelemetry import trace +from starlette.requests import Request +from starlette_context.plugins import Plugin + from backend.common.context import ctx from backend.core.conf import settings def get_request_trace_id() -> str: - """从请求头中获取追踪 ID""" + """从上下文中获取追踪 ID""" if ctx.exists(): return ctx.get(settings.TRACE_ID_REQUEST_HEADER_KEY, settings.TRACE_ID_LOG_DEFAULT_VALUE) return settings.TRACE_ID_LOG_DEFAULT_VALUE + + +class OtelTraceIdPlugin(Plugin): + """OpenTelemetry Trace ID 插件""" + + key = settings.TRACE_ID_REQUEST_HEADER_KEY + + async def process_request(self, request: Request) -> Any: + """从 OpenTelemetry span 中提取 trace_id""" + span = trace.get_current_span() + span_ctx = span.get_span_context() + + if span_ctx.is_valid: + return trace.format_trace_id(span_ctx.trace_id) + + return settings.TRACE_ID_LOG_DEFAULT_VALUE diff --git a/deploy/backend/grafana/dashboards/fba_celery.json b/deploy/backend/grafana/dashboards/fba_celery.json new file mode 100644 index 00000000..7e16be37 --- /dev/null +++ b/deploy/backend/grafana/dashboards/fba_celery.json @@ -0,0 +1,2049 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "注释与警报", + "type": "dashboard" + } + ] + }, + "description": "Celery 分布式任务队列监控仪表盘 - 实时监控 Worker 状态、任务执行情况、队列长度和性能指标", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "fastapi", + "fba_server" + ], + "targetBlank": true, + "title": "FastAPI 监控", + "type": "dashboards" + } + ], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 100, + "title": "🖥️ Worker 概览", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "显示每个 Celery Worker 的运行状态。绿色表示在线,红色表示离线", + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "0": { + "color": "#E74C3C", + "text": "离线" + }, + "1": { + "color": "#2ECC71", + "text": "在线" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#E74C3C", + "value": null + }, + { + "color": "#2ECC71", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "celery_worker_up{job=\"fba_celery_exporter\"}", + "legendFormat": "{{hostname}}", + "refId": "A" + } + ], + "title": "Worker 状态", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "当前在线的 Worker 数量", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#2ECC71", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#2ECC71", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "count(celery_worker_up{job=\"fba_celery_exporter\"} == 1) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "在线 Worker", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "当前正在执行的任务数量", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#3498DB", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#3498DB", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 8, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_worker_tasks_active{job=\"fba_celery_exporter\"}) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "执行中任务", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "任务成功率 = 成功任务数 / (成功 + 失败)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#E74C3C", + "value": null + }, + { + "color": "#F39C12", + "value": 0.9 + }, + { + "color": "#2ECC71", + "value": 0.95 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 12, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_task_succeeded_total{job=\"fba_celery_exporter\", name!=\"None\"}) / (sum(celery_task_succeeded_total{job=\"fba_celery_exporter\", name!=\"None\"}) + sum(celery_task_failed_total{job=\"fba_celery_exporter\", name!=\"None\"}) + 0.001)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "任务成功率", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "各队列中等待处理的任务数量", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#2ECC71", + "value": null + }, + { + "color": "#F39C12", + "value": 50 + }, + { + "color": "#E74C3C", + "value": 100 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 16, + "y": 1 + }, + "id": 5, + "options": { + "colorMode": "background_solid", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "value_and_name" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "celery_queue_length{job=\"fba_celery_exporter\"}", + "legendFormat": "{{queue_name}}", + "refId": "A" + } + ], + "title": "队列长度", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "各 Worker 当前处理的任务数量分布", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 6, + "options": { + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "celery_worker_tasks_active{job=\"fba_celery_exporter\"} >= 0", + "legendFormat": "{{hostname}}", + "refId": "A" + } + ], + "title": "Worker 负载分布", + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 101, + "title": "📊 任务统计", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "已发送到队列的任务总数(累计值)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#9B59B6", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 0, + "y": 6 + }, + "id": 10, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_task_sent_total{job=\"fba_celery_exporter\", name!=\"None\"}) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "已发送", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Worker 已接收的任务总数(累计值)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#3498DB", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 3, + "y": 6 + }, + "id": 11, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_task_received_total{job=\"fba_celery_exporter\", name!=\"None\"}) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "已接收", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "已开始执行的任务总数(累计值)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#1ABC9C", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 6, + "y": 6 + }, + "id": 12, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_task_started_total{job=\"fba_celery_exporter\", name!=\"None\"}) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "已开始", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "执行成功的任务总数(累计值)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 9, + "y": 6 + }, + "id": 13, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_task_succeeded_total{job=\"fba_celery_exporter\", name!=\"None\"}) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "成功", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "执行失败的任务总数(累计值)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 12, + "y": 6 + }, + "id": 14, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_task_failed_total{job=\"fba_celery_exporter\", name!=\"None\"}) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "失败", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "重试的任务总数(累计值)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "orange", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 15, + "y": 6 + }, + "id": 15, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_task_retried_total{job=\"fba_celery_exporter\", name!=\"None\"}) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "重试", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "被撤销的任务总数(累计值)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 18, + "y": 6 + }, + "id": 16, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_task_revoked_total{job=\"fba_celery_exporter\", name!=\"None\"}) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "撤销", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "被拒绝的任务总数(累计值)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 21, + "y": 6 + }, + "id": 17, + "options": { + "colorMode": "background_solid", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(celery_task_rejected_total{job=\"fba_celery_exporter\", name!=\"None\"}) or vector(0)", + "legendFormat": "", + "refId": "A" + } + ], + "title": "拒绝", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 102, + "title": "📈 任务趋势", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "任务执行结果的速率趋势,包括成功、失败和重试", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "任务/秒", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "成功.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#27ae60", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "失败.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#e74c3c", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "重试.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#f39c12", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 20, + "options": { + "legend": { + "calcs": [ + "sum", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": true, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(name) (rate(celery_task_succeeded_total{job=\"fba_celery_exporter\", name!=\"None\"}[5m]))", + "legendFormat": "成功 - {{name}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(name) (rate(celery_task_failed_total{job=\"fba_celery_exporter\", name!=\"None\"}[5m]))", + "legendFormat": "失败 - {{name}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(name) (rate(celery_task_retried_total{job=\"fba_celery_exporter\", name!=\"None\"}[5m]))", + "legendFormat": "重试 - {{name}}", + "refId": "C" + } + ], + "title": "任务执行速率", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "任务发送、接收和开始执行的速率趋势", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "任务/秒", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "发送.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "接收.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#9b59b6", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "开始.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#f39c12", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 21, + "options": { + "legend": { + "calcs": [ + "sum", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": true, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(name) (rate(celery_task_sent_total{job=\"fba_celery_exporter\", name!=\"None\"}[5m]))", + "legendFormat": "发送 - {{name}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(name) (rate(celery_task_received_total{job=\"fba_celery_exporter\", name!=\"None\"}[5m]))", + "legendFormat": "接收 - {{name}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(name) (rate(celery_task_started_total{job=\"fba_celery_exporter\", name!=\"None\"}[5m]))", + "legendFormat": "开始 - {{name}}", + "refId": "C" + } + ], + "title": "任务流转速率", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 104, + "title": "📦 队列监控", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "各队列中等待处理的任务数量趋势", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "任务数", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#27ae60", + "value": null + }, + { + "color": "#f39c12", + "value": 50 + }, + { + "color": "#e74c3c", + "value": 100 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 19 + }, + "id": 40, + "options": { + "legend": { + "calcs": [ + "last", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "celery_queue_length{job=\"fba_celery_exporter\"}", + "legendFormat": "{{queue_name}}", + "refId": "A" + } + ], + "title": "队列长度趋势", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "各 Worker 当前处理的活动任务数量趋势", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "任务数", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 19 + }, + "id": 41, + "options": { + "legend": { + "calcs": [ + "last", + "max", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "celery_worker_tasks_active{job=\"fba_celery_exporter\"}", + "legendFormat": "{{hostname}}", + "refId": "A" + } + ], + "title": "Worker 活动任务趋势", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 103, + "title": "⏱️ 任务性能", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "各任务类型的平均执行时间,颜色从绿到红表示执行时间从短到长", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 5 + }, + { + "color": "red", + "value": 30 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 28 + }, + "id": 30, + "options": { + "displayMode": "lcd", + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "maxVizHeight": 300, + "minVizHeight": 16, + "minVizWidth": 8, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "sizing": "auto", + "text": { + "titleSize": 14 + }, + "valueMode": "color" + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "celery_task_runtime_sum{job=\"fba_celery_exporter\", name!=\"None\"} / celery_task_runtime_count{job=\"fba_celery_exporter\", name!=\"None\"}", + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "title": "任务平均执行时间", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "成功任务按类型的分布情况", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 28 + }, + "id": 31, + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(name) (celery_task_succeeded_total{job=\"fba_celery_exporter\", name!=\"None\"})", + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "title": "成功任务分布", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "失败任务按类型的分布情况", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 28 + }, + "id": 32, + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(name) (celery_task_failed_total{job=\"fba_celery_exporter\", name!=\"None\"})", + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "title": "失败任务分布", + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 36 + }, + "id": 105, + "title": "📊 性能分析", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "各任务类型的执行时间分布(P50/P90/P99)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "执行时间 (秒)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 37 + }, + "id": 50, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum by(name, le) (rate(celery_task_runtime_bucket{job=\"fba_celery_exporter\", name!=\"None\"}[5m])))", + "legendFormat": "P50 - {{name}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.9, sum by(name, le) (rate(celery_task_runtime_bucket{job=\"fba_celery_exporter\", name!=\"None\"}[5m])))", + "legendFormat": "P90 - {{name}}", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by(name, le) (rate(celery_task_runtime_bucket{job=\"fba_celery_exporter\", name!=\"None\"}[5m])))", + "legendFormat": "P99 - {{name}}", + "refId": "C" + } + ], + "title": "任务执行时间分布 (P50/P90/P99)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "每分钟完成的任务数量(吞吐量)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "任务/分钟", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "成功.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#27ae60", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "失败.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#e74c3c", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 37 + }, + "id": 51, + "options": { + "legend": { + "calcs": [ + "sum", + "mean" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(celery_task_succeeded_total{job=\"fba_celery_exporter\", name!=\"None\"}[1m])) by (name)", + "legendFormat": "成功 - {{name}}", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(celery_task_failed_total{job=\"fba_celery_exporter\", name!=\"None\"}[1m])) by (name)", + "legendFormat": "失败 - {{name}}", + "refId": "B" + } + ], + "title": "任务吞吐量 (每分钟)", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "5s", + "schemaVersion": 41, + "tags": [ + "celery", + "fba_celery" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "title": "FastAPI Best Architecture Celery", + "uid": "fba_celery", + "version": 1 +} diff --git a/deploy/backend/grafana/dashboards/fba_server.json b/deploy/backend/grafana/dashboards/fba_server.json new file mode 100644 index 00000000..02ef1f75 --- /dev/null +++ b/deploy/backend/grafana/dashboards/fba_server.json @@ -0,0 +1,1892 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "注释与警报", + "type": "dashboard" + } + ] + }, + "description": "FastAPI Best Architecture 应用监控仪表盘 - 实时监控 API 请求、响应时间、错误率和系统日志", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "celery" + ], + "targetBlank": true, + "title": "Celery 监控", + "type": "dashboards" + } + ], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 100, + "title": "📊 服务概览", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "过去24小时内所有 API 端点的总请求数量", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#3498DB", + "value": null + }, + { + "color": "#2980B9", + "value": 1000 + }, + { + "color": "#1F618D", + "value": 10000 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "colorMode": "background_solid", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(fba_request_total{app_name=\"$app_name\", path!=\"/metrics\"})", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": "24h", + "title": "总请求数", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "当前每秒处理的请求数量,反映系统实时负载情况", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#2ECC71", + "value": null + }, + { + "color": "#27AE60", + "value": 50 + }, + { + "color": "#1E8449", + "value": 100 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "background_solid", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(fba_request_total{app_name=\"$app_name\", path!=\"/metrics\"}[5m]))", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "title": "当前 QPS", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "2xx 成功响应占总响应的百分比,反映服务健康程度。低于 95% 需要关注", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 2, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#E74C3C", + "value": null + }, + { + "color": "#F39C12", + "value": 0.9 + }, + { + "color": "#2ECC71", + "value": 0.95 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 8, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "background_solid", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(fba_response_total{app_name=\"$app_name\", status_code=~\"2.*\", path!=\"/metrics\"}) / sum(fba_response_total{app_name=\"$app_name\", path!=\"/metrics\"})", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "title": "成功率", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "过去24小时内捕获的异常总数,包括所有类型的错误", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#2ECC71", + "value": null + }, + { + "color": "#F39C12", + "value": 10 + }, + { + "color": "#E74C3C", + "value": 50 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 12, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "background_solid", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(fba_exception_total{app_name=\"$app_name\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": "24h", + "title": "异常总数", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "所有请求的平均响应时间(毫秒),反映整体性能", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#2ECC71", + "value": null + }, + { + "color": "#F39C12", + "value": 100 + }, + { + "color": "#E74C3C", + "value": 500 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 16, + "y": 1 + }, + "id": 5, + "options": { + "colorMode": "background_solid", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(fba_request_cost_time_sum{app_name=\"$app_name\", path!=\"/metrics\"}) / sum(fba_request_cost_time_count{app_name=\"$app_name\", path!=\"/metrics\"})", + "legendFormat": "", + "refId": "A" + } + ], + "title": "平均响应时间", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "当前正在处理中的请求数量,反映系统并发情况", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#27AE60", + "value": null + }, + { + "color": "#D68910", + "value": 10 + }, + { + "color": "#C0392B", + "value": 50 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 6, + "options": { + "colorMode": "background_solid", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(fba_request_in_progress{app_name=\"$app_name\", path!=\"/metrics\"}) or vector(0)", + "instant": true, + "legendFormat": "", + "refId": "A" + } + ], + "title": "进行中请求", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 101, + "title": "📈 请求趋势", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "每秒请求数的时间序列图,按 API 路径分组显示,用于观察流量模式和峰值。Mean=时间范围内平均值,Max=最大值", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "请求/秒", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 6 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "width": 300 + }, + "tooltip": { + "hideZeros": true, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(fba_request_total{app_name=\"$app_name\", path!=\"/metrics\"}[5m])", + "legendFormat": "{{method}} {{path}}", + "refId": "A" + } + ], + "title": "请求速率 (QPS)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "当前正在处理的请求数量趋势,用于监控系统并发负载。Mean=平均并发数,Max=峰值并发数", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "请求数", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 6 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "width": 300 + }, + "tooltip": { + "hideZeros": true, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "fba_request_in_progress{app_name=\"$app_name\", path!=\"/metrics\"}", + "legendFormat": "{{method}} {{path}}", + "refId": "A" + } + ], + "title": "进行中请求趋势", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 103, + "title": "✅ 响应状态", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "2xx 成功响应占比趋势,绿色区域表示健康(>95%),红色区域表示需要关注(<80%)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "成功率", + "axisPlacement": "auto", + "axisSoftMax": 1, + "axisSoftMin": 0, + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 0.8 + }, + { + "color": "green", + "value": 0.95 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 0, + "y": 17 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "mean", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(path) (fba_response_total{app_name=\"$app_name\", status_code=~\"2.*\", path!=\"/metrics\"}) / sum by(path) (fba_response_total{app_name=\"$app_name\", path!=\"/metrics\"})", + "legendFormat": "{{path}}", + "refId": "A" + } + ], + "title": "2xx 成功率趋势", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "5xx 服务器错误占比趋势,绿色区域表示健康(<1%),红色区域表示严重问题(>10%)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "错误率", + "axisPlacement": "auto", + "axisSoftMax": 0.2, + "axisSoftMin": 0, + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.01 + }, + { + "color": "red", + "value": 0.1 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 8, + "y": 17 + }, + "id": 31, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(path) (fba_response_total{app_name=\"$app_name\", status_code=~\"5.*\", path!=\"/metrics\"}) / sum by(path) (fba_response_total{app_name=\"$app_name\", path!=\"/metrics\"})", + "legendFormat": "{{path}}", + "refId": "A" + } + ], + "title": "5xx 错误率趋势", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "各 API 端点的请求数量分布,显示哪些端点最繁忙", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 17 + }, + "id": 32, + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum by(path) (fba_request_total{app_name=\"$app_name\", path!=\"/metrics\"})", + "legendFormat": "{{path}}", + "refId": "A" + } + ], + "title": "请求分布", + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 102, + "title": "⏱️ 响应时间", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "各 API 端点的平均响应时间,颜色从绿到红表示响应时间从快到慢", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 200 + } + ] + }, + "unit": "ms" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "方法" + }, + "properties": [ + { + "id": "custom.filterable", + "value": true + }, + { + "id": "custom.width", + "value": 80 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "接口" + }, + "properties": [ + { + "id": "custom.width", + "value": 800 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "平均响应时间" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "lcd", + "type": "gauge", + "valueDisplayMode": "text" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 20, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "mean" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "平均响应时间" + } + ] + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "fba_request_cost_time_sum{app_name=\"$app_name\", path!=\"/metrics\"} / fba_request_cost_time_count{app_name=\"$app_name\", path!=\"/metrics\"}", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "平均响应时间", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "__name__": true, + "app_name": true, + "instance": true, + "job": true + }, + "indexByName": {}, + "renameByName": { + "Value": "平均响应时间", + "method": "方法", + "path": "接口" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "99% 的请求在此时间内完成(P99 延迟),用于评估最差情况下的用户体验。Mean=平均P99,Max=最大P99", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "延迟 (ms)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 100 + }, + { + "color": "red", + "value": 500 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 26 + }, + "id": 21, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": true, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(fba_request_cost_time_bucket{app_name=\"$app_name\", path!=\"/metrics\"}[5m])) by(path, le))", + "legendFormat": "{{path}}", + "refId": "A" + } + ], + "title": "P99 响应时间", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "95% 的请求在此时间内完成(P95 延迟),用于评估大多数用户的体验。Mean=平均P95,Max=最大P95", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "延迟 (ms)", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "line+area" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 200 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 26 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": true, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(fba_request_cost_time_bucket{app_name=\"$app_name\", path!=\"/metrics\"}[5m])) by(path, le))", + "legendFormat": "{{path}}", + "refId": "A" + } + ], + "title": "P95 响应时间", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 104, + "title": "🔍 链路追踪", + "type": "row" + }, + { + "datasource": { + "type": "tempo", + "uid": "tempo" + }, + "description": "FastAPI 服务的分布式追踪数据,点击 Trace ID 可跳转到 Explore 查看完整调用链,支持通过顶部「接口筛选」变量过滤", + "fieldConfig": { + "defaults": { + "custom": { + "align": "left", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Service" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Trace ID" + }, + "properties": [ + { + "id": "custom.width", + "value": 350 + }, + { + "id": "custom.cellOptions", + "value": { + "type": "color-text" + } + }, + { + "id": "color", + "value": { + "fixedColor": "#6E9FFF", + "mode": "fixed" + } + }, + { + "id": "links", + "value": [ + { + "title": "查看调用链", + "url": "/explore?orgId=1&left={\"datasource\":\"tempo\",\"queries\":[{\"refId\":\"A\",\"queryType\":\"traceql\",\"query\":\"${__value.raw}\"}],\"range\":{\"from\":\"now-1h\",\"to\":\"now\"}}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Start time" + }, + "properties": [ + { + "id": "custom.width", + "value": 200 + }, + { + "id": "displayName", + "value": "时间" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Name" + }, + "properties": [ + { + "id": "displayName", + "value": "接口" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Duration" + }, + "properties": [ + { + "id": "custom.width", + "value": 120 + }, + { + "id": "displayName", + "value": "耗时" + }, + { + "id": "custom.cellOptions", + "value": { + "type": "gauge", + "mode": "gradient", + "valueDisplayMode": "text" + } + }, + { + "id": "color", + "value": { + "mode": "continuous-GrYlRd" + } + }, + { + "id": "max", + "value": 500 + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 40, + "options": { + "showHeader": true, + "cellHeight": "sm", + "footer": { + "show": false + } + }, + "transformations": [ + { + "id": "convertFieldType", + "options": { + "conversions": [ + { + "targetField": "Start time", + "destinationType": "time" + } + ] + } + }, + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "field": "Start time", + "desc": true + } + ] + } + } + ], + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "tempo", + "uid": "tempo" + }, + "queryType": "traceql", + "query": "{ span.http.method != \"OPTIONS\" && span.http.target !~ \"/metrics.*\" && span.http.target !~ \"/ws.*\" && trace:id =~ \".*${trace_id}.*\" }", + "limit": 20, + "refId": "A", + "tableType": "traces" + } + ], + "title": "服务 Traces", + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 45 + }, + "id": 105, + "title": "📝 应用日志", + "type": "row" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "按级别统计日志产生速率,ERROR/CRITICAL 级别增加需立即关注", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "日志/秒", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "DEBUG" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#85C1E9", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "INFO" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#52BE80", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "WARNING" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#F39C12", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "ERROR" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#E74C3C", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "CRITICAL" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#8E44AD", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 12, + "w": 8, + "x": 0, + "y": 46 + }, + "id": 50, + "options": { + "legend": { + "calcs": [ + "sum" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": true, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "code", + "expr": "(rate({service_name=\"fba_server\"} |= \"$trace_id\" [1m]))", + "legendFormat": "{{level}}", + "refId": "A" + } + ], + "title": "日志级别统计", + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "实时应用日志流,支持通过顶部「日志搜索」变量过滤", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 16, + "x": 8, + "y": 46 + }, + "id": 51, + "options": { + "dedupStrategy": "none", + "enableInfiniteScrolling": true, + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "code", + "expr": "{service_name=\"fba_server\"} | json | line_format \"{{.body}}\" |= \"$trace_id\"", + "refId": "A" + } + ], + "title": "实时日志", + "type": "logs" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "仅显示 ERROR 和 CRITICAL 级别日志,支持通过「日志搜索」变量过滤", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 58 + }, + "id": 52, + "options": { + "dedupStrategy": "none", + "enableInfiniteScrolling": true, + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": false, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "code", + "expr": "{service_name=\"fba_server\"} | json | level=~\"ERROR|CRITICAL\" | line_format \"{{.body}}\" |= \"$trace_id\"", + "refId": "A" + } + ], + "title": "错误日志", + "type": "logs" + } + ], + "preload": false, + "refresh": "5s", + "schemaVersion": 41, + "tags": [ + "fastapi", + "fba_server" + ], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": "fba 服务", + "value": "fba_server" + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(fba_app_info, app_name)", + "description": "数据来自哪个应用", + "label": "应用名称", + "name": "app_name", + "query": { + "qryType": 1, + "query": "label_values(fba_app_info, app_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": { + "text": "", + "value": "" + }, + "description": "输入 Trace ID 同时搜索 Traces 和日志", + "label": "Trace ID", + "name": "trace_id", + "options": [ + { + "selected": true, + "text": "", + "value": "" + } + ], + "query": "", + "type": "textbox" + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "title": "FastAPI Best Architecture", + "uid": "fba_server", + "version": 1 +} diff --git a/deploy/backend/grafana/fba_config.alloy b/deploy/backend/grafana/fba_config.alloy new file mode 100644 index 00000000..bba9ef2e --- /dev/null +++ b/deploy/backend/grafana/fba_config.alloy @@ -0,0 +1,158 @@ +// ============================================================================ +// Grafana Alloy 配置文件 +// Alloy 是 Grafana 的可观测性数据收集器(原 Grafana Agent) +// 官方文档: https://grafana.com/docs/alloy/latest/ +// +// 数据流向: +// 应用程序 -> OTLP Receiver -> Batch Processor -> Exporters -> 后端存储 +// - 日志 (Logs) -> Loki +// - 指标 (Metrics) -> Prometheus +// - 链路 (Traces) -> Tempo +// ============================================================================ + +// OTLP 接收器配置 +// OpenTelemetry Protocol (OTLP) 是 OpenTelemetry 的标准数据传输协议 +// 支持同时接收日志、指标和链路追踪数据 +otelcol.receiver.otlp "default" { + // gRPC 协议配置 + // gRPC 是高性能的 RPC 框架,适用于服务间通信 + grpc { + endpoint = "0.0.0.0:4317" + // 0.0.0.0 表示监听所有网络接口 + // 4317 是 OTLP gRPC 的标准端口 + // 应用程序通过此端口发送遥测数据 + } + + // HTTP 协议配置 + // HTTP 协议更通用,适用于无法使用 gRPC 的场景 + http { + endpoint = "0.0.0.0:4318" + // 4318 是 OTLP HTTP 的标准端口 + // 支持 JSON 和 Protobuf 格式的数据 + } + + // 输出配置 + // 定义接收到的数据发送到哪些处理器 + output { + logs = [otelcol.processor.batch.default.input] + metrics = [otelcol.processor.batch.default.input] + traces = [otelcol.processor.batch.default.input] + } +} + +// 批处理器配置 +// 将多个遥测数据项合并成批次,提高传输效率 +otelcol.processor.batch "default" { + // 输出配置 + // 定义处理后的数据发送到哪些导出器 + output { + logs = [otelcol.exporter.loki.default.input] + metrics = [otelcol.exporter.prometheus.default.input] + traces = [otelcol.exporter.otlp.tempo.input] + } +} + +// Loki 导出器配置 +// 将日志数据发送到 Grafana Loki +otelcol.exporter.loki "default" { + forward_to = [loki.write.loki_server.receiver] +} + +// Loki 写入组件 +// 负责将日志数据推送到 Loki 服务器 +loki.write "loki_server" { + endpoint { + url = "http://fba_loki:3100/loki/api/v1/push" + } +} + +// Prometheus 导出器配置 +// 将指标数据发送到 Prometheus +otelcol.exporter.prometheus "default" { + forward_to = [prometheus.remote_write.prom_server.receiver] +} + +// Prometheus 远程写入组件 +// 负责将指标数据推送到 Prometheus 服务器 +prometheus.remote_write "prom_server" { + endpoint { + url = "http://fba_prometheus:9090/api/v1/write" + } +} + +// Prometheus 自身指标抓取 +// 监控 Prometheus 服务器本身的运行状态 +prometheus.scrape "prometheus" { + targets = [{ + __address__ = "fba_prometheus:9090", + }] + forward_to = [prometheus.remote_write.prom_server.receiver] + // 抓取的指标发送到远程写入组件 + + job_name = "fba_prometheus" + // 任务名称 + // 将作为 job 标签添加到所有抓取的指标上 + + scrape_interval = "15s" + // 抓取间隔 +} + +// FastAPI 后端服务指标抓取 +// 监控 FastAPI 应用的性能和业务指标 +prometheus.scrape "fba_server" { + targets = [{ + __address__ = "fba_server:8001", + }] + forward_to = [prometheus.remote_write.prom_server.receiver] + // 抓取的指标发送到远程写入组件 + + job_name = "fba_server" + // 任务名称 + // 用于在 Prometheus 查询中区分不同服务 + + scrape_interval = "5s" + // 抓取间隔 + + scrape_timeout = "5s" + // 抓取超时时间 +} + +// Celery Exporter 指标抓取 +// 监控 Celery 异步任务队列的运行状态 +prometheus.scrape "fba_celery_exporter" { + targets = [{ + __address__ = "fba_celery_exporter:9808", + }] + forward_to = [prometheus.remote_write.prom_server.receiver] + // 抓取的指标发送到远程写入组件 + + job_name = "fba_celery_exporter" + // 任务名称 + // 用于标识 Celery 相关的指标 + + scrape_interval = "5s" + // 抓取间隔 + + scrape_timeout = "5s" + // 抓取超时时间 +} + +// Tempo 导出器配置 +// 将链路追踪数据发送到 Grafana Tempo +otelcol.exporter.otlp "tempo" { + client { + endpoint = "fba_tempo:4317" + + tls { + insecure = true + // 禁用 TLS 加密 + // 在 Docker 内部网络中通信时可以禁用 + // 生产环境建议启用 TLS + + insecure_skip_verify = true + // 跳过 TLS 证书验证 + // 仅在开发/测试环境使用 + // 生产环境应配置正确的证书 + } + } +} diff --git a/deploy/backend/grafana/fba_dashboards.yml b/deploy/backend/grafana/fba_dashboards.yml new file mode 100644 index 00000000..caceab30 --- /dev/null +++ b/deploy/backend/grafana/fba_dashboards.yml @@ -0,0 +1,36 @@ +# ============================================================================ +# Grafana 仪表盘 Provisioning 配置文件 +# 官方文档: https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards +# 用于自动加载和管理仪表盘,无需手动导入 +# ============================================================================ + +# API 版本号 +# 当前仅支持版本 1 +apiVersion: 1 + +# providers: 仪表盘提供者列表 +# 定义 Grafana 从哪里加载仪表盘以及如何管理它们 +providers: + - name: 'fba_server' + # 提供者名称,用于标识和日志记录 + + orgId: 1 + # 组织 ID + # 指定仪表盘属于哪个组织 + # 默认值: 1(默认组织) + + type: 'file' + # 提供者类型 + # 可选值: + # - file: 从本地文件系统加载仪表盘 JSON 文件 + + disableDeletion: true + # 禁止通过 UI 删除仪表盘 + + editable: false + # 是否允许通过 UI 编辑仪表盘 + + options: + path: '/etc/grafana/dashboards' + # 仪表盘 JSON 文件所在目录(容器内路径) + # Grafana 会自动扫描此目录下的所有 .json 文件 diff --git a/deploy/backend/grafana/fba_datasource.yml b/deploy/backend/grafana/fba_datasource.yml new file mode 100644 index 00000000..8bab48ca --- /dev/null +++ b/deploy/backend/grafana/fba_datasource.yml @@ -0,0 +1,164 @@ +# ============================================================================ +# Grafana 数据源 Provisioning 配置文件 +# 官方文档: https://grafana.com/docs/grafana/latest/administration/provisioning/#datasources +# 用于自动配置数据源,实现基础设施即代码 +# ============================================================================ + +# API 版本号 +# 当前仅支持版本 1 +apiVersion: 1 + +# datasources: 数据源列表 +# 定义 Grafana 连接的所有后端数据存储 +datasources: + # ========================================================================== + # Loki 数据源配置 + # Loki 是 Grafana 的日志聚合系统,类似于 Prometheus 但用于日志 + # 官方文档: https://grafana.com/docs/loki/latest/ + # ========================================================================== + - name: Loki + # 数据源显示名称,在 Grafana UI 中显示 + + uid: loki + # 数据源唯一标识符 + # 用于在仪表盘和告警规则中引用此数据源 + + type: loki + # 数据源类型 + # 必须与 Grafana 支持的数据源插件类型匹配 + + url: http://fba_loki:3100 + # Loki 服务器地址 + + isDefault: false + # 是否设为默认数据源 + + jsonData: + # 数据源特定的 JSON 配置 + + derivedFields: + # 派生字段配置 + # 用于从日志中提取字段并创建链接到其他数据源 + # 这是实现日志到链路追踪关联的关键配置 + + - datasourceUid: tempo + # 目标数据源的 UID + # 点击链接时将跳转到此数据源 + + matcherRegex: '"traceid":"([a-f0-9]{32})"' + # 正则表达式,用于从日志内容中提取 Trace ID + + matcherType: regex + # 匹配器类型 + # 可选值: regex(正则表达式) + + name: TraceID + # 派生字段的名称 + # 将显示在日志详情中 + + url: $${__value.raw} + # 链接 URL 模板 + # $${__value.raw} 表示提取的原始值 + + urlDisplayLabel: 查看 Trace + # 链接显示的文本标签 + + # ========================================================================== + # Prometheus 数据源配置 + # Prometheus 是云原生监控系统,用于收集和存储时序指标数据 + # 官方文档: https://prometheus.io/docs/ + # ========================================================================== + - name: Prometheus + # 数据源显示名称 + + uid: prometheus + # 数据源唯一标识符 + + type: prometheus + # 数据源类型 + + url: http://fba_prometheus:9090 + # Prometheus 服务器地址 + + isDefault: true + # 设为默认数据源 + + jsonData: + httpMethod: POST + # 查询使用的 HTTP 方法 + + exemplarTraceIdDestinations: + # Exemplar(范例)配置 + # Exemplar 是指标数据点关联的 Trace ID + # 用于从指标跳转到对应的链路追踪 + + - name: TraceID + # Exemplar 中 Trace ID 的字段名称 + + datasourceUid: tempo + # 目标 Tempo 数据源的 UID + # 点击 Exemplar 时将跳转到此数据源查看 Trace + + # ========================================================================== + # Tempo 数据源配置 + # Tempo 是 Grafana 的分布式链路追踪后端 + # 官方文档: https://grafana.com/docs/tempo/latest/ + # ========================================================================== + - name: Tempo + # 数据源显示名称 + + uid: tempo + # 数据源唯一标识符 + + type: tempo + # 数据源类型 + + url: http://fba_tempo:3200 + # Tempo 服务器地址 + + jsonData: + nodeGraph: + enabled: true + # 启用节点图功能 + # 可视化展示服务之间的调用关系图 + + tracesToLogsV2: + # Trace 到日志的关联配置(V2 版本) + # 允许从 Trace 详情页面跳转到相关日志 + + datasourceUid: loki + # 日志数据源的 UID + + filterByTraceID: true + # 是否按 Trace ID 过滤日志 + + spanStartTimeShift: "-1m" + # Span 开始时间偏移 + # 用于捕获 Span 开始前的相关日志 + + spanEndTimeShift: "1m" + # Span 结束时间偏移 + # 用于捕获 Span 结束后的相关日志 + + customQuery: true + # 启用自定义查询 + # 允许使用下面的 query 字段自定义日志查询语句 + + query: '{service_name="fba_server"} | json | traceid="${__span.traceId}"' + # 自定义 LogQL 查询语句 + + search: + hide: false + # 是否隐藏搜索功能 + + serviceMap: + datasourceUid: prometheus + # 服务地图数据源 + # 使用 Prometheus 中的指标数据生成服务依赖图 + # 需要 Tempo 的 metrics_generator 功能配合 + + lokiSearch: + datasourceUid: loki + # Loki 搜索数据源 + # 允许在 Tempo 中使用 Loki 进行日志搜索 + # 实现基于日志内容查找 Trace 的功能 diff --git a/deploy/backend/grafana/fba_grafana.ini b/deploy/backend/grafana/fba_grafana.ini new file mode 100644 index 00000000..789f8e94 --- /dev/null +++ b/deploy/backend/grafana/fba_grafana.ini @@ -0,0 +1,40 @@ +# ============================================================================ +# Grafana 主配置文件 +# 官方文档: https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/ +# ============================================================================ + +# [server] 服务器配置 +# 用于配置 Grafana HTTP 服务器的基本设置 +[server] +# 协议类型 +protocol = http + +# HTTP 服务监听端口 +http_port = 3000 + +# [security] 安全配置 +# 用于配置 Grafana 安全相关的默认设置 +[security] +# 默认 Grafana 管理员用户名 +admin_user = admin + +# 默认 Grafana 管理员密码 +admin_password = 123456 + +# 如果您将 Grafana 托管在 HTTPS +;cookie_secure = true + +# [users] 用户配置 +# 用于配置用户相关的默认设置 +[users] +# 默认 UI 主题 +default_theme=system + +# 默认界面语言 +default_language = zh-Hans + +# [dashboards] 仪表盘配置 +# 用于配置仪表盘相关的默认设置 +[dashboards] +# 默认首页仪表盘路径 +default_home_dashboard_path = /etc/grafana/dashboards/fba_server.json diff --git a/deploy/backend/grafana/fba_prometheus.yml b/deploy/backend/grafana/fba_prometheus.yml new file mode 100644 index 00000000..a4f5a5e7 --- /dev/null +++ b/deploy/backend/grafana/fba_prometheus.yml @@ -0,0 +1,13 @@ +# ============================================================================ +# Prometheus 配置文件 +# Prometheus 是云原生监控和告警系统,专注于时序指标数据 +# 官方文档: https://prometheus.io/docs/prometheus/latest/configuration/configuration/ +# ============================================================================ + +# global: 全局配置 +global: + scrape_interval: 15s + # 指标抓取间隔 + + evaluation_interval: 15s + # 规则评估间隔 diff --git a/deploy/backend/grafana/fba_tempo.yml b/deploy/backend/grafana/fba_tempo.yml new file mode 100644 index 00000000..e2fac950 --- /dev/null +++ b/deploy/backend/grafana/fba_tempo.yml @@ -0,0 +1,127 @@ +# ============================================================================ +# Tempo 配置文件 +# Tempo 是 Grafana 的高性能、低成本分布式链路追踪后端 +# 官方文档: https://grafana.com/docs/tempo/latest/configuration/ +# ============================================================================ + +# server: HTTP 服务器配置 +server: + http_listen_port: 3200 + # HTTP API 监听端口 + # 用于查询 Trace 数据和健康检查 + +# distributor: 分发器配置 +distributor: + receivers: + # 接收器配置 + + otlp: + # OpenTelemetry Protocol (OTLP) 接收器 + # OTLP 是 OpenTelemetry 的原生协议,推荐使用 + + protocols: + grpc: + endpoint: "0.0.0.0:4317" + # gRPC 协议端点 + + http: + endpoint: "0.0.0.0:4318" + # HTTP 协议端点 + +# storage: 存储配置 +storage: + trace: + backend: local + # 存储后端类型 + # 生产环境建议使用对象存储 + + wal: + path: /tmp/tempo/wal + # Write-Ahead Log (WAL) 目录 + # WAL 用于在数据写入存储前的临时缓存 + # 确保数据不会因意外重启而丢失 + # 注意: 生产环境应使用持久化存储路径 + + local: + path: /tmp/tempo/blocks + # 本地存储的数据块目录 + # 存储压缩后的 Trace 数据块 + # 仅在 backend: local 时使用 + +# metrics_generator: 指标生成器配置 +# 从 Trace 数据自动生成 Prometheus 指标 +# 这是实现 Trace 到 Metrics 关联的关键功能 +metrics_generator: + registry: + external_labels: + source: tempo + # 外部标签 + # 添加到所有生成的指标上 + + storage: + path: /tmp/tempo/generator/wal + # 指标生成器的 WAL 目录 + # 用于临时存储待发送的指标数据 + + remote_write: + # 远程写入配置 + + - url: http://fba_prometheus:9090/api/v1/write + # Prometheus 远程写入 API 地址 + # 需要 Prometheus 启用 remote-write-receiver 功能 + + send_exemplars: true + # 发送 Exemplar 数据 + # Exemplar 将指标数据点与 Trace ID 关联 + # 实现从指标图表点击跳转到对应 Trace + + traces_storage: + path: /tmp/tempo/generator/traces + # Trace 数据临时存储路径 + # 用于指标生成器处理 Trace 数据 + + processor: + # 处理器配置 + # 定义如何从 Trace 生成指标 + + span_metrics: + # Span 指标处理器 + + dimensions: + # 指标维度(标签) + # 这些属性将作为 Prometheus 指标的标签 + + - service.name + - http.method + - http.target + - http.status_code + + service_graphs: + # 服务图处理器 + # 生成服务间调用关系的指标 + # 用于 Grafana 中的服务地图可视化 + + dimensions: + - service.name + + local_blocks: + # 本地块处理器 + # 处理本地存储的 Trace 数据块 + + flush_to_storage: true + # 是否将处理后的数据刷新到存储 + +# overrides: 覆盖配置 +overrides: + defaults: + # 默认配置 + # 适用于所有租户(单租户模式下为全局配置) + + metrics_generator: + processors: + # 启用的指标处理器列表 + # 只有在此列表中的处理器才会运行 + + - span-metrics + - service-graphs + - local-blocks diff --git a/deploy/backend/fba_celery_beat.conf b/deploy/backend/supervisor/fba_celery_beat.conf similarity index 100% rename from deploy/backend/fba_celery_beat.conf rename to deploy/backend/supervisor/fba_celery_beat.conf diff --git a/deploy/backend/fba_celery_flower.conf b/deploy/backend/supervisor/fba_celery_flower.conf similarity index 100% rename from deploy/backend/fba_celery_flower.conf rename to deploy/backend/supervisor/fba_celery_flower.conf diff --git a/deploy/backend/fba_celery_worker.conf b/deploy/backend/supervisor/fba_celery_worker.conf similarity index 100% rename from deploy/backend/fba_celery_worker.conf rename to deploy/backend/supervisor/fba_celery_worker.conf diff --git a/deploy/backend/fba_server.conf b/deploy/backend/supervisor/fba_server.conf similarity index 100% rename from deploy/backend/fba_server.conf rename to deploy/backend/supervisor/fba_server.conf diff --git a/deploy/backend/supervisord.conf b/deploy/backend/supervisor/supervisord.conf similarity index 100% rename from deploy/backend/supervisord.conf rename to deploy/backend/supervisor/supervisord.conf diff --git a/docker-compose.yml b/docker-compose.yml index bc382358..159525e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,8 @@ volumes: name: fba_static_upload fba_rabbitmq: name: fba_rabbitmq + fba_prometheus_data: + name: fba_prometheus_data services: fba_server: @@ -30,6 +32,8 @@ services: - "8001:8001" container_name: fba_server restart: always + environment: + - TZ=Asia/Shanghai # 如果你是 mysql 用户,应将 fba_postgres 修改为 fba_mysql depends_on: - fba_postgres @@ -176,6 +180,8 @@ services: # 如果你需要分布式部署 Worker,则必须移除此 container_name 配置 container_name: fba_celery_worker restart: always + environment: + - TZ=Asia/Shanghai depends_on: - fba_rabbitmq volumes: @@ -199,6 +205,8 @@ services: image: fba_celery_beat:latest container_name: fba_celery_beat restart: always + environment: + - TZ=Asia/Shanghai depends_on: - fba_rabbitmq - fba_celery_worker @@ -225,6 +233,8 @@ services: - "8555:8555" container_name: fba_celery_flower restart: always + environment: + - TZ=Asia/Shanghai depends_on: - fba_rabbitmq - fba_celery_worker @@ -239,3 +249,100 @@ services: wait-for-it -s fba_rabbitmq:5672 -t 300 supervisord -c /etc/supervisor/supervisord.conf supervisorctl restart + + fba_celery_exporter: + image: danihodovic/celery-exporter + restart: always + ports: + - "9808:9808" + container_name: fba_celery_exporter + environment: + - CE_BROKER_URL=amqp://guest:guest@fba_rabbitmq:5672// + depends_on: + - fba_rabbitmq + networks: + - fba_network + + fba_loki: + image: grafana/loki + restart: always + ports: + - "3100:3100" + container_name: fba_loki + networks: + - fba_network + command: -config.file=/etc/loki/local-config.yaml + + fba_prometheus: + image: prom/prometheus + restart: always + ports: + - "9090:9090" + container_name: fba_prometheus + depends_on: + - fba_loki + volumes: + - ./deploy/backend/grafana/fba_prometheus.yml:/workspace/fba_prometheus.yml + - fba_prometheus_data:/prometheus + networks: + - fba_network + command: + - --web.enable-remote-write-receiver + - --enable-feature=exemplar-storage + - --config.file=/workspace/fba_prometheus.yml + + fba_alloy: + image: grafana/alloy + restart: always + ports: + - "4317:4317" + - "4318:4318" + - "12345:12345" + container_name: fba_alloy + depends_on: + - fba_loki + volumes: + - ./deploy/backend/grafana/fba_config.alloy:/etc/alloy/fba_config.alloy + networks: + - fba_network + command: + - run + - /etc/alloy/fba_config.alloy + - --server.http.listen-addr=0.0.0.0:12345 + + fba_tempo: + image: grafana/tempo:latest + restart: always + ports: + - "3200:3200" + expose: + - 4317 + - 4318 + container_name: fba_tempo + depends_on: + - fba_loki + volumes: + - ./deploy/backend/grafana/fba_tempo.yml:/etc/tempo/fba_tempo.yml + networks: + - fba_network + command: + - -config.file=/etc/tempo/fba_tempo.yml + - -auth.enabled=false + + fba_grafana: + image: grafana/grafana + restart: always + ports: + - "3000:3000" + container_name: fba_grafana + depends_on: + - fba_loki + - fba_prometheus + volumes: + - ./deploy/backend/grafana/fba_grafana.ini:/etc/grafana/grafana.ini + - ./deploy/backend/grafana/dashboards/fba_celery.json:/etc/grafana/dashboards/fba_celery.json + - ./deploy/backend/grafana/dashboards/fba_server.json:/etc/grafana/dashboards/fba_server.json + - ./deploy/backend/grafana/fba_dashboards.yml:/etc/grafana/provisioning/dashboards/fba_dashboards.yml + - ./deploy/backend/grafana/fba_datasource.yml:/etc/grafana/provisioning/datasources/fba_datasource.yml + networks: + - fba_network diff --git a/pyproject.toml b/pyproject.toml index 3ac56d51..cf8e37ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,12 @@ dependencies = [ "jinja2>=3.1.6", "loguru>=0.7.3", "msgspec>=0.20.0", + "opentelemetry-exporter-otlp-proto-grpc>=1.39.0", + "opentelemetry-instrumentation-fastapi>=0.60b0", + "opentelemetry-instrumentation-logging>=0.60b0", + "opentelemetry-instrumentation-sqlalchemy>=0.60b0", + "opentelemetry-sdk>=1.39.0", + "prometheus-client>=0.23.1", "psutil>=7.1.3", # https://github.com/fastapi-practices/fastapi_best_architecture/issues/887 "psycopg[binary]==3.2.10", diff --git a/requirements.txt b/requirements.txt index 1d190228..3dfcb899 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,9 @@ anyio==4.12.0 # starlette # watchfiles asgiref==3.11.0 - # via fastapi-best-architecture + # via + # fastapi-best-architecture + # opentelemetry-instrumentation-asgi async-timeout==5.0.1 ; python_full_version < '3.11.3' # via # asyncpg @@ -101,12 +103,16 @@ flower==2.0.1 # via fastapi-best-architecture gevent==25.9.1 # via fastapi-best-architecture +googleapis-common-protos==1.72.0 + # via opentelemetry-exporter-otlp-proto-grpc granian==2.6.0 # via fastapi-best-architecture greenlet==3.2.4 # via # gevent # sqlalchemy +grpcio==1.76.0 + # via opentelemetry-exporter-otlp-proto-grpc h11==0.16.0 # via # httpcore @@ -127,6 +133,8 @@ idna==3.11 # anyio # email-validator # httpx +importlib-metadata==8.7.0 + # via opentelemetry-api iniconfig==2.3.0 # via pytest ip2loc==1.0.0 @@ -153,9 +161,58 @@ mdurl==0.1.2 # via markdown-it-py msgspec==0.20.0 # via fastapi-best-architecture +opentelemetry-api==1.39.0 + # via + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-instrumentation + # opentelemetry-instrumentation-asgi + # opentelemetry-instrumentation-fastapi + # opentelemetry-instrumentation-logging + # opentelemetry-instrumentation-sqlalchemy + # opentelemetry-sdk + # opentelemetry-semantic-conventions +opentelemetry-exporter-otlp-proto-common==1.39.0 + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-exporter-otlp-proto-grpc==1.39.0 + # via fastapi-best-architecture +opentelemetry-instrumentation==0.60b0 + # via + # opentelemetry-instrumentation-asgi + # opentelemetry-instrumentation-fastapi + # opentelemetry-instrumentation-logging + # opentelemetry-instrumentation-sqlalchemy +opentelemetry-instrumentation-asgi==0.60b0 + # via opentelemetry-instrumentation-fastapi +opentelemetry-instrumentation-fastapi==0.60b0 + # via fastapi-best-architecture +opentelemetry-instrumentation-logging==0.60b0 + # via fastapi-best-architecture +opentelemetry-instrumentation-sqlalchemy==0.60b0 + # via fastapi-best-architecture +opentelemetry-proto==1.39.0 + # via + # opentelemetry-exporter-otlp-proto-common + # opentelemetry-exporter-otlp-proto-grpc +opentelemetry-sdk==1.39.0 + # via + # fastapi-best-architecture + # opentelemetry-exporter-otlp-proto-grpc +opentelemetry-semantic-conventions==0.60b0 + # via + # opentelemetry-instrumentation + # opentelemetry-instrumentation-asgi + # opentelemetry-instrumentation-fastapi + # opentelemetry-instrumentation-sqlalchemy + # opentelemetry-sdk +opentelemetry-util-http==0.60b0 + # via + # opentelemetry-instrumentation-asgi + # opentelemetry-instrumentation-fastapi packaging==25.0 # via # kombu + # opentelemetry-instrumentation + # opentelemetry-instrumentation-sqlalchemy # pytest pillow==12.0.0 # via fast-captcha @@ -163,9 +220,15 @@ pluggy==1.6.0 # via pytest prek==0.2.19 prometheus-client==0.23.1 - # via flower + # via + # fastapi-best-architecture + # flower prompt-toolkit==3.0.52 # via click-repl +protobuf==6.33.2 + # via + # googleapis-common-protos + # opentelemetry-proto psutil==7.1.3 # via fastapi-best-architecture psycopg==3.2.10 @@ -280,6 +343,11 @@ typing-extensions==4.15.0 # exceptiongroup # fastapi # fastapi-pagination + # grpcio + # opentelemetry-api + # opentelemetry-exporter-otlp-proto-grpc + # opentelemetry-sdk + # opentelemetry-semantic-conventions # psycopg # pydantic # pydantic-core @@ -328,8 +396,14 @@ websockets==15.0.1 # via uvicorn win32-setctime==1.2.0 ; sys_platform == 'win32' # via loguru +wrapt==1.17.3 + # via + # opentelemetry-instrumentation + # opentelemetry-instrumentation-sqlalchemy wsproto==1.3.2 # via simple-websocket +zipp==3.23.0 + # via importlib-metadata zope-event==6.1 # via gevent zope-interface==8.1.1 diff --git a/uv.lock b/uv.lock index 36b9d51a..1bbd82b4 100644 --- a/uv.lock +++ b/uv.lock @@ -704,6 +704,12 @@ dependencies = [ { name = "jinja2" }, { name = "loguru" }, { name = "msgspec" }, + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-instrumentation-fastapi" }, + { name = "opentelemetry-instrumentation-logging" }, + { name = "opentelemetry-instrumentation-sqlalchemy" }, + { name = "opentelemetry-sdk" }, + { name = "prometheus-client" }, { name = "psutil" }, { name = "psycopg", extra = ["binary"] }, { name = "pwdlib" }, @@ -758,6 +764,12 @@ requires-dist = [ { name = "jinja2", specifier = ">=3.1.6" }, { name = "loguru", specifier = ">=0.7.3" }, { name = "msgspec", specifier = ">=0.20.0" }, + { name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.39.0" }, + { name = "opentelemetry-instrumentation-fastapi", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-logging", specifier = ">=0.60b0" }, + { name = "opentelemetry-instrumentation-sqlalchemy", specifier = ">=0.60b0" }, + { name = "opentelemetry-sdk", specifier = ">=1.39.0" }, + { name = "prometheus-client", specifier = ">=0.23.1" }, { name = "psutil", specifier = ">=7.1.3" }, { name = "psycopg", extras = ["binary"], specifier = "==3.2.10" }, { name = "pwdlib", specifier = ">=0.3.0" }, @@ -901,6 +913,18 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/c2/69/a7c4ba2ffbc7c7dbf6d8b4f5d0f0a421f7815d229f4909854266c445a3d4/gevent-25.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:bb63c0d6cb9950cc94036a4995b9cc4667b8915366613449236970f4394f94d7" }, ] +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038" }, +] + [[package]] name = "granian" version = "2.6.0" @@ -1051,6 +1075,67 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01" }, ] +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/88/17/ff4795dc9a34b6aee6ec379f1b66438a3789cd1315aac0cbab60d92f74b3/grpcio-1.76.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:65a20de41e85648e00305c1bb09a3598f840422e522277641145a32d42dcefcc" }, + { url = "https://mirrors.aliyun.com/pypi/packages/4e/ff/35f9b96e3fa2f12e1dcd58a4513a2e2294a001d64dec81677361b7040c9a/grpcio-1.76.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:40ad3afe81676fd9ec6d9d406eda00933f218038433980aa19d401490e46ecde" }, + { url = "https://mirrors.aliyun.com/pypi/packages/3e/1c/8374990f9545e99462caacea5413ed783014b3b66ace49e35c533f07507b/grpcio-1.76.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:035d90bc79eaa4bed83f524331d55e35820725c9fbb00ffa1904d5550ed7ede3" }, + { url = "https://mirrors.aliyun.com/pypi/packages/1e/77/36fd7d7c75a6c12542c90a6d647a27935a1ecaad03e0ffdb7c42db6b04d2/grpcio-1.76.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4215d3a102bd95e2e11b5395c78562967959824156af11fa93d18fdd18050990" }, + { url = "https://mirrors.aliyun.com/pypi/packages/38/f7/e3cdb252492278e004722306c5a8935eae91e64ea11f0af3437a7de2e2b7/grpcio-1.76.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49ce47231818806067aea3324d4bf13825b658ad662d3b25fada0bdad9b8a6af" }, + { url = "https://mirrors.aliyun.com/pypi/packages/7e/20/340db7af162ccd20a0893b5f3c4a5d676af7b71105517e62279b5b61d95a/grpcio-1.76.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8cc3309d8e08fd79089e13ed4819d0af72aa935dd8f435a195fd152796752ff2" }, + { url = "https://mirrors.aliyun.com/pypi/packages/10/f0/b2160addc1487bd8fa4810857a27132fb4ce35c1b330c2f3ac45d697b106/grpcio-1.76.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:971fd5a1d6e62e00d945423a567e42eb1fa678ba89072832185ca836a94daaa6" }, + { url = "https://mirrors.aliyun.com/pypi/packages/2c/2c/ac6f98aa113c6ef111b3f347854e99ebb7fb9d8f7bb3af1491d438f62af4/grpcio-1.76.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d9adda641db7207e800a7f089068f6f645959f2df27e870ee81d44701dd9db3" }, + { url = "https://mirrors.aliyun.com/pypi/packages/90/84/7852f7e087285e3ac17a2703bc4129fafee52d77c6c82af97d905566857e/grpcio-1.76.0-cp310-cp310-win32.whl", hash = "sha256:063065249d9e7e0782d03d2bca50787f53bd0fb89a67de9a7b521c4a01f1989b" }, + { url = "https://mirrors.aliyun.com/pypi/packages/10/30/d3d2adcbb6dd3ff59d6ac3df6ef830e02b437fb5c90990429fd180e52f30/grpcio-1.76.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6ae758eb08088d36812dd5d9af7a9859c05b1e0f714470ea243694b49278e7b" }, + { url = "https://mirrors.aliyun.com/pypi/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a" }, + { url = "https://mirrors.aliyun.com/pypi/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c" }, + { url = "https://mirrors.aliyun.com/pypi/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465" }, + { url = "https://mirrors.aliyun.com/pypi/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48" }, + { url = "https://mirrors.aliyun.com/pypi/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da" }, + { url = "https://mirrors.aliyun.com/pypi/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397" }, + { url = "https://mirrors.aliyun.com/pypi/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749" }, + { url = "https://mirrors.aliyun.com/pypi/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00" }, + { url = "https://mirrors.aliyun.com/pypi/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054" }, + { url = "https://mirrors.aliyun.com/pypi/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d" }, + { url = "https://mirrors.aliyun.com/pypi/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8" }, + { url = "https://mirrors.aliyun.com/pypi/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280" }, + { url = "https://mirrors.aliyun.com/pypi/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4" }, + { url = "https://mirrors.aliyun.com/pypi/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11" }, + { url = "https://mirrors.aliyun.com/pypi/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6" }, + { url = "https://mirrors.aliyun.com/pypi/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8" }, + { url = "https://mirrors.aliyun.com/pypi/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980" }, + { url = "https://mirrors.aliyun.com/pypi/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882" }, + { url = "https://mirrors.aliyun.com/pypi/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958" }, + { url = "https://mirrors.aliyun.com/pypi/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347" }, + { url = "https://mirrors.aliyun.com/pypi/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2" }, + { url = "https://mirrors.aliyun.com/pypi/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468" }, + { url = "https://mirrors.aliyun.com/pypi/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3" }, + { url = "https://mirrors.aliyun.com/pypi/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb" }, + { url = "https://mirrors.aliyun.com/pypi/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae" }, + { url = "https://mirrors.aliyun.com/pypi/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77" }, + { url = "https://mirrors.aliyun.com/pypi/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03" }, + { url = "https://mirrors.aliyun.com/pypi/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42" }, + { url = "https://mirrors.aliyun.com/pypi/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f" }, + { url = "https://mirrors.aliyun.com/pypi/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8" }, + { url = "https://mirrors.aliyun.com/pypi/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62" }, + { url = "https://mirrors.aliyun.com/pypi/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd" }, + { url = "https://mirrors.aliyun.com/pypi/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc" }, + { url = "https://mirrors.aliyun.com/pypi/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a" }, + { url = "https://mirrors.aliyun.com/pypi/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba" }, + { url = "https://mirrors.aliyun.com/pypi/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09" }, + { url = "https://mirrors.aliyun.com/pypi/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc" }, + { url = "https://mirrors.aliyun.com/pypi/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc" }, + { url = "https://mirrors.aliyun.com/pypi/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e" }, + { url = "https://mirrors.aliyun.com/pypi/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1235,6 +1320,18 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea" }, ] +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -1614,6 +1711,173 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.39.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/c0/0b/e5428c009d4d9af0515b0a8371a8aaae695371af291f45e702f7969dce6b/opentelemetry_api-1.39.0.tar.gz", hash = "sha256:6130644268c5ac6bdffaf660ce878f10906b3e789f7e2daa5e169b047a2933b9" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/05/85/d831a9bc0a9e0e1a304ff3d12c1489a5fbc9bf6690a15dcbdae372bbca45/opentelemetry_api-1.39.0-py3-none-any.whl", hash = "sha256:3c3b3ca5c5687b1b5b37e5c5027ff68eacea8675241b29f13110a8ffbb8f0459" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.39.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/11/cb/3a29ce606b10c76d413d6edd42d25a654af03e73e50696611e757d2602f3/opentelemetry_exporter_otlp_proto_common-1.39.0.tar.gz", hash = "sha256:a135fceed1a6d767f75be65bd2845da344dd8b9258eeed6bc48509d02b184409" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/ef/c6/215edba62d13a3948c718b289539f70e40965bc37fc82ecd55bb0b749c1a/opentelemetry_exporter_otlp_proto_common-1.39.0-py3-none-any.whl", hash = "sha256:3d77be7c4bdf90f1a76666c934368b8abed730b5c6f0547a2ec57feb115849ac" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.39.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/7e/62/4db083ee9620da3065eeb559e9fc128f41a1d15e7c48d7c83aafbccd354c/opentelemetry_exporter_otlp_proto_grpc-1.39.0.tar.gz", hash = "sha256:7e7bb3f436006836c0e0a42ac619097746ad5553ad7128a5bd4d3e727f37fc06" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/56/e8/d420b94ffddfd8cff85bb4aa5d98da26ce7935dc3cf3eca6b83cd39ab436/opentelemetry_exporter_otlp_proto_grpc-1.39.0-py3-none-any.whl", hash = "sha256:758641278050de9bb895738f35ff8840e4a47685b7e6ef4a201fe83196ba7a05" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.60b0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/55/3c/bd53dbb42eff93d18e3047c7be11224aa9966ce98ac4cc5bfb860a32c95a/opentelemetry_instrumentation-0.60b0.tar.gz", hash = "sha256:4e9fec930f283a2677a2217754b40aaf9ef76edae40499c165bc7f1d15366a74" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/5c/7b/5b5b9f8cfe727a28553acf9cd287b1d7f706f5c0a00d6e482df55b169483/opentelemetry_instrumentation-0.60b0-py3-none-any.whl", hash = "sha256:aaafa1483543a402819f1bdfb06af721c87d60dd109501f9997332862a35c76a" }, +] + +[[package]] +name = "opentelemetry-instrumentation-asgi" +version = "0.60b0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/b0/0a/715ea7044708d3c215385fb2a1c6ffe429aacb3cd23a348060aaeda52834/opentelemetry_instrumentation_asgi-0.60b0.tar.gz", hash = "sha256:928731218050089dca69f0fe980b8bfe109f384be8b89802d7337372ddb67b91" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/9b/8c/c6c59127fd996107243ca45669355665a7daff578ddafb86d6d2d3b01428/opentelemetry_instrumentation_asgi-0.60b0-py3-none-any.whl", hash = "sha256:9d76a541269452c718a0384478f3291feb650c5a3f29e578fdc6613ea3729cf3" }, +] + +[[package]] +name = "opentelemetry-instrumentation-fastapi" +version = "0.60b0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-instrumentation-asgi" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/fe/51/a021a7c929b5103fcb6bfdfa5a99abcaeb3b505faf9e3ee3ec14612c1ef9/opentelemetry_instrumentation_fastapi-0.60b0.tar.gz", hash = "sha256:5d34d67eb634a08bfe9e530680d6177521cd9da79285144e6d5a8f42683ed1b3" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/b1/5a/e238c108eb65a726d75184439377a87d532050036b54e718e4c789b26d1a/opentelemetry_instrumentation_fastapi-0.60b0-py3-none-any.whl", hash = "sha256:415c6602db01ee339276ea4cabe3e80177c9e955631c087f2ef60a75e31bfaee" }, +] + +[[package]] +name = "opentelemetry-instrumentation-logging" +version = "0.60b0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/5b/8b/a1aed0b695a58a1f0bdcf90fae5e468cc0fd7de2b74b9816b17e45c38e43/opentelemetry_instrumentation_logging-0.60b0.tar.gz", hash = "sha256:6d87840666669cbbcd53d2230c7a33476862d0bf7f1adc67a95519c6ccfc8281" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/00/89/6d4f5d8d03376637bff5b8d9e91356104a6f1ce9a34a2099bb2f4bf5e2b5/opentelemetry_instrumentation_logging-0.60b0-py3-none-any.whl", hash = "sha256:af75b3020911b9b6a1b4b19819a165eb131ce9bfdd313062d578fc2dc9a5cd0f" }, +] + +[[package]] +name = "opentelemetry-instrumentation-sqlalchemy" +version = "0.60b0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/8f/52/6a6e8ef8d0430487f1c65fdb81f2001d0b3b68de72f7437b54ff2330de44/opentelemetry_instrumentation_sqlalchemy-0.60b0.tar.gz", hash = "sha256:df9fdc6f48709138343971cef95a055f52d0262331115231bd5d678c1a452891" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/e0/08/943c1c650e7826b0462f0a2528d37b9cf717957955df6259b9c25dfcaa51/opentelemetry_instrumentation_sqlalchemy-0.60b0-py3-none-any.whl", hash = "sha256:ff66ac39364bfb38fe127f823b36693c6f2a44f929926863640ac1575583a73f" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.39.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/48/b5/64d2f8c3393cd13ea2092106118f7b98461ba09333d40179a31444c6f176/opentelemetry_proto-1.39.0.tar.gz", hash = "sha256:c1fa48678ad1a1624258698e59be73f990b7fc1f39e73e16a9d08eef65dd838c" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/e3/4d/d500e1862beed68318705732d1976c390f4a72ca8009c4983ff627acff20/opentelemetry_proto-1.39.0-py3-none-any.whl", hash = "sha256:1e086552ac79acb501485ff0ce75533f70f3382d43d0a30728eeee594f7bf818" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.39.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/51/e3/7cd989003e7cde72e0becfe830abff0df55c69d237ee7961a541e0167833/opentelemetry_sdk-1.39.0.tar.gz", hash = "sha256:c22204f12a0529e07aa4d985f1bca9d6b0e7b29fe7f03e923548ae52e0e15dde" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/a4/b4/2adc8bc83eb1055ecb592708efb6f0c520cc2eb68970b02b0f6ecda149cf/opentelemetry_sdk-1.39.0-py3-none-any.whl", hash = "sha256:90cfb07600dfc0d2de26120cebc0c8f27e69bf77cd80ef96645232372709a514" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.60b0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/71/0e/176a7844fe4e3cb5de604212094dffaed4e18b32f1c56b5258bcbcba85c2/opentelemetry_semantic_conventions-0.60b0.tar.gz", hash = "sha256:227d7aa73cbb8a2e418029d6b6465553aa01cf7e78ec9d0bc3255c7b3ac5bf8f" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/d0/56/af0306666f91bae47db14d620775604688361f0f76a872e0005277311131/opentelemetry_semantic_conventions-0.60b0-py3-none-any.whl", hash = "sha256:069530852691136018087b52688857d97bba61cd641d0f8628d2d92788c4f78a" }, +] + +[[package]] +name = "opentelemetry-util-http" +version = "0.60b0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/38/0d/786a713445cf338131fef3a84fab1378e4b2ef3c3ea348eeb0c915eb804a/opentelemetry_util_http-0.60b0.tar.gz", hash = "sha256:e42b7bb49bba43b6f34390327d97e5016eb1c47949ceaf37c4795472a4e3a82d" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/53/5d/a448862f6d10c95685ed0e703596b6bd1784074e7ad90bffdc550abb7b68/opentelemetry_util_http-0.60b0-py3-none-any.whl", hash = "sha256:4f366f1a48adb74ffa6f80aee26f96882e767e01b03cd1cfb948b6e1020341fe" }, +] + [[package]] name = "packaging" version = "25.0" @@ -1900,6 +2164,21 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237" }, ] +[[package]] +name = "protobuf" +version = "6.33.2" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d" }, + { url = "https://mirrors.aliyun.com/pypi/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4" }, + { url = "https://mirrors.aliyun.com/pypi/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43" }, + { url = "https://mirrors.aliyun.com/pypi/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e" }, + { url = "https://mirrors.aliyun.com/pypi/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872" }, + { url = "https://mirrors.aliyun.com/pypi/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f" }, + { url = "https://mirrors.aliyun.com/pypi/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c" }, +] + [[package]] name = "psutil" version = "7.1.3" @@ -3073,6 +3352,75 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390" }, ] +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04" }, + { url = "https://mirrors.aliyun.com/pypi/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2" }, + { url = "https://mirrors.aliyun.com/pypi/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c" }, + { url = "https://mirrors.aliyun.com/pypi/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775" }, + { url = "https://mirrors.aliyun.com/pypi/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd" }, + { url = "https://mirrors.aliyun.com/pypi/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05" }, + { url = "https://mirrors.aliyun.com/pypi/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418" }, + { url = "https://mirrors.aliyun.com/pypi/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390" }, + { url = "https://mirrors.aliyun.com/pypi/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6" }, + { url = "https://mirrors.aliyun.com/pypi/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18" }, + { url = "https://mirrors.aliyun.com/pypi/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7" }, + { url = "https://mirrors.aliyun.com/pypi/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85" }, + { url = "https://mirrors.aliyun.com/pypi/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f" }, + { url = "https://mirrors.aliyun.com/pypi/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311" }, + { url = "https://mirrors.aliyun.com/pypi/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1" }, + { url = "https://mirrors.aliyun.com/pypi/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5" }, + { url = "https://mirrors.aliyun.com/pypi/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2" }, + { url = "https://mirrors.aliyun.com/pypi/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89" }, + { url = "https://mirrors.aliyun.com/pypi/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77" }, + { url = "https://mirrors.aliyun.com/pypi/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a" }, + { url = "https://mirrors.aliyun.com/pypi/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0" }, + { url = "https://mirrors.aliyun.com/pypi/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba" }, + { url = "https://mirrors.aliyun.com/pypi/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd" }, + { url = "https://mirrors.aliyun.com/pypi/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828" }, + { url = "https://mirrors.aliyun.com/pypi/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9" }, + { url = "https://mirrors.aliyun.com/pypi/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396" }, + { url = "https://mirrors.aliyun.com/pypi/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc" }, + { url = "https://mirrors.aliyun.com/pypi/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe" }, + { url = "https://mirrors.aliyun.com/pypi/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c" }, + { url = "https://mirrors.aliyun.com/pypi/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6" }, + { url = "https://mirrors.aliyun.com/pypi/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0" }, + { url = "https://mirrors.aliyun.com/pypi/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77" }, + { url = "https://mirrors.aliyun.com/pypi/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7" }, + { url = "https://mirrors.aliyun.com/pypi/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277" }, + { url = "https://mirrors.aliyun.com/pypi/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d" }, + { url = "https://mirrors.aliyun.com/pypi/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa" }, + { url = "https://mirrors.aliyun.com/pypi/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050" }, + { url = "https://mirrors.aliyun.com/pypi/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8" }, + { url = "https://mirrors.aliyun.com/pypi/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb" }, + { url = "https://mirrors.aliyun.com/pypi/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16" }, + { url = "https://mirrors.aliyun.com/pypi/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39" }, + { url = "https://mirrors.aliyun.com/pypi/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235" }, + { url = "https://mirrors.aliyun.com/pypi/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c" }, + { url = "https://mirrors.aliyun.com/pypi/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b" }, + { url = "https://mirrors.aliyun.com/pypi/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa" }, + { url = "https://mirrors.aliyun.com/pypi/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7" }, + { url = "https://mirrors.aliyun.com/pypi/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4" }, + { url = "https://mirrors.aliyun.com/pypi/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10" }, + { url = "https://mirrors.aliyun.com/pypi/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6" }, + { url = "https://mirrors.aliyun.com/pypi/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58" }, + { url = "https://mirrors.aliyun.com/pypi/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a" }, + { url = "https://mirrors.aliyun.com/pypi/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067" }, + { url = "https://mirrors.aliyun.com/pypi/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454" }, + { url = "https://mirrors.aliyun.com/pypi/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e" }, + { url = "https://mirrors.aliyun.com/pypi/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f" }, + { url = "https://mirrors.aliyun.com/pypi/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056" }, + { url = "https://mirrors.aliyun.com/pypi/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804" }, + { url = "https://mirrors.aliyun.com/pypi/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977" }, + { url = "https://mirrors.aliyun.com/pypi/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116" }, + { url = "https://mirrors.aliyun.com/pypi/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6" }, + { url = "https://mirrors.aliyun.com/pypi/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22" }, +] + [[package]] name = "wsproto" version = "1.3.2" @@ -3211,6 +3559,15 @@ wheels = [ { url = "https://mirrors.aliyun.com/pypi/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff" }, ] +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://mirrors.aliyun.com/pypi/simple" } +sdist = { url = "https://mirrors.aliyun.com/pypi/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166" } +wheels = [ + { url = "https://mirrors.aliyun.com/pypi/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e" }, +] + [[package]] name = "zope-event" version = "6.1"