@@ -77,6 +77,13 @@ def get_system_info() -> Dict[str, Any]:
7777from modelq .exceptions import TaskProcessingError , TaskTimeoutError ,RetryTaskException
7878from modelq .app .middleware import Middleware
7979from modelq .app .redis_retry import _RedisWithRetry
80+ from modelq .app .sentry import (
81+ is_sentry_available ,
82+ init_sentry ,
83+ capture_task_exception ,
84+ add_breadcrumb ,
85+ flush_sentry ,
86+ )
8087
8188from pydantic import BaseModel , ValidationError
8289from typing import Optional , Dict , Any , Type
@@ -120,6 +127,14 @@ def __init__(
120127 redis_retry_backoff : float = 2.0 ,
121128 redis_retry_jitter : float = 0.3 ,
122129 inactive_if_worker_boot_fail : bool = False , # Mark worker as unhealthy if before_worker_boot fails
130+ # Sentry integration (optional)
131+ sentry_dsn : Optional [str ] = None ,
132+ sentry_traces_sample_rate : float = 0.0 ,
133+ sentry_profiles_sample_rate : float = 0.0 ,
134+ sentry_environment : Optional [str ] = None ,
135+ sentry_release : Optional [str ] = None ,
136+ sentry_send_default_pii : bool = False ,
137+ sentry_debug : bool = False ,
123138 ** kwargs ,
124139 ):
125140 if redis_client :
@@ -159,6 +174,22 @@ def __init__(
159174 self .inactive_if_worker_boot_fail = inactive_if_worker_boot_fail
160175 self .worker_healthy = True # Track worker health status
161176
177+ # Initialize Sentry if DSN is provided (optional)
178+ self .sentry_enabled = False
179+ if sentry_dsn :
180+ self .sentry_enabled = init_sentry (
181+ dsn = sentry_dsn ,
182+ traces_sample_rate = sentry_traces_sample_rate ,
183+ profiles_sample_rate = sentry_profiles_sample_rate ,
184+ environment = sentry_environment ,
185+ release = sentry_release ,
186+ server_name = self .server_id ,
187+ send_default_pii = sentry_send_default_pii ,
188+ debug = sentry_debug ,
189+ )
190+ if self .sentry_enabled :
191+ logger .info ("Sentry integration enabled for ModelQ" )
192+
162193 # Register this server in Redis (with an initial heartbeat)
163194 self .register_server ()
164195
@@ -664,6 +695,16 @@ def worker_loop(worker_id):
664695 if task .task_name in self .allowed_tasks :
665696 try :
666697 logger .info (f"Worker { worker_id } started processing: { task .task_name } " )
698+
699+ # Add Sentry breadcrumb for task processing
700+ if self .sentry_enabled :
701+ add_breadcrumb (
702+ message = f"Processing task: { task .task_name } " ,
703+ category = "task" ,
704+ level = "info" ,
705+ data = {"task_id" : task .task_id , "worker_id" : worker_id },
706+ )
707+
667708 start_time = time .time ()
668709 self .process_task (task )
669710 end_time = time .time ()
@@ -704,6 +745,9 @@ def worker_loop(worker_id):
704745 )
705746 finally :
706747 self .check_middleware ("before_worker_shutdown" )
748+ # Flush Sentry events before worker shutdown
749+ if self .sentry_enabled :
750+ flush_sentry (timeout = 2.0 )
707751 self .check_middleware ("after_worker_shutdown" )
708752
709753 for i in range (no_of_workers ):
@@ -870,6 +914,26 @@ def process_task(self, task: Task) -> None:
870914
871915 # 2) Webhook (if configured)
872916 self .post_error_to_webhook (task , e )
917+
918+ # 3) Sentry (if enabled) - capture with full traceback
919+ if self .sentry_enabled :
920+ import sys
921+ event_id = capture_task_exception (
922+ exc = e ,
923+ task_id = task .task_id ,
924+ task_name = task .task_name ,
925+ payload = task .payload ,
926+ worker_id = self .server_id ,
927+ additional_context = {
928+ "created_at" : task .created_at ,
929+ "started_at" : task .started_at ,
930+ "additional_params" : task .additional_params ,
931+ },
932+ exc_info = sys .exc_info (), # Pass full traceback with line numbers
933+ )
934+ if event_id :
935+ logger .info (f"Error reported to Sentry: { event_id } " )
936+
873937 logger .error (f"Task { task .task_name } failed with error: { e } " )
874938 raise TaskProcessingError (task .task_name , str (e ))
875939
0 commit comments