github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cmd/roachtest/log.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package main
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"io"
    17  	"io/ioutil"
    18  	"log"
    19  	"os"
    20  	"path/filepath"
    21  
    22  	crdblog "github.com/cockroachdb/cockroach/pkg/util/log"
    23  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    24  )
    25  
    26  // The flags used by the internal loggers.
    27  const logFlags = log.Lshortfile | log.Ltime | log.LUTC
    28  
    29  type loggerConfig struct {
    30  	// prefix, if set, will be applied to lines passed to Printf()/Errorf().
    31  	// It will not be applied to lines written directly to stdout/stderr (not for
    32  	// a particular reason, but because it's not worth the extra code to do so).
    33  	prefix         string
    34  	stdout, stderr io.Writer
    35  }
    36  
    37  type loggerOption interface {
    38  	apply(*loggerConfig)
    39  }
    40  
    41  type logPrefix string
    42  
    43  var _ logPrefix // silence unused lint
    44  
    45  func (p logPrefix) apply(cfg *loggerConfig) {
    46  	cfg.prefix = string(p)
    47  }
    48  
    49  type quietStdoutOption struct {
    50  }
    51  
    52  func (quietStdoutOption) apply(cfg *loggerConfig) {
    53  	cfg.stdout = ioutil.Discard
    54  }
    55  
    56  type quietStderrOption struct {
    57  }
    58  
    59  func (quietStderrOption) apply(cfg *loggerConfig) {
    60  	cfg.stderr = ioutil.Discard
    61  }
    62  
    63  var quietStdout quietStdoutOption
    64  var quietStderr quietStderrOption
    65  
    66  // logger logs to a file in artifacts and stdio simultaneously. This makes it
    67  // possible to observe progress of multiple tests from the terminal (or the
    68  // TeamCity build log, if running in CI), while creating a non-interleaved
    69  // record in the build artifacts.
    70  type logger struct {
    71  	path string
    72  	file *os.File
    73  	// stdoutL and stderrL are the loggers used internally by Printf()/Errorf().
    74  	// They write to stdout/stderr (below), but prefix the messages with
    75  	// logger-specific formatting (file/line, time), plus an optional configurable
    76  	// prefix.
    77  	// stderrL is nil in case stdout == stderr. In that case, stdoutL is always
    78  	// used. We do this in order to take advantage of the logger's internal
    79  	// synchronization so that concurrent Printf()/Error() calls don't step over
    80  	// each other.
    81  	stdoutL, stderrL *log.Logger
    82  	// stdout, stderr are the raw Writers used by the loggers.
    83  	// If path/file is set, then they might Multiwriters, outputting to both a
    84  	// file and os.Stdout.
    85  	// They can be used directly by clients when a writer is required (e.g. when
    86  	// piping output from a subcommand).
    87  	stdout, stderr io.Writer
    88  
    89  	mu struct {
    90  		syncutil.Mutex
    91  		closed bool
    92  	}
    93  }
    94  
    95  // newLogger constructs a new logger object. Not intended for direct
    96  // use. Please use logger.ChildLogger instead.
    97  //
    98  // If path is empty, logs will go to stdout/stderr.
    99  func (cfg *loggerConfig) newLogger(path string) (*logger, error) {
   100  	if path == "" {
   101  		// Log to os.Stdout/Stderr is no other options are passed in.
   102  		stdout := cfg.stdout
   103  		if stdout == nil {
   104  			stdout = os.Stdout
   105  		}
   106  		stderr := cfg.stderr
   107  		if stderr == nil {
   108  			stderr = os.Stderr
   109  		}
   110  		return &logger{
   111  			stdout:  stdout,
   112  			stderr:  stderr,
   113  			stdoutL: log.New(os.Stdout, cfg.prefix, logFlags),
   114  			stderrL: log.New(os.Stderr, cfg.prefix, logFlags),
   115  		}, nil
   116  	}
   117  
   118  	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	f, err := os.Create(path)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	newWriter := func(w io.Writer) io.Writer {
   128  		if w == nil {
   129  			return f
   130  		}
   131  		return io.MultiWriter(f, w)
   132  	}
   133  
   134  	stdout := newWriter(cfg.stdout)
   135  	stderr := newWriter(cfg.stderr)
   136  	stdoutL := log.New(stdout, cfg.prefix, logFlags)
   137  	var stderrL *log.Logger
   138  	if cfg.stdout != cfg.stderr {
   139  		stderrL = log.New(stderr, cfg.prefix, logFlags)
   140  	} else {
   141  		stderrL = stdoutL
   142  	}
   143  	return &logger{
   144  		path:    path,
   145  		file:    f,
   146  		stdout:  stdout,
   147  		stderr:  stderr,
   148  		stdoutL: stdoutL,
   149  		stderrL: stderrL,
   150  	}, nil
   151  }
   152  
   153  type teeOptType bool
   154  
   155  const (
   156  	teeToStdout teeOptType = true
   157  	noTee       teeOptType = false
   158  )
   159  
   160  // rootLogger creates a logger.
   161  //
   162  // If path is empty, all logs go to stdout/stderr regardless of teeOpt.
   163  func rootLogger(path string, teeOpt teeOptType) (*logger, error) {
   164  	var stdout, stderr io.Writer
   165  	if teeOpt == teeToStdout {
   166  		stdout = os.Stdout
   167  		stderr = os.Stderr
   168  	}
   169  	cfg := &loggerConfig{stdout: stdout, stderr: stderr}
   170  	return cfg.newLogger(path)
   171  }
   172  
   173  // close closes the logger. It is idempotent.
   174  func (l *logger) close() {
   175  	l.mu.Lock()
   176  	defer l.mu.Unlock()
   177  	if l.mu.closed {
   178  		return
   179  	}
   180  	l.mu.closed = true
   181  	if l.file != nil {
   182  		l.file.Close()
   183  		l.file = nil
   184  	}
   185  }
   186  
   187  // closed returns true if close() was previously called.
   188  func (l *logger) closed() bool {
   189  	l.mu.Lock()
   190  	defer l.mu.Unlock()
   191  	return l.mu.closed
   192  }
   193  
   194  // ChildLogger constructs a new logger which logs to the specified file. The
   195  // prefix and teeing of stdout/stdout can be controlled by logger options.
   196  // If the parent logger was logging to a file, the new logger will log to a file
   197  // in the same dir called <name>.log.
   198  func (l *logger) ChildLogger(name string, opts ...loggerOption) (*logger, error) {
   199  	// If the parent logger is not logging to a file, then the child will not
   200  	// either. However, the child will write to stdout/stderr with a prefix.
   201  	if l.file == nil {
   202  		p := name + ": "
   203  
   204  		stdoutL := log.New(l.stdout, p, logFlags)
   205  		var stderrL *log.Logger
   206  		if l.stdout != l.stderr {
   207  			stderrL = log.New(l.stderr, p, logFlags)
   208  		} else {
   209  			stderrL = stdoutL
   210  		}
   211  		return &logger{
   212  			path:    l.path,
   213  			stdout:  l.stdout,
   214  			stderr:  l.stderr,
   215  			stdoutL: stdoutL,
   216  			stderrL: stderrL,
   217  		}, nil
   218  	}
   219  
   220  	cfg := &loggerConfig{
   221  		prefix: name + ": ", // might be overridden by opts
   222  		stdout: l.stdout,
   223  		stderr: l.stderr,
   224  	}
   225  	for _, opt := range opts {
   226  		opt.apply(cfg)
   227  	}
   228  
   229  	var path string
   230  	if l.path != "" {
   231  		path = filepath.Join(filepath.Dir(l.path), name+".log")
   232  	}
   233  	return cfg.newLogger(path)
   234  }
   235  
   236  // PrintfCtx prints a message to the logger's stdout. The context's log tags, if
   237  // any, will be prepended to the message. A newline is appended if the last
   238  // character is not already a newline.
   239  func (l *logger) PrintfCtx(ctx context.Context, f string, args ...interface{}) {
   240  	l.PrintfCtxDepth(ctx, 2 /* depth */, f, args...)
   241  }
   242  
   243  // Printf is like PrintfCtx, except it doesn't take a ctx and thus no log tags
   244  // can be passed.
   245  func (l *logger) Printf(f string, args ...interface{}) {
   246  	l.PrintfCtxDepth(context.Background(), 2 /* depth */, f, args...)
   247  }
   248  
   249  // PrintfCtxDepth is like PrintfCtx, except that it allows the caller to control
   250  // which stack frame is reported as the file:line in the message. depth=1 is
   251  // equivalent to PrintfCtx. E.g. pass 2 to ignore the caller's frame.
   252  func (l *logger) PrintfCtxDepth(ctx context.Context, depth int, f string, args ...interface{}) {
   253  	msg := crdblog.MakeMessage(ctx, f, args)
   254  	if err := l.stdoutL.Output(depth+1, msg); err != nil {
   255  		// Changing our interface to return an Error from a logging method seems too
   256  		// onerous. Let's yell to the default logger and if that fails, oh well.
   257  		_ = log.Output(depth+1, fmt.Sprintf("failed to log message: %v: %s", err, msg))
   258  	}
   259  }
   260  
   261  // ErrorfCtx is like PrintfCtx, except the logger outputs to its stderr.
   262  func (l *logger) ErrorfCtx(ctx context.Context, f string, args ...interface{}) {
   263  	l.ErrorfCtxDepth(ctx, 2 /* depth */, f, args...)
   264  }
   265  
   266  func (l *logger) ErrorfCtxDepth(ctx context.Context, depth int, f string, args ...interface{}) {
   267  	msg := crdblog.MakeMessage(ctx, f, args)
   268  	if err := l.stderrL.Output(depth+1, msg); err != nil {
   269  		// Changing our interface to return an Error from a logging method seems too
   270  		// onerous. Let's yell to the default logger and if that fails, oh well.
   271  		_ = log.Output(depth+1, fmt.Sprintf("failed to log error: %v: %s", err, msg))
   272  	}
   273  }
   274  
   275  // Errorf is like ErrorfCtx, except it doesn't take a ctx and thus no log tags
   276  // can be passed.
   277  func (l *logger) Errorf(f string, args ...interface{}) {
   278  	l.ErrorfCtxDepth(context.Background(), 2 /* depth */, f, args...)
   279  }