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. 29 package logger 30 31 import ( 32 "errors" 33 "fmt" 34 "io" 35 "io/ioutil" 36 "log" 37 "os" 38 "path/filepath" 39 "strconv" 40 "sync" 41 "syscall" 42 ) 43 44 type 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] 76 type fatalLog error 77 78 func fileRotation(from, baseName, ext string, cur, max int) error { 79 newName := baseName + "." + strconv.Itoa(cur) + ext 80 81 if _, err := os.Lstat(newName); err == nil { 82 if cur+1 <= max { 83 fileRotation(newName, baseName, ext, cur+1, max) 84 } 85 } 86 87 if err := os.Rename(from, newName); err != nil { 88 return fmt.Errorf("Failed to rotate %s to %s. %s", from, newName, err) 89 } 90 return nil 91 } 92 93 // CreateFileWithRotation returns a new os.File using os.Create, renaming any 94 // existing files to <filename>.#.<ext>, keeping up to maxCount files. 95 // <filename>.1.<ext> is the most recent backup, <filename>.2.<ext> is the 96 // second most recent backup, etc. 97 func CreateFileWithRotation(filename string, maxCount int) (*os.File, error) { 98 lockFileName := filepath.Join(filepath.Dir(filename), ".lock_"+filepath.Base(filename)) 99 lockFile, err := os.OpenFile(lockFileName, os.O_RDWR|os.O_CREATE, 0666) 100 if err != nil { 101 return nil, err 102 } 103 defer lockFile.Close() 104 105 err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX) 106 if err != nil { 107 return nil, err 108 } 109 110 if _, err := os.Lstat(filename); err == nil { 111 ext := filepath.Ext(filename) 112 basename := filename[:len(filename)-len(ext)] 113 if err = fileRotation(filename, basename, ext, 1, maxCount); err != nil { 114 return nil, err 115 } 116 } 117 118 return os.Create(filename) 119 } 120 121 // Recover can be used with defer in a GoRoutine to convert a Fatal panics to 122 // an error that can be handled. 123 func Recover(fn func(err error)) { 124 p := recover() 125 126 if p == nil { 127 return 128 } else if log, ok := p.(fatalLog); ok { 129 fn(error(log)) 130 } else { 131 panic(p) 132 } 133 } 134 135 type stdLogger struct { 136 stderr *log.Logger 137 verbose bool 138 139 fileLogger *log.Logger 140 mutex sync.Mutex 141 file *os.File 142 } 143 144 var _ Logger = &stdLogger{} 145 146 // New creates a new Logger. The out variable sets the destination, commonly 147 // os.Stderr, but it may be a buffer for tests, or a separate log file if 148 // the user doesn't need to see the output. 149 func New(out io.Writer) *stdLogger { 150 return &stdLogger{ 151 stderr: log.New(out, "", log.Ltime), 152 fileLogger: log.New(ioutil.Discard, "", log.Ldate|log.Lmicroseconds|log.Llongfile), 153 } 154 } 155 156 // SetVerbose controls whether Verbose[f|ln] logs to stderr as well as the 157 // file-backed log. 158 func (s *stdLogger) SetVerbose(v bool) *stdLogger { 159 s.verbose = v 160 return s 161 } 162 163 // SetOutput controls where the file-backed log will be saved. It will keep 164 // some number of backups of old log files. 165 func (s *stdLogger) SetOutput(path string) *stdLogger { 166 if f, err := CreateFileWithRotation(path, 5); err == nil { 167 s.mutex.Lock() 168 defer s.mutex.Unlock() 169 170 if s.file != nil { 171 s.file.Close() 172 } 173 s.file = f 174 s.fileLogger.SetOutput(f) 175 } else { 176 s.Fatal(err.Error()) 177 } 178 return s 179 } 180 181 // Close disables logging to the file and closes the file handle. 182 func (s *stdLogger) Close() { 183 s.mutex.Lock() 184 defer s.mutex.Unlock() 185 if s.file != nil { 186 s.fileLogger.SetOutput(ioutil.Discard) 187 s.file.Close() 188 s.file = nil 189 } 190 } 191 192 // Cleanup should be used with defer in your main function. It will close the 193 // log file and convert any Fatal panics back to os.Exit(1) 194 func (s *stdLogger) Cleanup() { 195 fatal := false 196 p := recover() 197 198 if _, ok := p.(fatalLog); ok { 199 fatal = true 200 p = nil 201 } else if p != nil { 202 s.Println(p) 203 } 204 205 s.Close() 206 207 if p != nil { 208 panic(p) 209 } else if fatal { 210 os.Exit(1) 211 } 212 } 213 214 // Output writes string to both stderr and the file log. 215 func (s *stdLogger) Output(calldepth int, str string) error { 216 s.stderr.Output(calldepth+1, str) 217 return s.fileLogger.Output(calldepth+1, str) 218 } 219 220 // VerboseOutput is equivalent to Output, but only goes to the file log 221 // unless SetVerbose(true) has been called. 222 func (s *stdLogger) VerboseOutput(calldepth int, str string) error { 223 if s.verbose { 224 s.stderr.Output(calldepth+1, str) 225 } 226 return s.fileLogger.Output(calldepth+1, str) 227 } 228 229 // Print prints to both stderr and the file log. 230 // Arguments are handled in the manner of fmt.Print. 231 func (s *stdLogger) Print(v ...interface{}) { 232 output := fmt.Sprint(v...) 233 s.Output(2, output) 234 } 235 236 // Printf prints to both stderr and the file log. 237 // Arguments are handled in the manner of fmt.Printf. 238 func (s *stdLogger) Printf(format string, v ...interface{}) { 239 output := fmt.Sprintf(format, v...) 240 s.Output(2, output) 241 } 242 243 // Println prints to both stderr and the file log. 244 // Arguments are handled in the manner of fmt.Println. 245 func (s *stdLogger) Println(v ...interface{}) { 246 output := fmt.Sprintln(v...) 247 s.Output(2, output) 248 } 249 250 // Verbose is equivalent to Print, but only goes to the file log unless 251 // SetVerbose(true) has been called. 252 func (s *stdLogger) Verbose(v ...interface{}) { 253 output := fmt.Sprint(v...) 254 s.VerboseOutput(2, output) 255 } 256 257 // Verbosef is equivalent to Printf, but only goes to the file log unless 258 // SetVerbose(true) has been called. 259 func (s *stdLogger) Verbosef(format string, v ...interface{}) { 260 output := fmt.Sprintf(format, v...) 261 s.VerboseOutput(2, output) 262 } 263 264 // Verboseln is equivalent to Println, but only goes to the file log unless 265 // SetVerbose(true) has been called. 266 func (s *stdLogger) Verboseln(v ...interface{}) { 267 output := fmt.Sprintln(v...) 268 s.VerboseOutput(2, output) 269 } 270 271 // Fatal is equivalent to Print() followed by a call to panic() that 272 // Cleanup will convert to a os.Exit(1). 273 func (s *stdLogger) Fatal(v ...interface{}) { 274 output := fmt.Sprint(v...) 275 s.Output(2, output) 276 panic(fatalLog(errors.New(output))) 277 } 278 279 // Fatalf is equivalent to Printf() followed by a call to panic() that 280 // Cleanup will convert to a os.Exit(1). 281 func (s *stdLogger) Fatalf(format string, v ...interface{}) { 282 output := fmt.Sprintf(format, v...) 283 s.Output(2, output) 284 panic(fatalLog(errors.New(output))) 285 } 286 287 // Fatalln is equivalent to Println() followed by a call to panic() that 288 // Cleanup will convert to a os.Exit(1). 289 func (s *stdLogger) Fatalln(v ...interface{}) { 290 output := fmt.Sprintln(v...) 291 s.Output(2, output) 292 panic(fatalLog(errors.New(output))) 293 } 294 295 // Panic is equivalent to Print() followed by a call to panic(). 296 func (s *stdLogger) Panic(v ...interface{}) { 297 output := fmt.Sprint(v...) 298 s.Output(2, output) 299 panic(output) 300 } 301 302 // Panicf is equivalent to Printf() followed by a call to panic(). 303 func (s *stdLogger) Panicf(format string, v ...interface{}) { 304 output := fmt.Sprintf(format, v...) 305 s.Output(2, output) 306 panic(output) 307 } 308 309 // Panicln is equivalent to Println() followed by a call to panic(). 310 func (s *stdLogger) Panicln(v ...interface{}) { 311 output := fmt.Sprintln(v...) 312 s.Output(2, output) 313 panic(output) 314 } 315