1// Copyright 2017 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package logger implements a logging package designed for command line 16// utilities. It uses the standard 'log' package and function, but splits 17// output between stderr and a rotating log file. 18// 19// In addition to the standard logger functions, Verbose[f|ln] calls only go to 20// the log file by default, unless SetVerbose(true) has been called. 21// 22// The log file also includes extended date/time/source information, which are 23// omitted from the stderr output for better readability. 24// 25// In order to better handle resource cleanup after a Fatal error, the Fatal 26// functions panic instead of calling os.Exit(). To actually do the cleanup, 27// and prevent the printing of the panic, call defer logger.Cleanup() at the 28// beginning of your main function. 29package logger 30 31import ( 32 "errors" 33 "fmt" 34 "io" 35 "io/ioutil" 36 "log" 37 "os" 38 "path/filepath" 39 "strconv" 40 "sync" 41 "syscall" 42) 43 44type Logger interface { 45 // Print* prints to both stderr and the file log. 46 // Arguments to Print are handled in the manner of fmt.Print. 47 Print(v ...interface{}) 48 // Arguments to Printf are handled in the manner of fmt.Printf 49 Printf(format string, v ...interface{}) 50 // Arguments to Println are handled in the manner of fmt.Println 51 Println(v ...interface{}) 52 53 // Verbose* is equivalent to Print*, but skips stderr unless the 54 // logger has been configured in verbose mode. 55 Verbose(v ...interface{}) 56 Verbosef(format string, v ...interface{}) 57 Verboseln(v ...interface{}) 58 59 // Fatal* is equivalent to Print* followed by a call to panic that 60 // can be converted to an error using Recover, or will be converted 61 // to a call to os.Exit(1) with a deferred call to Cleanup() 62 Fatal(v ...interface{}) 63 Fatalf(format string, v ...interface{}) 64 Fatalln(v ...interface{}) 65 66 // Panic is equivalent to Print* followed by a call to panic. 67 Panic(v ...interface{}) 68 Panicf(format string, v ...interface{}) 69 Panicln(v ...interface{}) 70 71 // Output writes the string to both stderr and the file log. 72 Output(calldepth int, str string) error 73} 74 75// fatalLog is the type used when Fatal[f|ln] 76type fatalLog struct { 77 error 78} 79 80func fileRotation(from, baseName, ext string, cur, max int) error { 81 newName := baseName + "." + strconv.Itoa(cur) + ext 82 83 if _, err := os.Lstat(newName); err == nil { 84 if cur+1 <= max { 85 fileRotation(newName, baseName, ext, cur+1, max) 86 } 87 } 88 89 if err := os.Rename(from, newName); err != nil { 90 return fmt.Errorf("Failed to rotate %s to %s. %s", from, newName, err) 91 } 92 return nil 93} 94 95// CreateFileWithRotation returns a new os.File using os.Create, renaming any 96// existing files to <filename>.#.<ext>, keeping up to maxCount files. 97// <filename>.1.<ext> is the most recent backup, <filename>.2.<ext> is the 98// second most recent backup, etc. 99func CreateFileWithRotation(filename string, maxCount int) (*os.File, error) { 100 lockFileName := filepath.Join(filepath.Dir(filename), ".lock_"+filepath.Base(filename)) 101 lockFile, err := os.OpenFile(lockFileName, os.O_RDWR|os.O_CREATE, 0666) 102 if err != nil { 103 return nil, err 104 } 105 defer lockFile.Close() 106 107 err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX) 108 if err != nil { 109 return nil, err 110 } 111 112 if _, err := os.Lstat(filename); err == nil { 113 ext := filepath.Ext(filename) 114 basename := filename[:len(filename)-len(ext)] 115 if err = fileRotation(filename, basename, ext, 1, maxCount); err != nil { 116 return nil, err 117 } 118 } 119 120 return os.Create(filename) 121} 122 123// Recover can be used with defer in a GoRoutine to convert a Fatal panics to 124// an error that can be handled. 125func Recover(fn func(err error)) { 126 p := recover() 127 128 if p == nil { 129 return 130 } else if log, ok := p.(fatalLog); ok { 131 fn(error(log)) 132 } else { 133 panic(p) 134 } 135} 136 137type stdLogger struct { 138 stderr *log.Logger 139 verbose bool 140 141 fileLogger *log.Logger 142 mutex sync.Mutex 143 file *os.File 144} 145 146var _ Logger = &stdLogger{} 147 148// New creates a new Logger. The out variable sets the destination, commonly 149// os.Stderr, but it may be a buffer for tests, or a separate log file if 150// the user doesn't need to see the output. 151func New(out io.Writer) *stdLogger { 152 return &stdLogger{ 153 stderr: log.New(out, "", log.Ltime), 154 fileLogger: log.New(ioutil.Discard, "", log.Ldate|log.Lmicroseconds|log.Llongfile), 155 } 156} 157 158// SetVerbose controls whether Verbose[f|ln] logs to stderr as well as the 159// file-backed log. 160func (s *stdLogger) SetVerbose(v bool) *stdLogger { 161 s.verbose = v 162 return s 163} 164 165// SetOutput controls where the file-backed log will be saved. It will keep 166// some number of backups of old log files. 167func (s *stdLogger) SetOutput(path string) *stdLogger { 168 if f, err := CreateFileWithRotation(path, 5); err == nil { 169 s.mutex.Lock() 170 defer s.mutex.Unlock() 171 172 if s.file != nil { 173 s.file.Close() 174 } 175 s.file = f 176 s.fileLogger.SetOutput(f) 177 } else { 178 s.Fatal(err.Error()) 179 } 180 return s 181} 182 183type panicWriter struct{} 184 185func (panicWriter) Write([]byte) (int, error) { panic("write to panicWriter") } 186 187// Close disables logging to the file and closes the file handle. 188func (s *stdLogger) Close() { 189 s.mutex.Lock() 190 defer s.mutex.Unlock() 191 if s.file != nil { 192 s.fileLogger.SetOutput(panicWriter{}) 193 s.file.Close() 194 s.file = nil 195 } 196} 197 198// Cleanup should be used with defer in your main function. It will close the 199// log file and convert any Fatal panics back to os.Exit(1) 200func (s *stdLogger) Cleanup() { 201 fatal := false 202 p := recover() 203 204 if _, ok := p.(fatalLog); ok { 205 fatal = true 206 p = nil 207 } else if p != nil { 208 s.Println(p) 209 } 210 211 s.Close() 212 213 if p != nil { 214 panic(p) 215 } else if fatal { 216 os.Exit(1) 217 } 218} 219 220// Output writes string to both stderr and the file log. 221func (s *stdLogger) Output(calldepth int, str string) error { 222 s.stderr.Output(calldepth+1, str) 223 return s.fileLogger.Output(calldepth+1, str) 224} 225 226// VerboseOutput is equivalent to Output, but only goes to the file log 227// unless SetVerbose(true) has been called. 228func (s *stdLogger) VerboseOutput(calldepth int, str string) error { 229 if s.verbose { 230 s.stderr.Output(calldepth+1, str) 231 } 232 return s.fileLogger.Output(calldepth+1, str) 233} 234 235// Print prints to both stderr and the file log. 236// Arguments are handled in the manner of fmt.Print. 237func (s *stdLogger) Print(v ...interface{}) { 238 output := fmt.Sprint(v...) 239 s.Output(2, output) 240} 241 242// Printf prints to both stderr and the file log. 243// Arguments are handled in the manner of fmt.Printf. 244func (s *stdLogger) Printf(format string, v ...interface{}) { 245 output := fmt.Sprintf(format, v...) 246 s.Output(2, output) 247} 248 249// Println prints to both stderr and the file log. 250// Arguments are handled in the manner of fmt.Println. 251func (s *stdLogger) Println(v ...interface{}) { 252 output := fmt.Sprintln(v...) 253 s.Output(2, output) 254} 255 256// Verbose is equivalent to Print, but only goes to the file log unless 257// SetVerbose(true) has been called. 258func (s *stdLogger) Verbose(v ...interface{}) { 259 output := fmt.Sprint(v...) 260 s.VerboseOutput(2, output) 261} 262 263// Verbosef is equivalent to Printf, but only goes to the file log unless 264// SetVerbose(true) has been called. 265func (s *stdLogger) Verbosef(format string, v ...interface{}) { 266 output := fmt.Sprintf(format, v...) 267 s.VerboseOutput(2, output) 268} 269 270// Verboseln is equivalent to Println, but only goes to the file log unless 271// SetVerbose(true) has been called. 272func (s *stdLogger) Verboseln(v ...interface{}) { 273 output := fmt.Sprintln(v...) 274 s.VerboseOutput(2, output) 275} 276 277// Fatal is equivalent to Print() followed by a call to panic() that 278// Cleanup will convert to a os.Exit(1). 279func (s *stdLogger) Fatal(v ...interface{}) { 280 output := fmt.Sprint(v...) 281 s.Output(2, output) 282 panic(fatalLog{errors.New(output)}) 283} 284 285// Fatalf is equivalent to Printf() followed by a call to panic() that 286// Cleanup will convert to a os.Exit(1). 287func (s *stdLogger) Fatalf(format string, v ...interface{}) { 288 output := fmt.Sprintf(format, v...) 289 s.Output(2, output) 290 panic(fatalLog{errors.New(output)}) 291} 292 293// Fatalln is equivalent to Println() followed by a call to panic() that 294// Cleanup will convert to a os.Exit(1). 295func (s *stdLogger) Fatalln(v ...interface{}) { 296 output := fmt.Sprintln(v...) 297 s.Output(2, output) 298 panic(fatalLog{errors.New(output)}) 299} 300 301// Panic is equivalent to Print() followed by a call to panic(). 302func (s *stdLogger) Panic(v ...interface{}) { 303 output := fmt.Sprint(v...) 304 s.Output(2, output) 305 panic(output) 306} 307 308// Panicf is equivalent to Printf() followed by a call to panic(). 309func (s *stdLogger) Panicf(format string, v ...interface{}) { 310 output := fmt.Sprintf(format, v...) 311 s.Output(2, output) 312 panic(output) 313} 314 315// Panicln is equivalent to Println() followed by a call to panic(). 316func (s *stdLogger) Panicln(v ...interface{}) { 317 output := fmt.Sprintln(v...) 318 s.Output(2, output) 319 panic(output) 320} 321