@@ -131,6 +131,7 @@ def __init__(
131131 sentry_release : Optional [str ] = None ,
132132 sentry_send_default_pii : bool = False ,
133133 sentry_debug : bool = False ,
134+ sentry_ignore_exceptions : Optional [Any ] = None ,
134135 silent : bool = False ,
135136 ** kwargs ,
136137 ):
@@ -164,6 +165,9 @@ def __init__(
164165 self .task_ttl = task_ttl or self .TASK_TTL
165166 self .inactive_if_worker_boot_fail = inactive_if_worker_boot_fail
166167 self .worker_healthy = True # Track worker health status
168+ self .sentry_ignore_exceptions = self ._normalize_sentry_ignore_exceptions (
169+ sentry_ignore_exceptions
170+ )
167171
168172 # Silent mode: suppress all modelq logging output
169173 if silent :
@@ -182,13 +186,50 @@ def __init__(
182186 server_name = self .server_id ,
183187 send_default_pii = sentry_send_default_pii ,
184188 debug = sentry_debug ,
189+ ignore_errors = self .sentry_ignore_exceptions or None ,
185190 )
186191 if self .sentry_enabled :
187192 logger .info ("Sentry integration enabled for ModelQ" )
188193
189194 # Register this server in Redis (with an initial heartbeat)
190195 self .register_server ()
191196
197+ @staticmethod
198+ def _normalize_sentry_ignore_exceptions (ignore_exceptions : Optional [Any ]) -> tuple :
199+ """
200+ Normalize Sentry ignored exception configuration to a tuple of exception types.
201+
202+ Accepts a single exception class or an iterable of exception classes.
203+ """
204+ if ignore_exceptions is None :
205+ return ()
206+
207+ if isinstance (ignore_exceptions , type ):
208+ ignore_exceptions = (ignore_exceptions ,)
209+ else :
210+ ignore_exceptions = tuple (ignore_exceptions )
211+
212+ invalid = [
213+ exc_type
214+ for exc_type in ignore_exceptions
215+ if not isinstance (exc_type , type ) or not issubclass (exc_type , BaseException )
216+ ]
217+ if invalid :
218+ raise TypeError (
219+ "sentry_ignore_exceptions must contain exception classes, "
220+ f"got invalid values: { invalid } "
221+ )
222+
223+ return ignore_exceptions
224+
225+ def _should_ignore_sentry_exception (self , exc : Exception ) -> bool :
226+ """Return True when an exception should fail normally but not report to Sentry."""
227+ return (
228+ exc is not None
229+ and bool (self .sentry_ignore_exceptions )
230+ and isinstance (exc , self .sentry_ignore_exceptions )
231+ )
232+
192233 def _connect_to_redis (
193234 self ,
194235 host : str ,
@@ -723,9 +764,16 @@ def worker_loop(worker_id):
723764 )
724765
725766 except TaskProcessingError as e :
726- logger .error (
727- f"Worker { worker_id } encountered a TaskProcessingError: { e } "
728- )
767+ if self ._should_ignore_sentry_exception (e .__cause__ ):
768+ logger .warning (
769+ "Worker %s encountered an ignored Sentry TaskProcessingError: %s" ,
770+ worker_id ,
771+ e ,
772+ )
773+ else :
774+ logger .error (
775+ f"Worker { worker_id } encountered a TaskProcessingError: { e } "
776+ )
729777 if task .payload .get ("retries" , 0 ) > 0 :
730778 new_task_dict = task .to_dict ()
731779 new_task_dict ["payload" ] = task .original_payload
@@ -926,27 +974,41 @@ def process_task(self, task: Task) -> None:
926974 # 2) Webhook (if configured)
927975 self .post_error_to_webhook (task , e )
928976
929- # 3) Sentry (if enabled) - capture with full traceback
977+ # 3) Sentry (if enabled) - capture with full traceback unless ignored
930978 if self .sentry_enabled :
931- import sys
932- event_id = capture_task_exception (
933- exc = e ,
934- task_id = task .task_id ,
935- task_name = task .task_name ,
936- payload = task .payload ,
937- worker_id = self .server_id ,
938- additional_context = {
939- "created_at" : task .created_at ,
940- "started_at" : task .started_at ,
941- "additional_params" : task .additional_params ,
942- },
943- exc_info = sys .exc_info (), # Pass full traceback with line numbers
944- )
945- if event_id :
946- logger .info (f"Error reported to Sentry: { event_id } " )
979+ if self ._should_ignore_sentry_exception (e ):
980+ logger .info (
981+ "Skipping Sentry capture for ignored exception %s in task %s" ,
982+ type (e ).__name__ ,
983+ task .task_name ,
984+ )
985+ else :
986+ import sys
987+ event_id = capture_task_exception (
988+ exc = e ,
989+ task_id = task .task_id ,
990+ task_name = task .task_name ,
991+ payload = task .payload ,
992+ worker_id = self .server_id ,
993+ additional_context = {
994+ "created_at" : task .created_at ,
995+ "started_at" : task .started_at ,
996+ "additional_params" : task .additional_params ,
997+ },
998+ exc_info = sys .exc_info (), # Pass full traceback with line numbers
999+ )
1000+ if event_id :
1001+ logger .info (f"Error reported to Sentry: { event_id } " )
9471002
948- logger .error (f"Task { task .task_name } failed with error: { e } " )
949- raise TaskProcessingError (task .task_name , str (e ))
1003+ if self ._should_ignore_sentry_exception (e ):
1004+ logger .warning (
1005+ "Task %s failed with ignored Sentry exception: %s" ,
1006+ task .task_name ,
1007+ e ,
1008+ )
1009+ else :
1010+ logger .error (f"Task { task .task_name } failed with error: { e } " )
1011+ raise TaskProcessingError (task .task_name , str (e )) from e
9501012
9511013 finally :
9521014 self .redis_client .srem ("processing_tasks" , task .task_id )
0 commit comments