From 03c95fa3469d5e26c87a5f45b6f5f1d810f2a23b Mon Sep 17 00:00:00 2001 From: sharath <29162020+tnfssc@users.noreply.github.com> Date: Tue, 27 May 2025 10:04:17 +0000 Subject: [PATCH] feat(sentry): enhance Sentry integration with user tracking and error handling improvements --- backend/api.py | 13 +------ backend/run_agent_background.py | 3 ++ backend/sentry.py | 17 +++++++++ backend/utils/auth_utils.py | 5 ++- frontend/Dockerfile | 2 ++ frontend/src/app/(dashboard)/layout.tsx | 2 ++ frontend/src/app/monitoring/route.ts | 45 ++++++++++++++++++++++++ frontend/src/components/sentry/index.tsx | 17 +++++++++ frontend/src/sentry.config.ts | 10 ++++-- 9 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 backend/sentry.py create mode 100644 frontend/src/app/monitoring/route.ts create mode 100644 frontend/src/components/sentry/index.tsx diff --git a/backend/api.py b/backend/api.py index 1e86d0ef..17efab95 100644 --- a/backend/api.py +++ b/backend/api.py @@ -1,6 +1,7 @@ from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse +import sentry from contextlib import asynccontextmanager from agentpress.thread_manager import ThreadManager from services.supabase import DBConnection @@ -12,18 +13,6 @@ from utils.logger import logger import uuid import time 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 from agent import api as agent_api diff --git a/backend/run_agent_background.py b/backend/run_agent_background.py index 035cd145..5c7ee70c 100644 --- a/backend/run_agent_background.py +++ b/backend/run_agent_background.py @@ -1,3 +1,4 @@ +import sentry import asyncio import json import traceback @@ -56,6 +57,8 @@ async def run_agent_background( """Run the agent in the background using Redis for state.""" 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"🚀 Using model: {model_name} (thinking: {enable_thinking}, reasoning_effort: {reasoning_effort})") diff --git a/backend/sentry.py b/backend/sentry.py new file mode 100644 index 00000000..c8647e88 --- /dev/null +++ b/backend/sentry.py @@ -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 diff --git a/backend/utils/auth_utils.py b/backend/utils/auth_utils.py index e2a090b8..83229b04 100644 --- a/backend/utils/auth_utils.py +++ b/backend/utils/auth_utils.py @@ -1,3 +1,4 @@ +import sentry from fastapi import HTTPException, Request from typing import Optional import jwt @@ -45,7 +46,8 @@ async def get_current_user_id_from_jwt(request: Request) -> str: detail="Invalid token payload", headers={"WWW-Authenticate": "Bearer"} ) - + + sentry.sentry.set_user({ "id": user_id }) return user_id 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 payload = jwt.decode(token, options={"verify_signature": False}) user_id = payload.get('sub') + sentry.sentry.set_user({ "id": user_id }) if user_id: return user_id except Exception: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 9fa32c2c..56809ba5 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -24,6 +24,8 @@ RUN npm install # Copy the frontend code COPY . . +ENV NEXT_PUBLIC_VERCEL_ENV production + RUN npm run build EXPOSE 3000 diff --git a/frontend/src/app/(dashboard)/layout.tsx b/frontend/src/app/(dashboard)/layout.tsx index 19f180b1..2b111d24 100644 --- a/frontend/src/app/(dashboard)/layout.tsx +++ b/frontend/src/app/(dashboard)/layout.tsx @@ -13,6 +13,7 @@ import { checkApiHealth } from '@/lib/api'; import { MaintenancePage } from '@/components/maintenance/maintenance-page'; import { DeleteOperationProvider } from '@/contexts/DeleteOperationContext'; import { StatusOverlay } from '@/components/ui/status-overlay'; +import { VSentry } from '@/components/sentry'; interface DashboardLayoutProps { children: React.ReactNode; @@ -99,6 +100,7 @@ export default function DashboardLayout({ children }: DashboardLayoutProps) { onOpenChange={setShowMaintenanceAlert} closeable={true} /> + {/* Status overlay for deletion operations */} diff --git a/frontend/src/app/monitoring/route.ts b/frontend/src/app/monitoring/route.ts new file mode 100644 index 00000000..36021a95 --- /dev/null +++ b/frontend/src/app/monitoring/route.ts @@ -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 }, + ); + } +}; diff --git a/frontend/src/components/sentry/index.tsx b/frontend/src/components/sentry/index.tsx new file mode 100644 index 00000000..43162420 --- /dev/null +++ b/frontend/src/components/sentry/index.tsx @@ -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; +}; diff --git a/frontend/src/sentry.config.ts b/frontend/src/sentry.config.ts index 4b7455a3..323e2bd5 100644 --- a/frontend/src/sentry.config.ts +++ b/frontend/src/sentry.config.ts @@ -1,5 +1,11 @@ -export const SentryConfig = { +import { consoleLoggingIntegration, type init } from '@sentry/nextjs'; + +type SentryConfig = Parameters[0]; + +export const SentryConfig: SentryConfig = { dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - tracesSampleRate: 1, + tracesSampleRate: 0.1, debug: false, + _experiments: { enableLogs: true }, + integrations: [consoleLoggingIntegration()], };