3939 PGBACKREST_CONF_PATH ,
4040 PGBACKREST_CONFIGURATION_FILE ,
4141 PGBACKREST_EXECUTABLE ,
42- PGBACKREST_LOG_LEVEL_STDERR ,
4342 PGBACKREST_LOGROTATE_FILE ,
4443 PGBACKREST_LOGS_PATH ,
4544 POSTGRESQL_DATA_PATH ,
@@ -185,13 +184,7 @@ def can_use_s3_repository(self) -> tuple[bool, str]:
185184
186185 try :
187186 return_code , stdout , stderr = self ._execute_command (
188- [
189- PGBACKREST_EXECUTABLE ,
190- PGBACKREST_CONFIGURATION_FILE ,
191- PGBACKREST_LOG_LEVEL_STDERR ,
192- "info" ,
193- "--output=json" ,
194- ],
187+ [PGBACKREST_EXECUTABLE , PGBACKREST_CONFIGURATION_FILE , "info" , "--output=json" ],
195188 timeout = 30 ,
196189 )
197190 except TimeoutExpired as e :
@@ -203,7 +196,7 @@ def can_use_s3_repository(self) -> tuple[bool, str]:
203196
204197 else :
205198 if return_code != 0 :
206- extracted_error = self ._extract_error_message (stderr )
199+ extracted_error = self ._extract_error_message (stdout , stderr )
207200 logger .error (f"Failed to run pgbackrest: { extracted_error } " )
208201 return False , FAILED_TO_INITIALIZE_STANZA_ERROR_MESSAGE
209202
@@ -367,25 +360,23 @@ def _execute_command(
367360 return process .returncode , process .stdout .decode (), process .stderr .decode ()
368361
369362 @staticmethod
370- def _extract_error_message (stderr : str ) -> str :
371- """Extract key error message from pgBackRest stderr output.
372-
373- Since we standardize all pgBackRest commands to use --log-level-stderr=warn,
374- all errors and warnings are consistently written to stderr. This makes error
375- extraction predictable and avoids potential log duplication issues.
363+ def _extract_error_message (stdout : str , stderr : str ) -> str :
364+ """Extract key error message from pgBackRest output.
376365
377366 Args:
378- stderr: Standard error from pgBackRest command containing errors/warnings.
367+ stdout: Standard output from pgBackRest command.
368+ stderr: Standard error from pgBackRest command.
379369
380370 Returns:
381- Extracted error message from stderr , prioritizing ERROR/WARN lines.
371+ Extracted error message, prioritizing ERROR/WARN lines from output .
382372 """
383- if not stderr .strip ():
373+ combined_output = f"{ stdout } \n { stderr } " .strip ()
374+ if not combined_output :
384375 return f"Unknown error occurred. Please check the logs at { PGBACKREST_LOGS_PATH } "
385376
386- # Extract lines with ERROR or WARN markers from pgBackRest stderr output
377+ # Extract lines with ERROR or WARN markers from pgBackRest output
387378 error_lines = []
388- for line in stderr .splitlines ():
379+ for line in combined_output .splitlines ():
389380 if "ERROR:" in line or "WARN:" in line :
390381 # Clean up the line by removing debug prefixes like "P00 ERROR:"
391382 cleaned = re .sub (r"^.*?(ERROR:|WARN:)" , r"\1" , line ).strip ()
@@ -395,8 +386,13 @@ def _extract_error_message(stderr: str) -> str:
395386 if error_lines :
396387 return "; " .join (error_lines )
397388
398- # Otherwise return the last non-empty line from stderr
399- return stderr .strip ().splitlines ()[- 1 ]
389+ # Otherwise return the last non-empty line from stderr or stdout
390+ if stderr .strip ():
391+ return stderr .strip ().splitlines ()[- 1 ]
392+ if stdout .strip ():
393+ return stdout .strip ().splitlines ()[- 1 ]
394+
395+ return f"Unknown error occurred. Please check the logs at { PGBACKREST_LOGS_PATH } "
400396
401397 def _format_backup_list (self , backup_list ) -> str :
402398 """Formats provided list of backups as a table."""
@@ -442,12 +438,11 @@ def _generate_backup_list_output(self) -> str:
442438 return_code , output , stderr = self ._execute_command ([
443439 PGBACKREST_EXECUTABLE ,
444440 PGBACKREST_CONFIGURATION_FILE ,
445- PGBACKREST_LOG_LEVEL_STDERR ,
446441 "info" ,
447442 "--output=json" ,
448443 ])
449444 if return_code != 0 :
450- extracted_error = self ._extract_error_message (stderr )
445+ extracted_error = self ._extract_error_message (output , stderr )
451446 raise ListBackupsError (f"Failed to list backups with error: { extracted_error } " )
452447
453448 backups = json .loads (output )[0 ]["backup" ]
@@ -515,12 +510,11 @@ def _list_backups(self, show_failed: bool, parse=True) -> dict[str, tuple[str, s
515510 return_code , output , stderr = self ._execute_command ([
516511 PGBACKREST_EXECUTABLE ,
517512 PGBACKREST_CONFIGURATION_FILE ,
518- PGBACKREST_LOG_LEVEL_STDERR ,
519513 "info" ,
520514 "--output=json" ,
521515 ])
522516 if return_code != 0 :
523- extracted_error = self ._extract_error_message (stderr )
517+ extracted_error = self ._extract_error_message (output , stderr )
524518 raise ListBackupsError (f"Failed to list backups with error: { extracted_error } " )
525519
526520 repository_info = next (iter (json .loads (output )), None )
@@ -551,13 +545,12 @@ def _list_timelines(self) -> dict[str, tuple[str, str]]:
551545 return_code , output , stderr = self ._execute_command ([
552546 PGBACKREST_EXECUTABLE ,
553547 PGBACKREST_CONFIGURATION_FILE ,
554- PGBACKREST_LOG_LEVEL_STDERR ,
555548 "repo-ls" ,
556549 "--recurse" ,
557550 "--output=json" ,
558551 ])
559552 if return_code != 0 :
560- extracted_error = self ._extract_error_message (stderr )
553+ extracted_error = self ._extract_error_message (output , stderr )
561554 raise ListBackupsError (f"Failed to list repository with error: { extracted_error } " )
562555
563556 repository = json .loads (output ).items ()
@@ -670,7 +663,6 @@ def _initialise_stanza(self, event: HookEvent) -> bool:
670663 return_code , _ , stderr = self ._execute_command ([
671664 PGBACKREST_EXECUTABLE ,
672665 PGBACKREST_CONFIGURATION_FILE ,
673- PGBACKREST_LOG_LEVEL_STDERR ,
674666 f"--stanza={ self .stanza_name } " ,
675667 "stanza-create" ,
676668 ])
@@ -729,15 +721,14 @@ def check_stanza(self) -> bool:
729721 return_code , stdout , stderr = self ._execute_command ([
730722 PGBACKREST_EXECUTABLE ,
731723 PGBACKREST_CONFIGURATION_FILE ,
732- PGBACKREST_LOG_LEVEL_STDERR ,
733724 f"--stanza={ self .stanza_name } " ,
734725 "check" ,
735726 ])
736727 if return_code == PGBACKREST_ARCHIVE_TIMEOUT_ERROR_CODE :
737728 # Raise an error if the archive command timeouts, so the user has the possibility
738729 # to fix network issues and call juju resolve to re-trigger the hook that calls
739730 # this method.
740- extracted_error = self ._extract_error_message (stderr )
731+ extracted_error = self ._extract_error_message (stdout , stderr )
741732 logger .error (
742733 f"error: { extracted_error } - please fix the error and call juju resolve on this unit"
743734 )
@@ -796,13 +787,12 @@ def _is_primary_pgbackrest_service_running(self) -> bool:
796787 return False
797788 return_code , stdout , stderr = self ._execute_command ([
798789 PGBACKREST_EXECUTABLE ,
799- PGBACKREST_LOG_LEVEL_STDERR ,
800790 "server-ping" ,
801791 "--io-timeout=10" ,
802792 self .charm .primary_endpoint ,
803793 ])
804794 if return_code != 0 :
805- extracted_error = self ._extract_error_message (stderr )
795+ extracted_error = self ._extract_error_message (stdout , stderr )
806796 logger .warning (
807797 f"Failed to contact pgBackRest TLS server on { self .charm .primary_endpoint } with error { extracted_error } "
808798 )
@@ -994,8 +984,8 @@ def _run_backup(
994984 command = [
995985 PGBACKREST_EXECUTABLE ,
996986 PGBACKREST_CONFIGURATION_FILE ,
997- PGBACKREST_LOG_LEVEL_STDERR ,
998987 f"--stanza={ self .stanza_name } " ,
988+ "--log-level-console=debug" ,
999989 f"--type={ BACKUP_TYPE_OVERRIDES [backup_type ]} " ,
1000990 "backup" ,
1001991 ]
@@ -1030,7 +1020,7 @@ def _run_backup(
10301020 f"backup/{ self .stanza_name } /{ backup_id } /backup.log" ,
10311021 s3_parameters ,
10321022 )
1033- extracted_error = self ._extract_error_message (stderr )
1023+ extracted_error = self ._extract_error_message (stdout , stderr )
10341024 error_message = f"Failed to backup PostgreSQL with error: { extracted_error } "
10351025 logger .error (f"Backup failed: { error_message } " )
10361026 event .fail (error_message )
@@ -1198,7 +1188,7 @@ def _on_restore_action(self, event): # noqa: C901
11981188 timeout = 10 ,
11991189 )
12001190 if return_code != 0 :
1201- extracted_error = self ._extract_error_message (stderr )
1191+ extracted_error = self ._extract_error_message (stdout , stderr )
12021192 error_message = (
12031193 f"Failed to remove previous cluster information with error: { extracted_error } "
12041194 )
0 commit comments