You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
321 lines
9.1 KiB
321 lines
9.1 KiB
// Copyright 2017 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package logger implements a logging package designed for command line
|
|
// utilities. It uses the standard 'log' package and function, but splits
|
|
// output between stderr and a rotating log file.
|
|
//
|
|
// In addition to the standard logger functions, Verbose[f|ln] calls only go to
|
|
// the log file by default, unless SetVerbose(true) has been called.
|
|
//
|
|
// The log file also includes extended date/time/source information, which are
|
|
// omitted from the stderr output for better readability.
|
|
//
|
|
// In order to better handle resource cleanup after a Fatal error, the Fatal
|
|
// functions panic instead of calling os.Exit(). To actually do the cleanup,
|
|
// and prevent the printing of the panic, call defer logger.Cleanup() at the
|
|
// beginning of your main function.
|
|
package logger
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"sync"
|
|
"syscall"
|
|
)
|
|
|
|
type Logger interface {
|
|
// Print* prints to both stderr and the file log.
|
|
// Arguments to Print are handled in the manner of fmt.Print.
|
|
Print(v ...interface{})
|
|
// Arguments to Printf are handled in the manner of fmt.Printf
|
|
Printf(format string, v ...interface{})
|
|
// Arguments to Println are handled in the manner of fmt.Println
|
|
Println(v ...interface{})
|
|
|
|
// Verbose* is equivalent to Print*, but skips stderr unless the
|
|
// logger has been configured in verbose mode.
|
|
Verbose(v ...interface{})
|
|
Verbosef(format string, v ...interface{})
|
|
Verboseln(v ...interface{})
|
|
|
|
// Fatal* is equivalent to Print* followed by a call to panic that
|
|
// can be converted to an error using Recover, or will be converted
|
|
// to a call to os.Exit(1) with a deferred call to Cleanup()
|
|
Fatal(v ...interface{})
|
|
Fatalf(format string, v ...interface{})
|
|
Fatalln(v ...interface{})
|
|
|
|
// Panic is equivalent to Print* followed by a call to panic.
|
|
Panic(v ...interface{})
|
|
Panicf(format string, v ...interface{})
|
|
Panicln(v ...interface{})
|
|
|
|
// Output writes the string to both stderr and the file log.
|
|
Output(calldepth int, str string) error
|
|
}
|
|
|
|
// fatalLog is the type used when Fatal[f|ln]
|
|
type fatalLog struct {
|
|
error
|
|
}
|
|
|
|
func fileRotation(from, baseName, ext string, cur, max int) error {
|
|
newName := baseName + "." + strconv.Itoa(cur) + ext
|
|
|
|
if _, err := os.Lstat(newName); err == nil {
|
|
if cur+1 <= max {
|
|
fileRotation(newName, baseName, ext, cur+1, max)
|
|
}
|
|
}
|
|
|
|
if err := os.Rename(from, newName); err != nil {
|
|
return fmt.Errorf("Failed to rotate %s to %s. %s", from, newName, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateFileWithRotation returns a new os.File using os.Create, renaming any
|
|
// existing files to <filename>.#.<ext>, keeping up to maxCount files.
|
|
// <filename>.1.<ext> is the most recent backup, <filename>.2.<ext> is the
|
|
// second most recent backup, etc.
|
|
func CreateFileWithRotation(filename string, maxCount int) (*os.File, error) {
|
|
lockFileName := filepath.Join(filepath.Dir(filename), ".lock_"+filepath.Base(filename))
|
|
lockFile, err := os.OpenFile(lockFileName, os.O_RDWR|os.O_CREATE, 0666)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer lockFile.Close()
|
|
|
|
err = syscall.Flock(int(lockFile.Fd()), syscall.LOCK_EX)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := os.Lstat(filename); err == nil {
|
|
ext := filepath.Ext(filename)
|
|
basename := filename[:len(filename)-len(ext)]
|
|
if err = fileRotation(filename, basename, ext, 1, maxCount); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return os.Create(filename)
|
|
}
|
|
|
|
// Recover can be used with defer in a GoRoutine to convert a Fatal panics to
|
|
// an error that can be handled.
|
|
func Recover(fn func(err error)) {
|
|
p := recover()
|
|
|
|
if p == nil {
|
|
return
|
|
} else if log, ok := p.(fatalLog); ok {
|
|
fn(error(log))
|
|
} else {
|
|
panic(p)
|
|
}
|
|
}
|
|
|
|
type stdLogger struct {
|
|
stderr *log.Logger
|
|
verbose bool
|
|
|
|
fileLogger *log.Logger
|
|
mutex sync.Mutex
|
|
file *os.File
|
|
}
|
|
|
|
var _ Logger = &stdLogger{}
|
|
|
|
// New creates a new Logger. The out variable sets the destination, commonly
|
|
// os.Stderr, but it may be a buffer for tests, or a separate log file if
|
|
// the user doesn't need to see the output.
|
|
func New(out io.Writer) *stdLogger {
|
|
return &stdLogger{
|
|
stderr: log.New(out, "", log.Ltime),
|
|
fileLogger: log.New(ioutil.Discard, "", log.Ldate|log.Lmicroseconds|log.Llongfile),
|
|
}
|
|
}
|
|
|
|
// SetVerbose controls whether Verbose[f|ln] logs to stderr as well as the
|
|
// file-backed log.
|
|
func (s *stdLogger) SetVerbose(v bool) *stdLogger {
|
|
s.verbose = v
|
|
return s
|
|
}
|
|
|
|
// SetOutput controls where the file-backed log will be saved. It will keep
|
|
// some number of backups of old log files.
|
|
func (s *stdLogger) SetOutput(path string) *stdLogger {
|
|
if f, err := CreateFileWithRotation(path, 5); err == nil {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if s.file != nil {
|
|
s.file.Close()
|
|
}
|
|
s.file = f
|
|
s.fileLogger.SetOutput(f)
|
|
} else {
|
|
s.Fatal(err.Error())
|
|
}
|
|
return s
|
|
}
|
|
|
|
type panicWriter struct{}
|
|
|
|
func (panicWriter) Write([]byte) (int, error) { panic("write to panicWriter") }
|
|
|
|
// Close disables logging to the file and closes the file handle.
|
|
func (s *stdLogger) Close() {
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
if s.file != nil {
|
|
s.fileLogger.SetOutput(panicWriter{})
|
|
s.file.Close()
|
|
s.file = nil
|
|
}
|
|
}
|
|
|
|
// Cleanup should be used with defer in your main function. It will close the
|
|
// log file and convert any Fatal panics back to os.Exit(1)
|
|
func (s *stdLogger) Cleanup() {
|
|
fatal := false
|
|
p := recover()
|
|
|
|
if _, ok := p.(fatalLog); ok {
|
|
fatal = true
|
|
p = nil
|
|
} else if p != nil {
|
|
s.Println(p)
|
|
}
|
|
|
|
s.Close()
|
|
|
|
if p != nil {
|
|
panic(p)
|
|
} else if fatal {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// Output writes string to both stderr and the file log.
|
|
func (s *stdLogger) Output(calldepth int, str string) error {
|
|
s.stderr.Output(calldepth+1, str)
|
|
return s.fileLogger.Output(calldepth+1, str)
|
|
}
|
|
|
|
// VerboseOutput is equivalent to Output, but only goes to the file log
|
|
// unless SetVerbose(true) has been called.
|
|
func (s *stdLogger) VerboseOutput(calldepth int, str string) error {
|
|
if s.verbose {
|
|
s.stderr.Output(calldepth+1, str)
|
|
}
|
|
return s.fileLogger.Output(calldepth+1, str)
|
|
}
|
|
|
|
// Print prints to both stderr and the file log.
|
|
// Arguments are handled in the manner of fmt.Print.
|
|
func (s *stdLogger) Print(v ...interface{}) {
|
|
output := fmt.Sprint(v...)
|
|
s.Output(2, output)
|
|
}
|
|
|
|
// Printf prints to both stderr and the file log.
|
|
// Arguments are handled in the manner of fmt.Printf.
|
|
func (s *stdLogger) Printf(format string, v ...interface{}) {
|
|
output := fmt.Sprintf(format, v...)
|
|
s.Output(2, output)
|
|
}
|
|
|
|
// Println prints to both stderr and the file log.
|
|
// Arguments are handled in the manner of fmt.Println.
|
|
func (s *stdLogger) Println(v ...interface{}) {
|
|
output := fmt.Sprintln(v...)
|
|
s.Output(2, output)
|
|
}
|
|
|
|
// Verbose is equivalent to Print, but only goes to the file log unless
|
|
// SetVerbose(true) has been called.
|
|
func (s *stdLogger) Verbose(v ...interface{}) {
|
|
output := fmt.Sprint(v...)
|
|
s.VerboseOutput(2, output)
|
|
}
|
|
|
|
// Verbosef is equivalent to Printf, but only goes to the file log unless
|
|
// SetVerbose(true) has been called.
|
|
func (s *stdLogger) Verbosef(format string, v ...interface{}) {
|
|
output := fmt.Sprintf(format, v...)
|
|
s.VerboseOutput(2, output)
|
|
}
|
|
|
|
// Verboseln is equivalent to Println, but only goes to the file log unless
|
|
// SetVerbose(true) has been called.
|
|
func (s *stdLogger) Verboseln(v ...interface{}) {
|
|
output := fmt.Sprintln(v...)
|
|
s.VerboseOutput(2, output)
|
|
}
|
|
|
|
// Fatal is equivalent to Print() followed by a call to panic() that
|
|
// Cleanup will convert to a os.Exit(1).
|
|
func (s *stdLogger) Fatal(v ...interface{}) {
|
|
output := fmt.Sprint(v...)
|
|
s.Output(2, output)
|
|
panic(fatalLog{errors.New(output)})
|
|
}
|
|
|
|
// Fatalf is equivalent to Printf() followed by a call to panic() that
|
|
// Cleanup will convert to a os.Exit(1).
|
|
func (s *stdLogger) Fatalf(format string, v ...interface{}) {
|
|
output := fmt.Sprintf(format, v...)
|
|
s.Output(2, output)
|
|
panic(fatalLog{errors.New(output)})
|
|
}
|
|
|
|
// Fatalln is equivalent to Println() followed by a call to panic() that
|
|
// Cleanup will convert to a os.Exit(1).
|
|
func (s *stdLogger) Fatalln(v ...interface{}) {
|
|
output := fmt.Sprintln(v...)
|
|
s.Output(2, output)
|
|
panic(fatalLog{errors.New(output)})
|
|
}
|
|
|
|
// Panic is equivalent to Print() followed by a call to panic().
|
|
func (s *stdLogger) Panic(v ...interface{}) {
|
|
output := fmt.Sprint(v...)
|
|
s.Output(2, output)
|
|
panic(output)
|
|
}
|
|
|
|
// Panicf is equivalent to Printf() followed by a call to panic().
|
|
func (s *stdLogger) Panicf(format string, v ...interface{}) {
|
|
output := fmt.Sprintf(format, v...)
|
|
s.Output(2, output)
|
|
panic(output)
|
|
}
|
|
|
|
// Panicln is equivalent to Println() followed by a call to panic().
|
|
func (s *stdLogger) Panicln(v ...interface{}) {
|
|
output := fmt.Sprintln(v...)
|
|
s.Output(2, output)
|
|
panic(output)
|
|
}
|