Simplify config example and log API bodies

This commit is contained in:
2026-06-08 13:26:22 +08:00
parent 5500947ddb
commit 64e3ea9631
4 changed files with 85 additions and 7 deletions

View File

@ -11,16 +11,10 @@ openviking:
# OpenViking HTTP server. The api_key must match server.root_api_key in ov.conf. # OpenViking HTTP server. The api_key must match server.root_api_key in ov.conf.
url: "http://127.0.0.1:1933" url: "http://127.0.0.1:1933"
api_key: "<OPENVIKING_ROOT_KEY>" api_key: "<OPENVIKING_ROOT_KEY>"
timeout: 30
verify_ssl: true
everos: everos:
# EverOS EverCore HTTP server. # EverOS EverCore HTTP server.
url: "http://127.0.0.1:1995" url: "http://127.0.0.1:1995"
api_key: ""
timeout: 180
verify_ssl: true
health_path: "/health"
storage: storage:
sqlite_path: "./memory_system_api.sqlite3" sqlite_path: "./memory_system_api.sqlite3"

View File

@ -36,6 +36,7 @@ class StorageConfig(BaseModel):
class LoggingConfig(BaseModel): class LoggingConfig(BaseModel):
level: str = "INFO" level: str = "INFO"
format: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
class Config(BaseModel): class Config(BaseModel):

View File

@ -1,13 +1,17 @@
"""Standalone FastAPI server for Memory System API.""" """Standalone FastAPI server for Memory System API."""
from __future__ import annotations from __future__ import annotations
from fastapi import FastAPI import logging
from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from .api import router from .api import router
from .config import Config, load_config, set_config from .config import Config, load_config, set_config
request_logger = logging.getLogger("memory_system_api.requests")
app = FastAPI(title="Memory System API", version="0.1.0") app = FastAPI(title="Memory System API", version="0.1.0")
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@ -16,6 +20,42 @@ app.add_middleware(
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
) )
@app.middleware("http")
async def log_request_and_response(request: Request, call_next):
request_body = await request.body()
request_logger.info(
"request %s %s body=%s",
request.method,
_path_with_query(request),
_body_for_log(request_body),
)
async def receive():
return {"type": "http.request", "body": request_body, "more_body": False}
response = await call_next(Request(request.scope, receive))
response_body = b""
async for chunk in response.body_iterator:
response_body += chunk
request_logger.info(
"response %s %s status=%s body=%s",
request.method,
_path_with_query(request),
response.status_code,
_body_for_log(response_body),
)
return Response(
content=response_body,
status_code=response.status_code,
headers=dict(response.headers),
media_type=response.media_type,
background=response.background,
)
app.include_router(router) app.include_router(router)
@ -25,6 +65,17 @@ def create_app(config: Config | None = None) -> FastAPI:
return app return app
def _path_with_query(request: Request) -> str:
query = request.url.query
return f"{request.url.path}?{query}" if query else request.url.path
def _body_for_log(body: bytes) -> str:
if not body:
return ""
return body.decode("utf-8", errors="replace")
def main() -> None: def main() -> None:
import argparse import argparse
import uvicorn import uvicorn
@ -41,6 +92,7 @@ def main() -> None:
if args.port: if args.port:
config.server.port = args.port config.server.port = args.port
set_config(config) set_config(config)
logging.basicConfig(level=config.logging.level.upper(), format=config.logging.format)
uvicorn.run(app, host=config.server.host, port=config.server.port, log_level=config.logging.level.lower()) uvicorn.run(app, host=config.server.host, port=config.server.port, log_level=config.logging.level.lower())

View File

@ -1,3 +1,8 @@
import logging
from fastapi.testclient import TestClient
def test_memory_system_server_exposes_routes(): def test_memory_system_server_exposes_routes():
from memory_system_api.server import app from memory_system_api.server import app
@ -60,3 +65,29 @@ def test_memory_system_messages_does_not_require_account_key_header():
route = next(route for route in app.routes if getattr(route, "path", "") == "/memory-system/messages") route = next(route for route in app.routes if getattr(route, "path", "") == "/memory-system/messages")
assert all(getattr(dependency.call, "__name__", "") != "account_key_header" for dependency in route.dependant.dependencies) assert all(getattr(dependency.call, "__name__", "") != "account_key_header" for dependency in route.dependant.dependencies)
def test_memory_system_logs_request_and_response_bodies(caplog):
from memory_system_api.api import get_service
from memory_system_api.server import app
class FakeService:
async def create_user(self, user_id: str):
return {"status": "success", "account": {"user_id": user_id}}
app.dependency_overrides[get_service] = lambda: FakeService()
try:
with caplog.at_level(logging.INFO, logger="memory_system_api.requests"):
response = TestClient(app).post("/memory-system/users", json={"user_id": "userA"})
finally:
app.dependency_overrides.clear()
assert response.status_code == 200
assert response.json() == {"status": "success", "account": {"user_id": "userA"}}
assert any("request POST /memory-system/users body={\"user_id\":\"userA\"}" in record.message for record in caplog.records)
assert any(
"response POST /memory-system/users status=200 body={\"status\":\"success\",\"account\":{\"user_id\":\"userA\"}}"
in record.message
for record in caplog.records
)