feat(sentry): enhance Sentry integration with user tracking and error handling improvements

This commit is contained in:
sharath 2025-05-27 10:04:17 +00:00
parent eac741e867
commit 03c95fa346
No known key found for this signature in database
9 changed files with 99 additions and 15 deletions

View File

@ -1,6 +1,7 @@
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
import sentry
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from agentpress.thread_manager import ThreadManager from agentpress.thread_manager import ThreadManager
from services.supabase import DBConnection from services.supabase import DBConnection
@ -12,18 +13,6 @@ from utils.logger import logger
import uuid import uuid
import time import time
from collections import OrderedDict from collections import OrderedDict
import sentry_sdk
import os
sentry_dsn = os.getenv("SENTRY_DSN", None)
if sentry_dsn:
sentry_sdk.init(
dsn=sentry_dsn,
send_default_pii=True,
_experiments={
"enable_logs": True,
},
)
# Import the agent API module # Import the agent API module
from agent import api as agent_api from agent import api as agent_api

View File

@ -1,3 +1,4 @@
import sentry
import asyncio import asyncio
import json import json
import traceback import traceback
@ -56,6 +57,8 @@ async def run_agent_background(
"""Run the agent in the background using Redis for state.""" """Run the agent in the background using Redis for state."""
await initialize() await initialize()
sentry.sentry.set_tag("thread_id", thread_id)
logger.info(f"Starting background agent run: {agent_run_id} for thread: {thread_id} (Instance: {instance_id})") logger.info(f"Starting background agent run: {agent_run_id} for thread: {thread_id} (Instance: {instance_id})")
logger.info(f"🚀 Using model: {model_name} (thinking: {enable_thinking}, reasoning_effort: {reasoning_effort})") logger.info(f"🚀 Using model: {model_name} (thinking: {enable_thinking}, reasoning_effort: {reasoning_effort})")

17
backend/sentry.py Normal file
View File

@ -0,0 +1,17 @@
import sentry_sdk
from sentry_sdk.integrations.dramatiq import DramatiqIntegration
import os
sentry_dsn = os.getenv("SENTRY_DSN", None)
if sentry_dsn:
sentry_sdk.init(
dsn=sentry_dsn,
integrations=[DramatiqIntegration()],
traces_sample_rate=0.1,
send_default_pii=True,
_experiments={
"enable_logs": True,
},
)
sentry = sentry_sdk

View File

@ -1,3 +1,4 @@
import sentry
from fastapi import HTTPException, Request from fastapi import HTTPException, Request
from typing import Optional from typing import Optional
import jwt import jwt
@ -46,6 +47,7 @@ async def get_current_user_id_from_jwt(request: Request) -> str:
headers={"WWW-Authenticate": "Bearer"} headers={"WWW-Authenticate": "Bearer"}
) )
sentry.sentry.set_user({ "id": user_id })
return user_id return user_id
except PyJWTError: except PyJWTError:
@ -119,6 +121,7 @@ async def get_user_id_from_stream_auth(
# For Supabase JWT, we just need to decode and extract the user ID # For Supabase JWT, we just need to decode and extract the user ID
payload = jwt.decode(token, options={"verify_signature": False}) payload = jwt.decode(token, options={"verify_signature": False})
user_id = payload.get('sub') user_id = payload.get('sub')
sentry.sentry.set_user({ "id": user_id })
if user_id: if user_id:
return user_id return user_id
except Exception: except Exception:

View File

@ -24,6 +24,8 @@ RUN npm install
# Copy the frontend code # Copy the frontend code
COPY . . COPY . .
ENV NEXT_PUBLIC_VERCEL_ENV production
RUN npm run build RUN npm run build
EXPOSE 3000 EXPOSE 3000

View File

@ -13,6 +13,7 @@ import { checkApiHealth } from '@/lib/api';
import { MaintenancePage } from '@/components/maintenance/maintenance-page'; import { MaintenancePage } from '@/components/maintenance/maintenance-page';
import { DeleteOperationProvider } from '@/contexts/DeleteOperationContext'; import { DeleteOperationProvider } from '@/contexts/DeleteOperationContext';
import { StatusOverlay } from '@/components/ui/status-overlay'; import { StatusOverlay } from '@/components/ui/status-overlay';
import { VSentry } from '@/components/sentry';
interface DashboardLayoutProps { interface DashboardLayoutProps {
children: React.ReactNode; children: React.ReactNode;
@ -99,6 +100,7 @@ export default function DashboardLayout({ children }: DashboardLayoutProps) {
onOpenChange={setShowMaintenanceAlert} onOpenChange={setShowMaintenanceAlert}
closeable={true} closeable={true}
/> />
<VSentry />
{/* Status overlay for deletion operations */} {/* Status overlay for deletion operations */}
<StatusOverlay /> <StatusOverlay />

View File

@ -0,0 +1,45 @@
const SENTRY_URL = new URL(
process.env.NEXT_PUBLIC_SENTRY_DSN ?? 'https://example.com/abc',
);
const SENTRY_HOST = SENTRY_URL.hostname;
const SENTRY_PROJECT_ID = SENTRY_URL.pathname.split('/').pop();
export const POST = async (req: Request) => {
try {
if (!process.env.NEXT_PUBLIC_SENTRY_DSN) {
return Response.json(
{ error: 'Sentry is not configured' },
{ status: 500 },
);
}
const envelopeBytes = await req.arrayBuffer();
const envelope = new TextDecoder().decode(envelopeBytes);
const piece = envelope.split('\n')[0];
const header = JSON.parse(piece) as { dsn: string };
const dsn = new URL(header.dsn);
const project_id = dsn.pathname.replace('/', '');
if (dsn.hostname !== SENTRY_HOST) {
throw new Error(`Invalid sentry hostname: ${dsn.hostname}`);
}
if (project_id !== SENTRY_PROJECT_ID) {
throw new Error(`Invalid sentry project id: ${project_id}`);
}
const upstream_sentry_url = `https://${SENTRY_HOST}/api/${project_id}/envelope/`;
const response = await fetch(upstream_sentry_url, {
body: envelopeBytes,
method: 'POST',
});
return response;
} catch (e) {
console.error('error tunneling to sentry', e);
return Response.json(
{ error: 'error tunneling to sentry' },
{ status: 500 },
);
}
};

View File

@ -0,0 +1,17 @@
'use client';
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
import { useAuth } from '../AuthProvider';
export const VSentry: React.FC = () => {
const { user } = useAuth();
useEffect(() => {
if (!document) return;
const scope = Sentry.getCurrentScope();
if (!user) scope.setUser(null);
else scope.setUser({ email: user.email, id: user.id });
}, [user]);
return null;
};

View File

@ -1,5 +1,11 @@
export const SentryConfig = { import { consoleLoggingIntegration, type init } from '@sentry/nextjs';
type SentryConfig = Parameters<typeof init>[0];
export const SentryConfig: SentryConfig = {
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1, tracesSampleRate: 0.1,
debug: false, debug: false,
_experiments: { enableLogs: true },
integrations: [consoleLoggingIntegration()],
}; };