mirror of
https://github.com/BoardWare-Genius/jarvis-models.git
synced 2025-12-13 16:53:24 +00:00
Merge pull request #33 from BoardWare-Genius/ivan
Feat: Loki for models server
This commit is contained in:
@ -45,6 +45,14 @@ log:
|
|||||||
time_format: "%Y-%m-%d %H:%M:%S"
|
time_format: "%Y-%m-%d %H:%M:%S"
|
||||||
filename: "D:/Workspace/Logging/jarvis/jarvis-models.log"
|
filename: "D:/Workspace/Logging/jarvis/jarvis-models.log"
|
||||||
|
|
||||||
|
loki:
|
||||||
|
url: "https://loki.bwgdi.com/loki/api/v1/push"
|
||||||
|
labels:
|
||||||
|
app: jarvis
|
||||||
|
env: dev
|
||||||
|
location: "gdi"
|
||||||
|
layer: models
|
||||||
|
|
||||||
melotts:
|
melotts:
|
||||||
mode: local # or docker
|
mode: local # or docker
|
||||||
url: http://10.6.44.141:18080/convert/tts
|
url: http://10.6.44.141:18080/convert/tts
|
||||||
|
|||||||
@ -9,4 +9,5 @@ chromadb==0.5.0
|
|||||||
langchain==0.1.17
|
langchain==0.1.17
|
||||||
langchain-community==0.0.36
|
langchain-community==0.0.36
|
||||||
sentence-transformers==2.7.0
|
sentence-transformers==2.7.0
|
||||||
openai
|
openai
|
||||||
|
python-logging-loki
|
||||||
60
server.py
60
server.py
@ -4,8 +4,12 @@ from fastapi import FastAPI, Request, status
|
|||||||
from fastapi.responses import JSONResponse, Response, StreamingResponse
|
from fastapi.responses import JSONResponse, Response, StreamingResponse
|
||||||
|
|
||||||
from src.blackbox.blackbox_factory import BlackboxFactory
|
from src.blackbox.blackbox_factory import BlackboxFactory
|
||||||
|
from src.log.loki_config import LokiLogger
|
||||||
|
import logging
|
||||||
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from injector import Injector
|
from injector import Injector
|
||||||
|
import yaml
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
@ -20,16 +24,64 @@ app.add_middleware(
|
|||||||
injector = Injector()
|
injector = Injector()
|
||||||
blackbox_factory = injector.get(BlackboxFactory)
|
blackbox_factory = injector.get(BlackboxFactory)
|
||||||
|
|
||||||
|
with open(".env.yaml", "r") as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
logger = LokiLogger(
|
||||||
|
# url=config["loki"]["url"],
|
||||||
|
# username=config["loki"]["username"],
|
||||||
|
# password=config["loki"]["password"],
|
||||||
|
tags=config["loki"]["labels"],
|
||||||
|
logger_name=__name__,
|
||||||
|
level=logging.DEBUG
|
||||||
|
).get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
@app.exception_handler(Exception)
|
||||||
|
async def catch_all_exceptions(request: Request, exc: Exception):
|
||||||
|
"""
|
||||||
|
捕获所有在 ASGI 应用中未被处理的异常,并记录到 Loki。
|
||||||
|
"""
|
||||||
|
logger.error(
|
||||||
|
f"Unhandled exception in ASGI application for path: {request.url.path}, method: {request.method} - {exc}",
|
||||||
|
exc_info=True, # 必须为 True,才能获取完整的堆栈信息
|
||||||
|
extra={
|
||||||
|
"path": request.url.path,
|
||||||
|
"method": request.method,
|
||||||
|
"error_type": type(exc).__name__,
|
||||||
|
"error_message": str(exc)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=500,
|
||||||
|
content={"message": "Internal Server Error", "detail": "An unexpected error occurred."}
|
||||||
|
)
|
||||||
|
|
||||||
@app.post("/")
|
@app.post("/")
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def blackbox(blackbox_name: Union[str, None] = None, request: Request = None):
|
async def blackbox(blackbox_name: Union[str, None] = None, request: Request = None):
|
||||||
if not blackbox_name:
|
if not blackbox_name:
|
||||||
return await JSONResponse(content={"error": "blackbox_name is required"}, status_code=status.HTTP_400_BAD_REQUEST)
|
return JSONResponse(content={"error": "blackbox_name is required"}, status_code=status.HTTP_400_BAD_REQUEST)
|
||||||
try:
|
try:
|
||||||
box = blackbox_factory.get_blackbox(blackbox_name)
|
box = blackbox_factory.get_blackbox(blackbox_name)
|
||||||
except ValueError:
|
except ValueError as e:
|
||||||
return await JSONResponse(content={"error": "value error"}, status_code=status.HTTP_400_BAD_REQUEST)
|
logger.error(f"获取 blackbox 失败: {blackbox_name} - {e}", exc_info=True)
|
||||||
return await box.fast_api_handler(request)
|
return JSONResponse(content={"error": "value error"}, status_code=status.HTTP_400_BAD_REQUEST)
|
||||||
|
try:
|
||||||
|
response = await box.fast_api_handler(request)
|
||||||
|
# 检查响应的状态码,如果 >= 400,则认为是错误响应并记录
|
||||||
|
if response.status_code >= 400:
|
||||||
|
try:
|
||||||
|
decoded_content = response.body.decode('utf-8', errors='ignore')
|
||||||
|
logger.error(f"Blackbox 返回错误响应: URL={request.url}, Method={request.method}, Status={response.status_code}, Content={decoded_content}")
|
||||||
|
except Exception as body_exc:
|
||||||
|
logger.error(f"Blackbox {blackbox_name} 返回错误响应: URL={request.url}, Method={request.method}, Status={response.status_code}, 无法解析响应内容: {body_exc}")
|
||||||
|
return response
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Blackbox 内部抛出异常: URL={request.url}, Method={request.method}, 异常报错: {type(e).__name__}: {e}", exc_info=True)
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=500,
|
||||||
|
content={"message": "Internal Server Error", "detail": "An unexpected error occurred."}
|
||||||
|
)
|
||||||
|
|
||||||
# @app.get("/audio/{filename}")
|
# @app.get("/audio/{filename}")
|
||||||
# async def serve_audio(filename: str):
|
# async def serve_audio(filename: str):
|
||||||
|
|||||||
102
src/log/loki_config.py
Normal file
102
src/log/loki_config.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import logging
|
||||||
|
import logging_loki
|
||||||
|
import os
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
from typing import Dict, Optional
|
||||||
|
class LokiLogger:
|
||||||
|
"""
|
||||||
|
一个用于配置和获取Loki日志记录器的类。
|
||||||
|
可以在其他文件里import后直接使用。
|
||||||
|
|
||||||
|
用法示例:
|
||||||
|
from loki_config import LokiLogger
|
||||||
|
|
||||||
|
# 获取日志记录器实例 (使用默认配置或环境变量)
|
||||||
|
loki_logger_instance = LokiLogger().get_logger()
|
||||||
|
loki_logger_instance.info("这条日志会推送到Loki!")
|
||||||
|
|
||||||
|
# 或者使用自定义配置
|
||||||
|
my_loki_logger = LokiLogger(
|
||||||
|
url="https://your-custom-loki-url.com/loki/api/v1/push",
|
||||||
|
username="myuser",
|
||||||
|
password="mypassword",
|
||||||
|
tags={"app_name": "my-custom-app", "environment": "prod"},
|
||||||
|
level=logging.DEBUG
|
||||||
|
).get_logger()
|
||||||
|
my_loki_logger.debug("这条调试日志也推送到Loki。")
|
||||||
|
"""
|
||||||
|
def __init__(self,
|
||||||
|
url: Optional[str] = None,
|
||||||
|
username: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
tags: Optional[Dict[str, str]] = None,
|
||||||
|
level: int = logging.INFO,
|
||||||
|
logger_name: str = "app_loki_logger"):
|
||||||
|
"""
|
||||||
|
初始化LokiLogger。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str, optional): Loki的推送URL。默认从环境变量LOKI_URL获取,
|
||||||
|
如果不存在则使用"https://loki.bwgdi.com/loki/api/v1/push"。
|
||||||
|
username (str, optional): Loki的认证用户名。默认从环境变量LOKI_USERNAME获取。
|
||||||
|
password (str, optional): Loki的认证密码。默认从环境变量LOKI_PASSWORD获取。
|
||||||
|
tags (Dict[str, str], optional): 发送到Loki的默认标签。例如:{"application": "my-app"}。
|
||||||
|
如果未提供,则默认为{"application": "default-app", "source": "python-app"}。
|
||||||
|
level (int, optional): 日志级别(如logging.INFO, logging.DEBUG)。默认为logging.INFO。
|
||||||
|
logger_name (str, optional): 日志记录器的名称。默认为"app_loki_logger"。
|
||||||
|
"""
|
||||||
|
# 从环境变量获取配置,如果未通过参数提供
|
||||||
|
self._url = url if url else os.getenv("LOKI_URL", "https://loki.bwgdi.com/loki/api/v1/push")
|
||||||
|
self._username = username if username else os.getenv("LOKI_USERNAME",'admin')
|
||||||
|
self._password = password if password else os.getenv("LOKI_PASSWORD",'admin')
|
||||||
|
self._tags = tags if tags is not None else {"app": "jarvis", "env": "dev", "location": "gdi", "layer": "models"}
|
||||||
|
|
||||||
|
# 获取或创建指定名称的日志记录器
|
||||||
|
self._logger = logging.getLogger(logger_name)
|
||||||
|
self._logger.setLevel(level)
|
||||||
|
|
||||||
|
for handler in self._logger.handlers[:]:
|
||||||
|
self._logger.removeHandler(handler)
|
||||||
|
# 检查是否已存在LokiHandler,避免重复添加导致重复日志
|
||||||
|
# 在多文件或多次初始化的情况下,同一个logger_name可能会获取到同一个logger实例
|
||||||
|
if not any(isinstance(h, logging_loki.LokiHandler) for h in self._logger.handlers):
|
||||||
|
try:
|
||||||
|
auth = None
|
||||||
|
if self._username and self._password:
|
||||||
|
auth = HTTPBasicAuth(self._username, self._password)
|
||||||
|
|
||||||
|
loki_handler = logging_loki.LokiHandler(
|
||||||
|
url=self._url,
|
||||||
|
tags=self._tags,
|
||||||
|
version="1", # 通常Loki API版本为1
|
||||||
|
auth=auth
|
||||||
|
)
|
||||||
|
self._logger.addHandler(loki_handler)
|
||||||
|
# 同时添加一个StreamHandler到控制台,以便在本地调试时也能看到日志输出
|
||||||
|
if not any(isinstance(h, logging.StreamHandler) for h in self._logger.handlers):
|
||||||
|
self._logger.addHandler(logging.StreamHandler())
|
||||||
|
self._logger.info(f"LokiLogger: 已成功配置Loki日志处理器,目标地址:{self._url}")
|
||||||
|
except Exception as e:
|
||||||
|
# 如果Loki配置失败,确保仍然有StreamHandler将日志输出到控制台
|
||||||
|
if not any(isinstance(h, logging.StreamHandler) for h in self._logger.handlers):
|
||||||
|
self._logger.addHandler(logging.StreamHandler())
|
||||||
|
self._logger.error(f"LokiLogger: 配置LokiHandler失败:{e}。将回退到控制台日志记录。", exc_info=True)
|
||||||
|
else:
|
||||||
|
# 如果LokiHandler已经存在,只确保StreamHandler存在
|
||||||
|
if not any(isinstance(h, logging.StreamHandler) for h in self._logger.handlers):
|
||||||
|
self._logger.addHandler(logging.StreamHandler())
|
||||||
|
self._logger.debug(f"LokiLogger: '{logger_name}' 记录器已配置LokiHandler,跳过重新配置。")
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(self) -> logging.Logger:
|
||||||
|
"""
|
||||||
|
返回已配置的日志记录器实例。
|
||||||
|
"""
|
||||||
|
self._logger.debug("LokiLogger: 获取已配置的日志记录器实例。")
|
||||||
|
return self._logger
|
||||||
|
|
||||||
|
# 可选:如果希望有一个默认的全局LokiLogger实例
|
||||||
|
# 你可以在这里实例化,然后在其他文件直接从这里导入 `loki_logger`
|
||||||
|
# 例如:
|
||||||
|
# DEFAULT_LOKI_LOGGER_INSTANCE = LokiLogger().get_logger()
|
||||||
|
# 然后在其他文件里: `from loki_config import DEFAULT_LOKI_LOGGER_INSTANCE as logger`
|
||||||
Reference in New Issue
Block a user