mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 10:28:47 +02:00
Log client details for API and agent requests
This commit is contained in:
@@ -41,7 +41,14 @@ def _last_user_prompt(messages: list[dict[str, Any]]) -> str | None:
|
||||
|
||||
|
||||
def create_chat_run(
|
||||
*, request_id: str, agui_run_id: str | None, thread_id: str | None, messages: list[dict[str, Any]]
|
||||
*,
|
||||
request_id: str,
|
||||
agui_run_id: str | None,
|
||||
thread_id: str | None,
|
||||
messages: list[dict[str, Any]],
|
||||
client_ip: str | None = None,
|
||||
user_agent: str | None = None,
|
||||
browser: str | None = None,
|
||||
) -> int:
|
||||
now = utcnow()
|
||||
with Session(engine) as session:
|
||||
@@ -50,6 +57,9 @@ def create_chat_run(
|
||||
agui_run_id=agui_run_id,
|
||||
requested_thread_id=thread_id,
|
||||
model=settings.AGENT_MODEL,
|
||||
client_ip=client_ip,
|
||||
user_agent=user_agent,
|
||||
browser=browser,
|
||||
user_prompt=_last_user_prompt(messages),
|
||||
expires_at=now + timedelta(days=CHAT_LOG_RETENTION_DAYS),
|
||||
)
|
||||
|
||||
@@ -38,6 +38,41 @@ from app.logging import configure_structured_logging, log_event
|
||||
|
||||
|
||||
AGENT_PATH = "/api/v1/agent/agui"
|
||||
MAX_USER_AGENT_LENGTH = 512
|
||||
|
||||
|
||||
def _request_client_details(request: Request) -> tuple[str, str | None, str | None]:
|
||||
"""Return the browser client details propagated through the frontend BFF.
|
||||
|
||||
Production nginx-proxy supplies X-Real-IP and the frontend forwards it to
|
||||
FastAPI. For direct local development, fall back to X-Forwarded-For and
|
||||
then the socket peer. The BFF is the only production path to the backend,
|
||||
which is not publicly exposed.
|
||||
"""
|
||||
client_ip = request.headers.get("x-real-ip", "").strip()
|
||||
if not client_ip:
|
||||
forwarded = request.headers.get("x-forwarded-for", "")
|
||||
client_ip = forwarded.split(",", 1)[0].strip()
|
||||
if not client_ip:
|
||||
client_ip = request.client.host if request.client else "unknown"
|
||||
|
||||
user_agent = request.headers.get("user-agent", "").strip()[:MAX_USER_AGENT_LENGTH] or None
|
||||
if not user_agent:
|
||||
return client_ip, None, None
|
||||
ua = user_agent.lower()
|
||||
if "edg/" in ua or "edgios/" in ua:
|
||||
browser = "Edge"
|
||||
elif "opr/" in ua or "opera" in ua:
|
||||
browser = "Opera"
|
||||
elif "firefox/" in ua or "fxios/" in ua:
|
||||
browser = "Firefox"
|
||||
elif "chrome/" in ua or "crios/" in ua:
|
||||
browser = "Chrome"
|
||||
elif "safari/" in ua:
|
||||
browser = "Safari"
|
||||
else:
|
||||
browser = "Other"
|
||||
return client_ip, user_agent, browser
|
||||
|
||||
|
||||
def _pair_orphan_tool_calls(messages: list) -> list:
|
||||
@@ -151,6 +186,10 @@ async def log_api_request(request: Request, call_next):
|
||||
"""
|
||||
request_id = uuid.uuid4().hex
|
||||
request.state.request_id = request_id
|
||||
client_ip, user_agent, browser = _request_client_details(request)
|
||||
request.state.client_ip = client_ip
|
||||
request.state.user_agent = user_agent
|
||||
request.state.browser = browser
|
||||
started = perf_counter()
|
||||
status_code = 500
|
||||
is_logged_api = request.url.path.startswith("/api/") and request.url.path != "/api/health"
|
||||
@@ -167,6 +206,9 @@ async def log_api_request(request: Request, call_next):
|
||||
status_code=status_code,
|
||||
duration_ms=round((perf_counter() - started) * 1000, 1),
|
||||
error_type=error_type,
|
||||
client_ip=client_ip,
|
||||
user_agent=user_agent,
|
||||
browser=browser,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -253,6 +295,9 @@ async def agent_auth_and_session(request: Request, call_next):
|
||||
has_user_message=any(
|
||||
message.get("role") == "user" for message in body.get("messages") or []
|
||||
),
|
||||
client_ip=request.state.client_ip,
|
||||
user_agent=request.state.user_agent,
|
||||
browser=request.state.browser,
|
||||
)
|
||||
# The BFF removes its `method: agent/run` envelope before
|
||||
# forwarding to MAF. A non-empty user message is the reliable
|
||||
@@ -263,6 +308,9 @@ async def agent_auth_and_session(request: Request, call_next):
|
||||
agui_run_id=body.get("runId"),
|
||||
thread_id=body.get("threadId") or body.get("thread_id"),
|
||||
messages=body.get("messages") or [],
|
||||
client_ip=request.state.client_ip,
|
||||
user_agent=request.state.user_agent,
|
||||
browser=request.state.browser,
|
||||
)
|
||||
except Exception as exc:
|
||||
# Parsing/repair must not prevent the agent endpoint from serving;
|
||||
|
||||
@@ -585,6 +585,9 @@ class ChatRunLog(SQLModel, table=True):
|
||||
requested_thread_id: Optional[str] = Field(default=None, index=True)
|
||||
response_thread_id: Optional[str] = Field(default=None, index=True)
|
||||
model: str
|
||||
client_ip: Optional[str] = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
user_agent: Optional[str] = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
browser: Optional[str] = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
user_prompt: Optional[str] = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||
tool_calls: list = Field(
|
||||
default_factory=list,
|
||||
|
||||
Reference in New Issue
Block a user