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:
		@@ -3,6 +3,7 @@
 | 
			
		||||
#
 | 
			
		||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
 | 
			
		||||
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
import fcntl
 | 
			
		||||
import os
 | 
			
		||||
import struct
 | 
			
		||||
@@ -18,6 +19,10 @@
 | 
			
		||||
_debug = False
 | 
			
		||||
_verbose = False
 | 
			
		||||
_stacktrace = False
 | 
			
		||||
_timestamp = False
 | 
			
		||||
_msg_enabled = True
 | 
			
		||||
_warn_enabled = True
 | 
			
		||||
_error_enabled = True
 | 
			
		||||
indent = "  "
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -43,6 +48,65 @@ def set_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):
 | 
			
		||||
    global _stacktrace
 | 
			
		||||
    _stacktrace = flag
 | 
			
		||||
@@ -65,15 +129,28 @@ def process_stacktrace(countback):
 | 
			
		||||
    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):
 | 
			
		||||
    if not msg_enabled():
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    newline = kwargs.get('newline', True)
 | 
			
		||||
    st_text = ""
 | 
			
		||||
    if _stacktrace:
 | 
			
		||||
        st_text = process_stacktrace(2)
 | 
			
		||||
    if newline:
 | 
			
		||||
        cprint("@*b{%s==>} %s" % (st_text, cescape(message)))
 | 
			
		||||
        cprint("@*b{%s==>} %s%s" % (
 | 
			
		||||
            st_text, get_timestamp(), cescape(message)))
 | 
			
		||||
    else:
 | 
			
		||||
        cwrite("@*b{%s==>} %s" % (st_text, cescape(message)))
 | 
			
		||||
        cwrite("@*b{%s==>} %s%s" % (
 | 
			
		||||
            st_text, get_timestamp(), cescape(message)))
 | 
			
		||||
    for arg in args:
 | 
			
		||||
        print(indent + str(arg))
 | 
			
		||||
 | 
			
		||||
@@ -88,8 +165,9 @@ def info(message, *args, **kwargs):
 | 
			
		||||
    st_text = ""
 | 
			
		||||
    if _stacktrace:
 | 
			
		||||
        st_text = process_stacktrace(st_countback)
 | 
			
		||||
    cprint("@%s{%s==>} %s" % (format, st_text, cescape(str(message))),
 | 
			
		||||
           stream=stream)
 | 
			
		||||
    cprint("@%s{%s==>} %s%s" % (
 | 
			
		||||
        format, st_text, get_timestamp(), cescape(str(message))),
 | 
			
		||||
        stream=stream)
 | 
			
		||||
    for arg in args:
 | 
			
		||||
        if wrap:
 | 
			
		||||
            lines = textwrap.wrap(
 | 
			
		||||
@@ -115,12 +193,18 @@ def debug(message, *args, **kwargs):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def error(message, *args, **kwargs):
 | 
			
		||||
    if not error_enabled():
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    kwargs.setdefault('format', '*r')
 | 
			
		||||
    kwargs.setdefault('stream', sys.stderr)
 | 
			
		||||
    info("Error: " + str(message), *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def warn(message, *args, **kwargs):
 | 
			
		||||
    if not warn_enabled():
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    kwargs.setdefault('format', '*Y')
 | 
			
		||||
    kwargs.setdefault('stream', sys.stderr)
 | 
			
		||||
    info("Warning: " + str(message), *args, **kwargs)
 | 
			
		||||
 
 | 
			
		||||
@@ -238,7 +238,7 @@ def fetch(self):
 | 
			
		||||
        if not spack.config.get('config:verify_ssl'):
 | 
			
		||||
            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
 | 
			
		||||
        else:
 | 
			
		||||
            curl_args.append('-sS')  # just errors when not.
 | 
			
		||||
 
 | 
			
		||||
@@ -329,6 +329,9 @@ def make_argument_parser(**kwargs):
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-d', '--debug', action='store_true',
 | 
			
		||||
        help="write out debug logs during compile")
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--timestamp', action='store_true',
 | 
			
		||||
        help="Add a timestamp to tty output")
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--pdb', action='store_true',
 | 
			
		||||
        help="run spack under the pdb debugger")
 | 
			
		||||
@@ -399,6 +402,9 @@ def setup_main_options(args):
 | 
			
		||||
        spack.util.debug.register_interrupt_handler()
 | 
			
		||||
        spack.config.set('config:debug', True, scope='command_line')
 | 
			
		||||
 | 
			
		||||
    if args.timestamp:
 | 
			
		||||
        tty.set_timestamp(True)
 | 
			
		||||
 | 
			
		||||
    # override lock configuration if passed on command line
 | 
			
		||||
    if args.locks is not None:
 | 
			
		||||
        spack.util.lock.check_lock_safety(spack.paths.prefix)
 | 
			
		||||
 
 | 
			
		||||
@@ -1476,6 +1476,9 @@ def build_process():
 | 
			
		||||
                        # Save the build environment in a file before building.
 | 
			
		||||
                        dump_environment(self.env_path)
 | 
			
		||||
 | 
			
		||||
                        # cache debug settings
 | 
			
		||||
                        debug_enabled = tty.is_debug()
 | 
			
		||||
 | 
			
		||||
                        # Spawn a daemon that reads from a pipe and redirects
 | 
			
		||||
                        # everything to log_path
 | 
			
		||||
                        with log_output(self.log_path, echo, True) as logger:
 | 
			
		||||
@@ -1483,8 +1486,11 @@ def build_process():
 | 
			
		||||
                                    self.phases, self._InstallPhase_phases):
 | 
			
		||||
 | 
			
		||||
                                with logger.force_echo():
 | 
			
		||||
                                    inner_debug = tty.is_debug()
 | 
			
		||||
                                    tty.set_debug(debug_enabled)
 | 
			
		||||
                                    tty.msg(
 | 
			
		||||
                                        "Executing phase: '%s'" % phase_name)
 | 
			
		||||
                                    tty.set_debug(inner_debug)
 | 
			
		||||
 | 
			
		||||
                                # Redirect stdout and stderr to daemon pipe
 | 
			
		||||
                                phase = getattr(self, phase_attr)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
import argparse
 | 
			
		||||
import os
 | 
			
		||||
import filecmp
 | 
			
		||||
import re
 | 
			
		||||
from six.moves import builtins
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    # 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user