Allow tty output to be timestamped (#10554)

* Spack debug output now includes microsecond-granularity timestamps.
* Timestamps can also be enabled with the `--timestamp` command line argument.
This commit is contained in:
sknigh 2019-02-13 10:14:35 -08:00 committed by Todd Gamblin
parent 2202579685
commit a76c50d1ee
5 changed files with 103 additions and 6 deletions

View File

@ -3,6 +3,7 @@
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
from datetime import datetime
import fcntl import fcntl
import os import os
import struct import struct
@ -18,6 +19,10 @@
_debug = False _debug = False
_verbose = False _verbose = False
_stacktrace = False _stacktrace = False
_timestamp = False
_msg_enabled = True
_warn_enabled = True
_error_enabled = True
indent = " " indent = " "
@ -43,6 +48,65 @@ def set_verbose(flag):
_verbose = flag _verbose = flag
def set_timestamp(flag):
global _timestamp
_timestamp = flag
def set_msg_enabled(flag):
global _msg_enabled
_msg_enabled = flag
def set_warn_enabled(flag):
global _warn_enabled
_warn_enabled = flag
def set_error_enabled(flag):
global _error_enabled
_error_enabled = flag
def msg_enabled():
return _msg_enabled
def warn_enabled():
return _warn_enabled
def error_enabled():
return _error_enabled
class SuppressOutput:
"""Class for disabling output in a scope using 'with' keyword"""
def __init__(self,
msg_enabled=True,
warn_enabled=True,
error_enabled=True):
self._msg_enabled_initial = _msg_enabled
self._warn_enabled_initial = _warn_enabled
self._error_enabled_initial = _error_enabled
self._msg_enabled = msg_enabled
self._warn_enabled = warn_enabled
self._error_enabled = error_enabled
def __enter__(self):
set_msg_enabled(self._msg_enabled)
set_warn_enabled(self._warn_enabled)
set_error_enabled(self._error_enabled)
def __exit__(self, exc_type, exc_val, exc_tb):
set_msg_enabled(self._msg_enabled_initial)
set_warn_enabled(self._warn_enabled_initial)
set_error_enabled(self._error_enabled_initial)
def set_stacktrace(flag): def set_stacktrace(flag):
global _stacktrace global _stacktrace
_stacktrace = flag _stacktrace = flag
@ -65,15 +129,28 @@ def process_stacktrace(countback):
return st_text return st_text
def get_timestamp(force=False):
"""Get a string timestamp"""
if _debug or _timestamp or force:
return datetime.now().strftime("[%Y-%m-%d-%H:%M:%S.%f] ")
else:
return ''
def msg(message, *args, **kwargs): def msg(message, *args, **kwargs):
if not msg_enabled():
return
newline = kwargs.get('newline', True) newline = kwargs.get('newline', True)
st_text = "" st_text = ""
if _stacktrace: if _stacktrace:
st_text = process_stacktrace(2) st_text = process_stacktrace(2)
if newline: if newline:
cprint("@*b{%s==>} %s" % (st_text, cescape(message))) cprint("@*b{%s==>} %s%s" % (
st_text, get_timestamp(), cescape(message)))
else: else:
cwrite("@*b{%s==>} %s" % (st_text, cescape(message))) cwrite("@*b{%s==>} %s%s" % (
st_text, get_timestamp(), cescape(message)))
for arg in args: for arg in args:
print(indent + str(arg)) print(indent + str(arg))
@ -88,8 +165,9 @@ def info(message, *args, **kwargs):
st_text = "" st_text = ""
if _stacktrace: if _stacktrace:
st_text = process_stacktrace(st_countback) st_text = process_stacktrace(st_countback)
cprint("@%s{%s==>} %s" % (format, st_text, cescape(str(message))), cprint("@%s{%s==>} %s%s" % (
stream=stream) format, st_text, get_timestamp(), cescape(str(message))),
stream=stream)
for arg in args: for arg in args:
if wrap: if wrap:
lines = textwrap.wrap( lines = textwrap.wrap(
@ -115,12 +193,18 @@ def debug(message, *args, **kwargs):
def error(message, *args, **kwargs): def error(message, *args, **kwargs):
if not error_enabled():
return
kwargs.setdefault('format', '*r') kwargs.setdefault('format', '*r')
kwargs.setdefault('stream', sys.stderr) kwargs.setdefault('stream', sys.stderr)
info("Error: " + str(message), *args, **kwargs) info("Error: " + str(message), *args, **kwargs)
def warn(message, *args, **kwargs): def warn(message, *args, **kwargs):
if not warn_enabled():
return
kwargs.setdefault('format', '*Y') kwargs.setdefault('format', '*Y')
kwargs.setdefault('stream', sys.stderr) kwargs.setdefault('stream', sys.stderr)
info("Warning: " + str(message), *args, **kwargs) info("Warning: " + str(message), *args, **kwargs)

View File

@ -238,7 +238,7 @@ def fetch(self):
if not spack.config.get('config:verify_ssl'): if not spack.config.get('config:verify_ssl'):
curl_args.append('-k') curl_args.append('-k')
if sys.stdout.isatty(): if sys.stdout.isatty() and tty.msg_enabled():
curl_args.append('-#') # status bar when using a tty curl_args.append('-#') # status bar when using a tty
else: else:
curl_args.append('-sS') # just errors when not. curl_args.append('-sS') # just errors when not.

View File

@ -329,6 +329,9 @@ def make_argument_parser(**kwargs):
parser.add_argument( parser.add_argument(
'-d', '--debug', action='store_true', '-d', '--debug', action='store_true',
help="write out debug logs during compile") help="write out debug logs during compile")
parser.add_argument(
'--timestamp', action='store_true',
help="Add a timestamp to tty output")
parser.add_argument( parser.add_argument(
'--pdb', action='store_true', '--pdb', action='store_true',
help="run spack under the pdb debugger") help="run spack under the pdb debugger")
@ -399,6 +402,9 @@ def setup_main_options(args):
spack.util.debug.register_interrupt_handler() spack.util.debug.register_interrupt_handler()
spack.config.set('config:debug', True, scope='command_line') spack.config.set('config:debug', True, scope='command_line')
if args.timestamp:
tty.set_timestamp(True)
# override lock configuration if passed on command line # override lock configuration if passed on command line
if args.locks is not None: if args.locks is not None:
spack.util.lock.check_lock_safety(spack.paths.prefix) spack.util.lock.check_lock_safety(spack.paths.prefix)

View File

@ -1476,6 +1476,9 @@ def build_process():
# Save the build environment in a file before building. # Save the build environment in a file before building.
dump_environment(self.env_path) dump_environment(self.env_path)
# cache debug settings
debug_enabled = tty.is_debug()
# Spawn a daemon that reads from a pipe and redirects # Spawn a daemon that reads from a pipe and redirects
# everything to log_path # everything to log_path
with log_output(self.log_path, echo, True) as logger: with log_output(self.log_path, echo, True) as logger:
@ -1483,8 +1486,11 @@ def build_process():
self.phases, self._InstallPhase_phases): self.phases, self._InstallPhase_phases):
with logger.force_echo(): with logger.force_echo():
inner_debug = tty.is_debug()
tty.set_debug(debug_enabled)
tty.msg( tty.msg(
"Executing phase: '%s'" % phase_name) "Executing phase: '%s'" % phase_name)
tty.set_debug(inner_debug)
# Redirect stdout and stderr to daemon pipe # Redirect stdout and stderr to daemon pipe
phase = getattr(self, phase_attr) phase = getattr(self, phase_attr)

View File

@ -6,6 +6,7 @@
import argparse import argparse
import os import os
import filecmp import filecmp
import re
from six.moves import builtins from six.moves import builtins
import pytest import pytest
@ -129,7 +130,7 @@ def test_package_output(tmpdir, capsys, install_mockery, mock_fetch):
# make sure that output from the actual package file appears in the # make sure that output from the actual package file appears in the
# right place in the build log. # right place in the build log.
assert "BEFORE INSTALL\n==> './configure'" in out assert re.search(r"BEFORE INSTALL\n==>( \[.+\])? './configure'", out)
assert "'install'\nAFTER INSTALL" in out assert "'install'\nAFTER INSTALL" in out