2323 "URL_ERROR" ,
2424 "URL_UNREACHABLE" ,
2525)
26+ SCHEDULER_CLOUD_RUN_DEDUP_SECONDS = 120
2627
2728
2829def _split_values (raw : str | None ) -> list [str ]:
@@ -135,6 +136,41 @@ def _scheduler_entry_since(
135136 return max (matches ) if matches else fallback
136137
137138
139+ def _is_duplicate_scheduler_failure (
140+ entry : dict [str , Any ],
141+ cloud_run_failures_by_service : dict [str , list [dict [str , Any ]]],
142+ ) -> bool :
143+ scheduler_timestamp = _parse_timestamp (entry .get ("timestamp" ))
144+ job_name = _entry_job_name (entry )
145+ if scheduler_timestamp is None or not job_name :
146+ return False
147+
148+ tolerance = dt .timedelta (seconds = SCHEDULER_CLOUD_RUN_DEDUP_SECONDS )
149+ for service , failures in cloud_run_failures_by_service .items ():
150+ if not any (alias and alias in job_name for alias in _service_job_aliases (service )):
151+ continue
152+ for failure in failures :
153+ cloud_run_timestamp = _parse_timestamp (failure .get ("timestamp" ))
154+ if (
155+ cloud_run_timestamp is not None
156+ and abs (scheduler_timestamp - cloud_run_timestamp ) <= tolerance
157+ ):
158+ return True
159+ return False
160+
161+
162+ def _services_without_success (
163+ services : list [str ],
164+ success_count_by_service : dict [str , int ],
165+ queried_services : set [str ],
166+ ) -> list [str ]:
167+ return [
168+ service
169+ for service in services
170+ if service in queried_services and success_count_by_service .get (service , 0 ) == 0
171+ ]
172+
173+
138174def _run_gcloud (args : list [str ]) -> subprocess .CompletedProcess [str ]:
139175 return subprocess .run (args , text = True , capture_output = True , check = False )
140176
@@ -505,6 +541,9 @@ def main() -> int:
505541 issues : list [str ] = []
506542 details : list [str ] = []
507543 success_count = 0
544+ success_count_by_service : dict [str , int ] = {}
545+ queried_services : set [str ] = set ()
546+ cloud_run_failures_by_service : dict [str , list [dict [str , Any ]]] = {}
508547 service_since_by_name : dict [str , dt .datetime ] = {}
509548
510549 try :
@@ -527,16 +566,26 @@ def main() -> int:
527566 except RuntimeError as exc :
528567 issues .append (f"Cloud Run log query failed for { service } : { exc } " )
529568 continue
569+ queried_services .add (service )
530570 failures = [entry for entry in entries if _is_failure (entry )]
531- success_count += sum (1 for entry in entries if _is_success (entry ))
571+ cloud_run_failures_by_service [service ] = failures
572+ service_success_count = sum (1 for entry in entries if _is_success (entry ))
573+ success_count_by_service [service ] = service_success_count
574+ success_count += service_success_count
532575 if failures :
533576 issues .append (f"{ len (failures )} Cloud Run failure log(s) for { service } " )
534577 details .extend (_summarize (entry ) for entry in failures [:5 ])
535578
536- if services and require_success and success_count == 0 :
537- issues .append (
538- f"no successful Cloud Run request found for { ', ' .join (services )} in the last { lookback_minutes } minutes"
539- )
579+ if services and require_success :
580+ for service in _services_without_success (
581+ services ,
582+ success_count_by_service ,
583+ queried_services ,
584+ ):
585+ issues .append (
586+ f"no successful Cloud Run request found for { service } "
587+ f"in the last { lookback_minutes } minutes"
588+ )
540589
541590 if check_scheduler and scheduler_pattern :
542591 log_filter = f'resource.type="cloud_scheduler_job" AND timestamp >= "{ since_text } "'
@@ -557,6 +606,11 @@ def main() -> int:
557606 entry_since = _scheduler_entry_since (entry , service_since_by_name , since )
558607 if entry_timestamp and entry_timestamp < entry_since :
559608 continue
609+ if _is_duplicate_scheduler_failure (
610+ entry ,
611+ cloud_run_failures_by_service ,
612+ ):
613+ continue
560614 failures .append (entry )
561615 if failures :
562616 issues .append (f"{ len (failures )} Cloud Scheduler failure log(s)" )
0 commit comments