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()],
};