@@ -401,26 +401,81 @@ def docker_logs(container: str, tail: int) -> str:
401401 return completed .stdout
402402
403403
404+ RETRYABLE_AZURE_LOG_PATTERNS = (
405+ "connecttimeouterror" ,
406+ "connection to management.azure.com timed out" ,
407+ "max retries exceeded" ,
408+ "read timed out" ,
409+ "serviceunavailable" ,
410+ "gateway timeout" ,
411+ "temporarily unavailable" ,
412+ "too many requests" ,
413+ "http 429" ,
414+ "http 5" ,
415+ )
416+ NON_RETRYABLE_AZURE_LOG_PATTERNS = (
417+ "authorizationfailed" ,
418+ "forbidden" ,
419+ "invalidauthenticationtoken" ,
420+ "please run 'az login'" ,
421+ "resource not found" ,
422+ "resourcenotfound" ,
423+ )
424+
425+
426+ def azure_log_error_output (exc : BaseException ) -> str :
427+ if isinstance (exc , subprocess .CalledProcessError ):
428+ return str (exc .output or "" )
429+ if isinstance (exc , subprocess .TimeoutExpired ):
430+ output = exc .output or exc .stderr or ""
431+ if isinstance (output , bytes ):
432+ return output .decode ("utf-8" , errors = "replace" )
433+ return str (output )
434+ return str (exc )
435+
436+
437+ def retryable_azure_log_error (exc : BaseException ) -> bool :
438+ if isinstance (exc , subprocess .TimeoutExpired ):
439+ return True
440+ text = azure_log_error_output (exc ).lower ()
441+ if any (pattern in text for pattern in NON_RETRYABLE_AZURE_LOG_PATTERNS ):
442+ return False
443+ return any (pattern in text for pattern in RETRYABLE_AZURE_LOG_PATTERNS )
444+
445+
404446def azure_containerapp_logs (name : str , resource_group : str , tail : int ) -> str :
405- completed = subprocess .run (
406- [
407- "az" ,
408- "containerapp" ,
409- "logs" ,
410- "show" ,
411- "--name" ,
412- name ,
413- "--resource-group" ,
414- resource_group ,
415- "--tail" ,
416- str (tail ),
417- ],
418- check = True ,
419- stdout = subprocess .PIPE ,
420- stderr = subprocess .STDOUT ,
421- text = True ,
422- )
423- return completed .stdout
447+ command = [
448+ "az" ,
449+ "containerapp" ,
450+ "logs" ,
451+ "show" ,
452+ "--name" ,
453+ name ,
454+ "--resource-group" ,
455+ resource_group ,
456+ "--tail" ,
457+ str (tail ),
458+ ]
459+ attempts = 3
460+ last_exc : BaseException | None = None
461+ for attempt in range (1 , attempts + 1 ):
462+ try :
463+ completed = subprocess .run (
464+ command ,
465+ check = True ,
466+ stdout = subprocess .PIPE ,
467+ stderr = subprocess .STDOUT ,
468+ text = True ,
469+ errors = "replace" ,
470+ )
471+ return completed .stdout
472+ except (subprocess .CalledProcessError , subprocess .TimeoutExpired ) as exc :
473+ last_exc = exc
474+ if attempt >= attempts or not retryable_azure_log_error (exc ):
475+ break
476+ time .sleep (2.0 * (2 ** (attempt - 1 )))
477+ assert last_exc is not None
478+ raise last_exc
424479
425480
426481def collect_logs (
0 commit comments