mirror of
https://github.com/TheSmallHanCat/sora2api.git
synced 2026-02-22 00:34:42 +08:00
feat: 新增客户端请求响应日志记录、优化错误处理及日志源标识
This commit is contained in:
@@ -1,13 +1,15 @@
|
|||||||
"""API routes - OpenAI compatible endpoints"""
|
"""API routes - OpenAI compatible endpoints"""
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from fastapi.responses import StreamingResponse, JSONResponse
|
from fastapi.responses import StreamingResponse, JSONResponse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List
|
from typing import List
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
from ..core.auth import verify_api_key_header
|
from ..core.auth import verify_api_key_header
|
||||||
from ..core.models import ChatCompletionRequest
|
from ..core.models import ChatCompletionRequest
|
||||||
from ..services.generation_handler import GenerationHandler, MODEL_CONFIG
|
from ..services.generation_handler import GenerationHandler, MODEL_CONFIG
|
||||||
|
from ..core.logger import debug_logger
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@@ -71,10 +73,22 @@ async def list_models(api_key: str = Depends(verify_api_key_header)):
|
|||||||
@router.post("/v1/chat/completions")
|
@router.post("/v1/chat/completions")
|
||||||
async def create_chat_completion(
|
async def create_chat_completion(
|
||||||
request: ChatCompletionRequest,
|
request: ChatCompletionRequest,
|
||||||
api_key: str = Depends(verify_api_key_header)
|
api_key: str = Depends(verify_api_key_header),
|
||||||
|
http_request: Request = None
|
||||||
):
|
):
|
||||||
"""Create chat completion (unified endpoint for image and video generation)"""
|
"""Create chat completion (unified endpoint for image and video generation)"""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Log client request
|
||||||
|
debug_logger.log_request(
|
||||||
|
method="POST",
|
||||||
|
url="/v1/chat/completions",
|
||||||
|
headers=dict(http_request.headers) if http_request else {},
|
||||||
|
body=request.dict(),
|
||||||
|
source="Client"
|
||||||
|
)
|
||||||
|
|
||||||
# Extract prompt from messages
|
# Extract prompt from messages
|
||||||
if not request.messages:
|
if not request.messages:
|
||||||
raise HTTPException(status_code=400, detail="Messages cannot be empty")
|
raise HTTPException(status_code=400, detail="Messages cannot be empty")
|
||||||
@@ -153,18 +167,36 @@ async def create_chat_completion(
|
|||||||
result = chunk
|
result = chunk
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return JSONResponse(content=json.loads(result))
|
duration_ms = (time.time() - start_time) * 1000
|
||||||
|
response_data = json.loads(result)
|
||||||
|
debug_logger.log_response(
|
||||||
|
status_code=200,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
body=response_data,
|
||||||
|
duration_ms=duration_ms,
|
||||||
|
source="Client"
|
||||||
|
)
|
||||||
|
return JSONResponse(content=response_data)
|
||||||
else:
|
else:
|
||||||
|
duration_ms = (time.time() - start_time) * 1000
|
||||||
|
error_response = {
|
||||||
|
"error": {
|
||||||
|
"message": "Availability check failed",
|
||||||
|
"type": "server_error",
|
||||||
|
"param": None,
|
||||||
|
"code": None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug_logger.log_response(
|
||||||
|
status_code=500,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
body=error_response,
|
||||||
|
duration_ms=duration_ms,
|
||||||
|
source="Client"
|
||||||
|
)
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
content={
|
content=error_response
|
||||||
"error": {
|
|
||||||
"message": "Availability check failed",
|
|
||||||
"type": "server_error",
|
|
||||||
"param": None,
|
|
||||||
"code": None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle streaming
|
# Handle streaming
|
||||||
@@ -229,31 +261,64 @@ async def create_chat_completion(
|
|||||||
result = chunk
|
result = chunk
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return JSONResponse(content=json.loads(result))
|
duration_ms = (time.time() - start_time) * 1000
|
||||||
|
response_data = json.loads(result)
|
||||||
|
debug_logger.log_response(
|
||||||
|
status_code=200,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
body=response_data,
|
||||||
|
duration_ms=duration_ms,
|
||||||
|
source="Client"
|
||||||
|
)
|
||||||
|
return JSONResponse(content=response_data)
|
||||||
else:
|
else:
|
||||||
# Return OpenAI-compatible error format
|
# Return OpenAI-compatible error format
|
||||||
|
duration_ms = (time.time() - start_time) * 1000
|
||||||
|
error_response = {
|
||||||
|
"error": {
|
||||||
|
"message": "Availability check failed",
|
||||||
|
"type": "server_error",
|
||||||
|
"param": None,
|
||||||
|
"code": None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug_logger.log_response(
|
||||||
|
status_code=500,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
body=error_response,
|
||||||
|
duration_ms=duration_ms,
|
||||||
|
source="Client"
|
||||||
|
)
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
content={
|
content=error_response
|
||||||
"error": {
|
|
||||||
"message": "Availability check failed",
|
|
||||||
"type": "server_error",
|
|
||||||
"param": None,
|
|
||||||
"code": None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Return OpenAI-compatible error format
|
# Return OpenAI-compatible error format
|
||||||
|
duration_ms = (time.time() - start_time) * 1000
|
||||||
|
error_response = {
|
||||||
|
"error": {
|
||||||
|
"message": str(e),
|
||||||
|
"type": "server_error",
|
||||||
|
"param": None,
|
||||||
|
"code": None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug_logger.log_error(
|
||||||
|
error_message=str(e),
|
||||||
|
status_code=500,
|
||||||
|
response_text=str(e),
|
||||||
|
source="Client"
|
||||||
|
)
|
||||||
|
debug_logger.log_response(
|
||||||
|
status_code=500,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
body=error_response,
|
||||||
|
duration_ms=duration_ms,
|
||||||
|
source="Client"
|
||||||
|
)
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
content={
|
content=error_response
|
||||||
"error": {
|
|
||||||
"message": str(e),
|
|
||||||
"type": "server_error",
|
|
||||||
"param": None,
|
|
||||||
"code": None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -68,9 +68,20 @@ class DebugLogger:
|
|||||||
headers: Dict[str, str],
|
headers: Dict[str, str],
|
||||||
body: Optional[Any] = None,
|
body: Optional[Any] = None,
|
||||||
files: Optional[Dict] = None,
|
files: Optional[Dict] = None,
|
||||||
proxy: Optional[str] = None
|
proxy: Optional[str] = None,
|
||||||
|
source: str = "Server"
|
||||||
):
|
):
|
||||||
"""Log API request details to log.txt"""
|
"""Log API request details to log.txt
|
||||||
|
|
||||||
|
Args:
|
||||||
|
method: HTTP method
|
||||||
|
url: Request URL
|
||||||
|
headers: Request headers
|
||||||
|
body: Request body
|
||||||
|
files: Files to upload
|
||||||
|
proxy: Proxy URL
|
||||||
|
source: Request source - "Client" for user->sora2api, "Server" for sora2api->Sora
|
||||||
|
"""
|
||||||
|
|
||||||
# Check if debug mode is enabled
|
# Check if debug mode is enabled
|
||||||
if not config.debug_enabled:
|
if not config.debug_enabled:
|
||||||
@@ -78,7 +89,7 @@ class DebugLogger:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self._write_separator()
|
self._write_separator()
|
||||||
self.logger.info(f"🔵 [REQUEST] {self._format_timestamp()}")
|
self.logger.info(f"🔵 [REQUEST][{source}] {self._format_timestamp()}")
|
||||||
self._write_separator("-")
|
self._write_separator("-")
|
||||||
|
|
||||||
# Basic info
|
# Basic info
|
||||||
@@ -136,9 +147,18 @@ class DebugLogger:
|
|||||||
status_code: int,
|
status_code: int,
|
||||||
headers: Dict[str, str],
|
headers: Dict[str, str],
|
||||||
body: Any,
|
body: Any,
|
||||||
duration_ms: Optional[float] = None
|
duration_ms: Optional[float] = None,
|
||||||
|
source: str = "Server"
|
||||||
):
|
):
|
||||||
"""Log API response details to log.txt"""
|
"""Log API response details to log.txt
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status_code: HTTP status code
|
||||||
|
headers: Response headers
|
||||||
|
body: Response body
|
||||||
|
duration_ms: Request duration in milliseconds
|
||||||
|
source: Request source - "Client" for user->sora2api, "Server" for sora2api->Sora
|
||||||
|
"""
|
||||||
|
|
||||||
# Check if debug mode is enabled
|
# Check if debug mode is enabled
|
||||||
if not config.debug_enabled:
|
if not config.debug_enabled:
|
||||||
@@ -146,7 +166,7 @@ class DebugLogger:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self._write_separator()
|
self._write_separator()
|
||||||
self.logger.info(f"🟢 [RESPONSE] {self._format_timestamp()}")
|
self.logger.info(f"🟢 [RESPONSE][{source}] {self._format_timestamp()}")
|
||||||
self._write_separator("-")
|
self._write_separator("-")
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
@@ -192,9 +212,17 @@ class DebugLogger:
|
|||||||
self,
|
self,
|
||||||
error_message: str,
|
error_message: str,
|
||||||
status_code: Optional[int] = None,
|
status_code: Optional[int] = None,
|
||||||
response_text: Optional[str] = None
|
response_text: Optional[str] = None,
|
||||||
|
source: str = "Server"
|
||||||
):
|
):
|
||||||
"""Log API error details to log.txt"""
|
"""Log API error details to log.txt
|
||||||
|
|
||||||
|
Args:
|
||||||
|
error_message: Error message
|
||||||
|
status_code: HTTP status code
|
||||||
|
response_text: Response text
|
||||||
|
source: Request source - "Client" for user->sora2api, "Server" for sora2api->Sora
|
||||||
|
"""
|
||||||
|
|
||||||
# Check if debug mode is enabled
|
# Check if debug mode is enabled
|
||||||
if not config.debug_enabled:
|
if not config.debug_enabled:
|
||||||
@@ -202,7 +230,7 @@ class DebugLogger:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self._write_separator()
|
self._write_separator()
|
||||||
self.logger.info(f"🔴 [ERROR] {self._format_timestamp()}")
|
self.logger.info(f"🔴 [ERROR][{source}] {self._format_timestamp()}")
|
||||||
self._write_separator("-")
|
self._write_separator("-")
|
||||||
|
|
||||||
if status_code:
|
if status_code:
|
||||||
|
|||||||
@@ -226,7 +226,8 @@ class SoraClient:
|
|||||||
debug_logger.log_error(
|
debug_logger.log_error(
|
||||||
error_message=f"nf/create request failed: {str(e)}",
|
error_message=f"nf/create request failed: {str(e)}",
|
||||||
status_code=0,
|
status_code=0,
|
||||||
response_text=str(e)
|
response_text=str(e),
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -259,7 +260,8 @@ class SoraClient:
|
|||||||
debug_logger.log_error(
|
debug_logger.log_error(
|
||||||
error_message=f"Sentinel request failed: {str(e)}",
|
error_message=f"Sentinel request failed: {str(e)}",
|
||||||
status_code=0,
|
status_code=0,
|
||||||
response_text=str(e)
|
response_text=str(e),
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -386,7 +388,8 @@ class SoraClient:
|
|||||||
headers=headers,
|
headers=headers,
|
||||||
body=json_data,
|
body=json_data,
|
||||||
files=multipart,
|
files=multipart,
|
||||||
proxy=proxy_url
|
proxy=proxy_url,
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Record start time
|
# Record start time
|
||||||
@@ -414,7 +417,8 @@ class SoraClient:
|
|||||||
status_code=response.status_code,
|
status_code=response.status_code,
|
||||||
headers=dict(response.headers),
|
headers=dict(response.headers),
|
||||||
body=response_json if response_json else response.text,
|
body=response_json if response_json else response.text,
|
||||||
duration_ms=duration_ms
|
duration_ms=duration_ms,
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check status
|
# Check status
|
||||||
@@ -436,7 +440,8 @@ class SoraClient:
|
|||||||
debug_logger.log_error(
|
debug_logger.log_error(
|
||||||
error_message=f"Unsupported country: {error_msg}",
|
error_message=f"Unsupported country: {error_msg}",
|
||||||
status_code=response.status_code,
|
status_code=response.status_code,
|
||||||
response_text=error_msg
|
response_text=error_msg,
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
# Raise exception with structured error data
|
# Raise exception with structured error data
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
@@ -446,7 +451,8 @@ class SoraClient:
|
|||||||
debug_logger.log_error(
|
debug_logger.log_error(
|
||||||
error_message=error_msg,
|
error_message=error_msg,
|
||||||
status_code=response.status_code,
|
status_code=response.status_code,
|
||||||
response_text=response.text
|
response_text=response.text,
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
@@ -654,7 +660,8 @@ class SoraClient:
|
|||||||
status_code=response.status_code,
|
status_code=response.status_code,
|
||||||
headers=dict(response.headers),
|
headers=dict(response.headers),
|
||||||
body=response.text if response.text else "No content",
|
body=response.text if response.text else "No content",
|
||||||
duration_ms=duration_ms
|
duration_ms=duration_ms,
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check status (DELETE typically returns 204 No Content or 200 OK)
|
# Check status (DELETE typically returns 204 No Content or 200 OK)
|
||||||
@@ -663,7 +670,8 @@ class SoraClient:
|
|||||||
debug_logger.log_error(
|
debug_logger.log_error(
|
||||||
error_message=error_msg,
|
error_message=error_msg,
|
||||||
status_code=response.status_code,
|
status_code=response.status_code,
|
||||||
response_text=response.text
|
response_text=response.text,
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
@@ -719,7 +727,8 @@ class SoraClient:
|
|||||||
status_code=response.status_code,
|
status_code=response.status_code,
|
||||||
headers=dict(response.headers),
|
headers=dict(response.headers),
|
||||||
body=response.text if response.text else "No content",
|
body=response.text if response.text else "No content",
|
||||||
duration_ms=duration_ms
|
duration_ms=duration_ms,
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check status
|
# Check status
|
||||||
@@ -728,7 +737,8 @@ class SoraClient:
|
|||||||
debug_logger.log_error(
|
debug_logger.log_error(
|
||||||
error_message=error_msg,
|
error_message=error_msg,
|
||||||
status_code=response.status_code,
|
status_code=response.status_code,
|
||||||
response_text=response.text
|
response_text=response.text,
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
@@ -741,7 +751,8 @@ class SoraClient:
|
|||||||
debug_logger.log_error(
|
debug_logger.log_error(
|
||||||
error_message=error_msg,
|
error_message=error_msg,
|
||||||
status_code=401,
|
status_code=401,
|
||||||
response_text=str(result)
|
response_text=str(result),
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
@@ -757,7 +768,8 @@ class SoraClient:
|
|||||||
debug_logger.log_error(
|
debug_logger.log_error(
|
||||||
error_message=f"Custom parse request failed: {str(e)}",
|
error_message=f"Custom parse request failed: {str(e)}",
|
||||||
status_code=500,
|
status_code=500,
|
||||||
response_text=str(e)
|
response_text=str(e),
|
||||||
|
source="Server"
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user