Skip to content

Commit

Permalink
improve user-visibility of captured task stdout (#506)
Browse files Browse the repository at this point in the history
- when task command fails, exception log reports stdout_file as well as stderr_file
- when task command succeeds, with nonempty stdout that isn't used in WDL outputs, note this in the log (and suggest `>&2`)
  • Loading branch information
mlin committed May 20, 2021
1 parent 151ef87 commit 57642e2
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 4 deletions.
17 changes: 15 additions & 2 deletions WDL/runtime/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,26 @@ class CommandFailed(_RuntimeError):
Path to a file containing the task's standard error
"""

# pyre-ignore
def __init__(self, exit_status: int, stderr_file: str, message: str = "", **kwargs) -> None:
stdout_file: str
"""
Path to a file containing the task's standard output
"""

def __init__(
self,
exit_status: int,
stderr_file: str,
stdout_file: str,
message: str = "",
**kwargs, # pyre-ignore
) -> None:
oom_hint = ", a possible indication that it ran out of memory" if exit_status == 137 else ""
super().__init__(
message or f"task command failed with exit status {exit_status}{oom_hint}", **kwargs
)
self.exit_status = exit_status
self.stderr_file = stderr_file
self.stdout_file = stdout_file


class Terminated(_RuntimeError):
Expand Down Expand Up @@ -131,6 +143,7 @@ def pos_json(pos: SourcePosition) -> Dict[str, Any]:
elif isinstance(exn, CommandFailed):
info["exit_status"] = getattr(exn, "exit_status")
info["stderr_file"] = getattr(exn, "stderr_file")
info["stdout_file"] = getattr(exn, "stdout_file")
elif str(exn):
info["message"] = str(exn)
if hasattr(exn, "job_id"):
Expand Down
24 changes: 23 additions & 1 deletion WDL/runtime/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import shutil
import regex
from typing import Tuple, List, Dict, Optional, Callable, Set, Any, Union
from contextlib import ExitStack
from contextlib import ExitStack, suppress

from .. import Error, Type, Env, Value, StdLib, Tree, Expr, _util
from .._util import (
Expand Down Expand Up @@ -605,6 +605,28 @@ def _eval_task_outputs(
env: Env.Bindings[Value.Base],
container: "runtime.task_container.TaskContainer",
) -> Env.Bindings[Value.Base]:
stdout_file = os.path.join(container.host_dir, "stdout.txt")
with suppress(FileNotFoundError):
if os.path.getsize(stdout_file) > 0:
# If the task produced nonempty stdout that isn't used in the WDL outputs, generate a
# courtesy log message directing user where to find it
stdout_used = False
expr_stack = [outp.expr for outp in task.outputs]
while expr_stack:
expr = expr_stack.pop()
assert isinstance(expr, Expr.Base)
if isinstance(expr, Expr.Apply) and expr.function_name == "stdout":
stdout_used = True
else:
expr_stack.extend(expr.children)
if not stdout_used:
logger.notice( # pyre-ignore
_(
"command stdout unused; consider output `File cmd_out = stdout()`"
" or redirect command to stderr log >&2",
stdout_file=stdout_file,
)
)

# helper to rewrite File/Directory from in-container paths to host paths
def rewriter(v: Union[Value.File, Value.Directory], output_name: str) -> str:
Expand Down
11 changes: 10 additions & 1 deletion WDL/runtime/task_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,20 @@ def run(self, logger: logging.Logger, command: str) -> None:

if not self.success_exit_code(exit_code):
raise CommandFailed(
exit_code, self.host_stderr_txt(), more_info=self.failure_info
exit_code,
self.host_stderr_txt(),
self.host_stdout_txt(),
more_info=self.failure_info,
) if not terminating() else Terminated()

@abstractmethod
def _run(self, logger: logging.Logger, terminating: Callable[[], bool], command: str) -> int:
"""
Implementation-specific: run command in container & return exit status.
Take care to write informative log messages for any backend-specific errorsd. Miniwdl's
outer exception handler will only emit a brief, generic log message about the run failing.
"""
# run command in container & return exit status
raise NotImplementedError()

Expand Down

0 comments on commit 57642e2

Please sign in to comment.