feat: 增加log 配置

This commit is contained in:
ViperEkura 2026-04-13 21:20:56 +08:00
parent 85619c0d97
commit 9bf3e53bf4
6 changed files with 110 additions and 10 deletions

View File

@ -1,7 +1,7 @@
# 配置文件
app:
secret_key: ${APP_SECRET_KEY}
debug: true
debug: false
host: 0.0.0.0
port: 8000
@ -19,3 +19,7 @@ tools:
cache_ttl: 300
max_workers: 4
max_iterations: 10
logging:
level: INFO

View File

@ -99,10 +99,15 @@ export function createSSEStream(url, body, { onProcessStep, onDone, onError }) {
onError(data.content)
}
} catch (e) {
// 忽略解析错误
console.error('SSE parse error:', e, 'line:', line)
}
}
}
// 如果没有收到 done 事件,触发错误
if (!completed && onError) {
onError('stream ended without done event')
}
break
}

View File

@ -1,4 +1,5 @@
"""FastAPI application factory"""
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
@ -7,6 +8,8 @@ from luxx.config import config
from luxx.database import init_db
from luxx.routes import api_router
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
@ -31,7 +34,7 @@ async def lifespan(app: FastAPI):
)
db.add(default_user)
db.commit()
print("Default admin user created: admin / admin123")
logger.info("Default admin user created: admin / admin123")
finally:
db.close()

View File

@ -112,6 +112,19 @@ class Config:
def tools_max_iterations(self) -> int:
return self.get("tools.max_iterations", 10)
# Logging configuration
@property
def log_level(self) -> str:
return self.get("logging.level", "INFO")
@property
def log_format(self) -> str:
return self.get("logging.format", "%(asctime)s | %(levelname)-8s | %(message)s")
@property
def log_date_format(self) -> str:
return self.get("logging.date_format", "%Y-%m-%d %H:%M:%S")
# Global configuration instance
config = Config()

View File

@ -1,10 +1,13 @@
"""LLM API client"""
import json
import httpx
import logging
from typing import Dict, Any, Optional, List, AsyncGenerator
from luxx.config import config
logger = logging.getLogger(__name__)
class LLMResponse:
"""LLM response"""
@ -147,19 +150,18 @@ class LLMClient:
"""
body = self._build_body(model, messages, tools, stream=True, **kwargs)
print(f"[LLM] Starting stream_call for model: {model}")
print(f"[LLM] Messages count: {len(messages)}")
logger.info(f"Starting stream_call for model: {model}, messages count: {len(messages)}")
try:
async with httpx.AsyncClient(timeout=120.0) as client:
print(f"[LLM] Sending request to {self.api_url}")
logger.info(f"Sending request to {self.api_url}")
async with client.stream(
"POST",
self.api_url,
headers=self._build_headers(),
json=body
) as response:
print(f"[LLM] Response status: {response.status_code}")
logger.info(f"Response status: {response.status_code}")
response.raise_for_status()
async for line in response.aiter_lines():
@ -167,10 +169,10 @@ class LLMClient:
yield line + "\n"
except httpx.HTTPStatusError as e:
status_code = e.response.status_code if e.response else "?"
print(f"[LLM] HTTP error: {status_code}")
logger.error(f"HTTP error: {status_code}")
yield f"event: error\ndata: {json.dumps({'content': f'HTTP {status_code}: Request failed'})}\n\n"
except Exception as e:
print(f"[LLM] Exception: {type(e).__name__}: {str(e)}")
logger.error(f"Exception: {type(e).__name__}: {str(e)}")
yield f"event: error\ndata: {json.dumps({'content': str(e)})}\n\n"

75
run.py
View File

@ -1,17 +1,90 @@
#!/usr/bin/env python3
"""Application entry point"""
import logging
import logging.config
from copy import copy
import uvicorn
from uvicorn.logging import ColourizedFormatter
from luxx.config import config
# Custom formatter extending uvicorn's ColourizedFormatter
class ModuleFormatter(ColourizedFormatter):
"""Add module name after level prefix for non-uvicorn loggers"""
def formatMessage(self, record: logging.LogRecord) -> str:
# Copy record to avoid modifying the original
recordcopy = copy(record)
# Get level name with color
levelname = recordcopy.levelname
separator = " " * (8 - len(recordcopy.levelname))
if self.use_colors:
levelname = self.color_level_name(levelname, recordcopy.levelno)
if "color_message" in recordcopy.__dict__:
recordcopy.msg = recordcopy.__dict__["color_message"]
recordcopy.__dict__["message"] = recordcopy.getMessage()
# Add module name for all loggers
name = record.name
# For uvicorn.error, show "uvicorn" not "error"
if name.startswith('uvicorn.'):
module = 'uvicorn'
else:
module = name
levelprefix = levelname + ":" + separator
recordcopy.__dict__["levelprefix"] = f"{levelprefix} [{module}]"
return super(ColourizedFormatter, self).formatMessage(recordcopy)
def get_log_config() -> dict:
"""Get log configuration from config.yaml"""
log_level = getattr(config, 'log_level', 'INFO')
return {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"()": ModuleFormatter,
"fmt": "%(levelprefix)s %(message)s",
},
},
"handlers": {
"default": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
},
"loggers": {
"uvicorn": {"handlers": ["default"], "level": log_level, "propagate": False},
"uvicorn.error": {"handlers": ["default"], "level": log_level, "propagate": False},
"uvicorn.access": {"handlers": [], "level": "WARNING", "propagate": False},
"uvicorn.asgi": {"handlers": [], "level": "WARNING", "propagate": False},
},
"root": {
"handlers": ["default"],
"level": log_level,
},
}
LOG_CONFIG = get_log_config()
def main():
"""Start the application"""
logging.config.dictConfig(LOG_CONFIG)
uvicorn.run(
"luxx:app",
host=config.app_host,
port=config.app_port,
reload=config.debug,
log_level="debug" if config.debug else "info"
log_level=config.log_level.lower(),
log_config=LOG_CONFIG
)