@@ -195,7 +195,8 @@ def can_use_s3_repository(self) -> tuple[bool, str]:
195195
196196 else :
197197 if return_code != 0 :
198- logger .error (f"Failed to run pgbackrest: { stderr } " )
198+ extracted_error = self ._extract_error_message (stdout , stderr )
199+ logger .error (f"Failed to run pgbackrest: { extracted_error } " )
199200 return False , FAILED_TO_INITIALIZE_STANZA_ERROR_MESSAGE
200201
201202 for stanza in json .loads (stdout ):
@@ -357,6 +358,41 @@ def _execute_command(
357358 )
358359 return process .returncode , process .stdout .decode (), process .stderr .decode ()
359360
361+ @staticmethod
362+ def _extract_error_message (stdout : str , stderr : str ) -> str :
363+ """Extract key error message from pgBackRest output.
364+
365+ Args:
366+ stdout: Standard output from pgBackRest command.
367+ stderr: Standard error from pgBackRest command.
368+
369+ Returns:
370+ Extracted error message, prioritizing ERROR/WARN lines from output.
371+ """
372+ combined_output = f"{ stdout } \n { stderr } " .strip ()
373+ if not combined_output :
374+ return f"Unknown error occurred. Please check the logs at { PGBACKREST_LOGS_PATH } "
375+
376+ # Extract lines with ERROR or WARN markers from pgBackRest output
377+ error_lines = []
378+ for line in combined_output .splitlines ():
379+ if "ERROR:" in line or "WARN:" in line :
380+ # Clean up the line by removing debug prefixes like "P00 ERROR:"
381+ cleaned = re .sub (r"^.*?(ERROR:|WARN:)" , r"\1" , line ).strip ()
382+ error_lines .append (cleaned )
383+
384+ # If we found error/warning lines, return them joined
385+ if error_lines :
386+ return " " .join (error_lines )
387+
388+ # Otherwise return the last non-empty line from stderr or stdout
389+ if stderr .strip ():
390+ return stderr .strip ().splitlines ()[- 1 ]
391+ if stdout .strip ():
392+ return stdout .strip ().splitlines ()[- 1 ]
393+
394+ return f"Unknown error occurred. Please check the logs at { PGBACKREST_LOGS_PATH } "
395+
360396 def _format_backup_list (self , backup_list ) -> str :
361397 """Formats provided list of backups as a table."""
362398 s3_parameters , _ = self ._retrieve_s3_parameters ()
@@ -405,7 +441,8 @@ def _generate_backup_list_output(self) -> str:
405441 "--output=json" ,
406442 ])
407443 if return_code != 0 :
408- raise ListBackupsError (f"Failed to list backups with error: { stderr } " )
444+ extracted_error = self ._extract_error_message (output , stderr )
445+ raise ListBackupsError (f"Failed to list backups with error: { extracted_error } " )
409446
410447 backups = json .loads (output )[0 ]["backup" ]
411448 for backup in backups :
@@ -476,7 +513,8 @@ def _list_backups(self, show_failed: bool, parse=True) -> dict[str, tuple[str, s
476513 "--output=json" ,
477514 ])
478515 if return_code != 0 :
479- raise ListBackupsError (f"Failed to list backups with error: { stderr } " )
516+ extracted_error = self ._extract_error_message (output , stderr )
517+ raise ListBackupsError (f"Failed to list backups with error: { extracted_error } " )
480518
481519 repository_info = next (iter (json .loads (output )), None )
482520
@@ -511,7 +549,8 @@ def _list_timelines(self) -> dict[str, tuple[str, str]]:
511549 "--output=json" ,
512550 ])
513551 if return_code != 0 :
514- raise ListBackupsError (f"Failed to list repository with error: { stderr } " )
552+ extracted_error = self ._extract_error_message (output , stderr )
553+ raise ListBackupsError (f"Failed to list repository with error: { extracted_error } " )
515554
516555 repository = json .loads (output ).items ()
517556 if repository is None :
@@ -733,15 +772,16 @@ def _is_primary_pgbackrest_service_running(self) -> bool:
733772 if not self .charm .primary_endpoint :
734773 logger .warning ("Failed to contact pgBackRest TLS server: no primary endpoint" )
735774 return False
736- return_code , _ , stderr = self ._execute_command ([
775+ return_code , stdout , stderr = self ._execute_command ([
737776 PGBACKREST_EXECUTABLE ,
738777 "server-ping" ,
739778 "--io-timeout=10" ,
740779 self .charm .primary_endpoint ,
741780 ])
742781 if return_code != 0 :
782+ extracted_error = self ._extract_error_message (stdout , stderr )
743783 logger .warning (
744- f"Failed to contact pgBackRest TLS server on { self .charm .primary_endpoint } with error { stderr } "
784+ f"Failed to contact pgBackRest TLS server on { self .charm .primary_endpoint } with error { extracted_error } "
745785 )
746786 return return_code == 0
747787
@@ -967,7 +1007,8 @@ def _run_backup(
9671007 f"backup/{ self .stanza_name } /{ backup_id } /backup.log" ,
9681008 s3_parameters ,
9691009 )
970- error_message = f"Failed to backup PostgreSQL with error: { stderr } "
1010+ extracted_error = self ._extract_error_message (stdout , stderr )
1011+ error_message = f"Failed to backup PostgreSQL with error: { extracted_error } "
9711012 logger .error (f"Backup failed: { error_message } " )
9721013 event .fail (error_message )
9731014 else :
@@ -1122,7 +1163,7 @@ def _on_restore_action(self, event): # noqa: C901
11221163
11231164 # Remove previous cluster information to make it possible to initialise a new cluster.
11241165 logger .info ("Removing previous cluster information" )
1125- return_code , _ , stderr = self ._execute_command (
1166+ return_code , stdout , stderr = self ._execute_command (
11261167 [
11271168 "charmed-postgresql.patronictl" ,
11281169 "-c" ,
@@ -1134,7 +1175,10 @@ def _on_restore_action(self, event): # noqa: C901
11341175 timeout = 10 ,
11351176 )
11361177 if return_code != 0 :
1137- error_message = f"Failed to remove previous cluster information with error: { stderr } "
1178+ extracted_error = self ._extract_error_message (stdout , stderr )
1179+ error_message = (
1180+ f"Failed to remove previous cluster information with error: { extracted_error } "
1181+ )
11381182 logger .error (f"Restore failed: { error_message } " )
11391183 event .fail (error_message )
11401184 return
0 commit comments