Skip to content

Commit

Permalink
Make it easier to see last error details
Browse files Browse the repository at this point in the history
If an error is encountered during execution when not in debug mode, the
full error output is sent to a file. Instead of copying and pasting the
file path to see it, you can now just re-run the previous run command
with the '--last-error' or just '-e' switch to view the error.
  • Loading branch information
markgw committed Oct 13, 2020
1 parent 6b508e0 commit 3622501
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 7 deletions.
18 changes: 17 additions & 1 deletion src/python/pimlico/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def add_arguments(self, parser):
help="Perform a preliminary run of any modules that take multiple datasets into one of "
"their inputs. This means that we will run the module even if not all the datasets "
"are yet available (but at least one is) and mark it as preliminarily completed")
parser.add_argument("--exit-on-error", "-e", action="store_true",
parser.add_argument("--exit-on-error", action="store_true",
help="If an error is encountered while executing a module that causes the whole module "
"execution to fail, output the error and exit. By default, Pimlico will send "
"error output to a file (or print it in debug mode) and continue to execute the "
Expand All @@ -63,6 +63,9 @@ def add_arguments(self, parser):
"fails and a summary at the end of everything), 'end' "
"(send only the final summary). Email sending must be configured: "
"see 'email' command to test")
parser.add_argument("--last-error", "-e", action="store_true",
help="Don't execute, just output the error log from the last execution of the given "
"module(s)")

def run_command(self, pipeline, opts):
debug = opts.debug
Expand Down Expand Up @@ -146,6 +149,19 @@ def run_command(self, pipeline, opts):
if pipeline.local_config_sources:
log.info("Loaded local config from: %s" % ", ".join(pipeline.local_config_sources))

if opts.last_error:
# Just output the last error log for each module
for module_name in module_specs:
module = pipeline[module_name]
last_error_log = module.get_last_log_filename()
if last_error_log is None:
log.info("No error logs found for module {}".format(module_name))
else:
log.info("Outputting error log for {} from {}".format(module_name, last_error_log))
with open(last_error_log, "r") as f:
print(f.read())
sys.exit(0)

# If email report has been requested, check now before we begin that email sending is configured
if opts.email is not None:
try:
Expand Down
50 changes: 44 additions & 6 deletions src/python/pimlico/core/modules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
is loaded.
"""
import re
from builtins import zip
import copy
from operator import itemgetter

from future.utils import raise_from
from past.builtins import basestring
Expand Down Expand Up @@ -1195,6 +1197,30 @@ def is_locked(self):
"""
return os.path.exists(self.lock_path)

def get_log_filenames(self, name="error"):
"""
Get a list of all the log filenames of the given prefix that exist in the module's
output dir. They will be ordered according to their numerical suffixes (i.e. the
order in which they were created).
Returns a list of (filename, num) tuples, where num is the numerical suffix as an int.
"""
dir_name = self.get_module_output_dir(absolute=True)
if not os.path.exists(dir_name):
# No log files, as there's no output dir
return []
# Search for all filenames of the right form
pattern = re.compile(r"{}(\d\d\d).log".format(name))
found = []
for module_file in os.listdir(dir_name):
m = pattern.match(module_file)
if m is not None:
file_number = int(m.group(1))
found.append((module_file, file_number))
found.sort(key=itemgetter(1))
return found

def get_new_log_filename(self, name="error"):
"""
Returns an absolute path that can be used to output a log file for this module. This is used for
Expand All @@ -1207,13 +1233,25 @@ def get_new_log_filename(self, name="error"):
os.makedirs(dir_name)

# Search for a filename that doesn't already exist
pattern = "%s%03d.log"
module_files = os.listdir(dir_name)
file_number = 0
while pattern % (name, file_number) in module_files:
file_number += 1
return os.path.join(dir_name, pattern % (name, file_number))
old_files = self.get_log_filenames(name)
if len(old_files) == 0:
file_number = 0
else:
file_number = old_files[-1][1] + 1
filename = "{}{:03d}.log".format(name, file_number)
return os.path.join(dir_name, filename)

def get_last_log_filename(self, name="error"):
"""
Get the most recent error log that was created by a call to get_new_log_filename().
Returns an absolute path, or None if no matching files are found.
"""
old_files = self.get_log_filenames(name)
if len(old_files) == 0:
return None
else:
return os.path.join(self.get_module_output_dir(absolute=True), old_files[-1][0])

def collect_unexecuted_dependencies(modules):
"""
Expand Down
1 change: 1 addition & 0 deletions src/python/pimlico/core/modules/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ def execute_modules(pipeline, modules, log, force_rerun=False, debug=False, exit
print(debug_mess, file=sys.stderr)
else:
log.error("Full debug info output to %s" % error_filename)
log.error("Append '-e' to run command to view the full log")

# Only send email error report if this was an execution error, not a load error
if type(e) is ModuleExecutionError and email == "modend":
Expand Down

0 comments on commit 3622501

Please sign in to comment.