Home | History | Annotate | Download | only in logger
      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