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 }