blob: 58890e9664b438736dbb5e675d1a3d2cb618f325 [file] [log] [blame]
Dan Willemsen1e704462016-08-21 15:17:17 -07001// 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"
Dan Willemsenca8feb32017-10-18 13:18:41 -070041 "syscall"
Dan Willemsen1e704462016-08-21 15:17:17 -070042)
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]
Dan Willemsenef661b72019-01-28 20:00:01 -080076type fatalLog struct {
77 error
78}
Dan Willemsen1e704462016-08-21 15:17:17 -070079
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 {
Colin Crossf46e37f2018-03-21 16:25:58 -070090 return fmt.Errorf("Failed to rotate %s to %s. %s", from, newName, err)
Dan Willemsen1e704462016-08-21 15:17:17 -070091 }
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.
Dan Willemsen1e704462016-08-21 15:17:17 -070099func CreateFileWithRotation(filename string, maxCount int) (*os.File, error) {
Dan Willemsenca8feb32017-10-18 13:18:41 -0700100 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
Dan Willemsen1e704462016-08-21 15:17:17 -0700112 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.
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700160func (s *stdLogger) SetVerbose(v bool) *stdLogger {
Dan Willemsen1e704462016-08-21 15:17:17 -0700161 s.verbose = v
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700162 return s
Dan Willemsen1e704462016-08-21 15:17:17 -0700163}
164
165// SetOutput controls where the file-backed log will be saved. It will keep
166// some number of backups of old log files.
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700167func (s *stdLogger) SetOutput(path string) *stdLogger {
Dan Willemsen1e704462016-08-21 15:17:17 -0700168 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 }
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700180 return s
Dan Willemsen1e704462016-08-21 15:17:17 -0700181}
182
183// Close disables logging to the file and closes the file handle.
184func (s *stdLogger) Close() {
185 s.mutex.Lock()
186 defer s.mutex.Unlock()
187 if s.file != nil {
188 s.fileLogger.SetOutput(ioutil.Discard)
189 s.file.Close()
190 s.file = nil
191 }
192}
193
194// Cleanup should be used with defer in your main function. It will close the
195// log file and convert any Fatal panics back to os.Exit(1)
196func (s *stdLogger) Cleanup() {
197 fatal := false
198 p := recover()
199
200 if _, ok := p.(fatalLog); ok {
201 fatal = true
202 p = nil
203 } else if p != nil {
204 s.Println(p)
205 }
206
207 s.Close()
208
209 if p != nil {
210 panic(p)
211 } else if fatal {
212 os.Exit(1)
213 }
214}
215
216// Output writes string to both stderr and the file log.
217func (s *stdLogger) Output(calldepth int, str string) error {
218 s.stderr.Output(calldepth+1, str)
219 return s.fileLogger.Output(calldepth+1, str)
220}
221
222// VerboseOutput is equivalent to Output, but only goes to the file log
223// unless SetVerbose(true) has been called.
224func (s *stdLogger) VerboseOutput(calldepth int, str string) error {
225 if s.verbose {
226 s.stderr.Output(calldepth+1, str)
227 }
228 return s.fileLogger.Output(calldepth+1, str)
229}
230
231// Print prints to both stderr and the file log.
232// Arguments are handled in the manner of fmt.Print.
233func (s *stdLogger) Print(v ...interface{}) {
234 output := fmt.Sprint(v...)
235 s.Output(2, output)
236}
237
238// Printf prints to both stderr and the file log.
239// Arguments are handled in the manner of fmt.Printf.
240func (s *stdLogger) Printf(format string, v ...interface{}) {
241 output := fmt.Sprintf(format, v...)
242 s.Output(2, output)
243}
244
245// Println prints to both stderr and the file log.
246// Arguments are handled in the manner of fmt.Println.
247func (s *stdLogger) Println(v ...interface{}) {
248 output := fmt.Sprintln(v...)
249 s.Output(2, output)
250}
251
252// Verbose is equivalent to Print, but only goes to the file log unless
253// SetVerbose(true) has been called.
254func (s *stdLogger) Verbose(v ...interface{}) {
255 output := fmt.Sprint(v...)
256 s.VerboseOutput(2, output)
257}
258
259// Verbosef is equivalent to Printf, but only goes to the file log unless
260// SetVerbose(true) has been called.
261func (s *stdLogger) Verbosef(format string, v ...interface{}) {
262 output := fmt.Sprintf(format, v...)
263 s.VerboseOutput(2, output)
264}
265
266// Verboseln is equivalent to Println, but only goes to the file log unless
267// SetVerbose(true) has been called.
268func (s *stdLogger) Verboseln(v ...interface{}) {
269 output := fmt.Sprintln(v...)
270 s.VerboseOutput(2, output)
271}
272
273// Fatal is equivalent to Print() followed by a call to panic() that
274// Cleanup will convert to a os.Exit(1).
275func (s *stdLogger) Fatal(v ...interface{}) {
276 output := fmt.Sprint(v...)
277 s.Output(2, output)
Dan Willemsenef661b72019-01-28 20:00:01 -0800278 panic(fatalLog{errors.New(output)})
Dan Willemsen1e704462016-08-21 15:17:17 -0700279}
280
281// Fatalf is equivalent to Printf() followed by a call to panic() that
282// Cleanup will convert to a os.Exit(1).
283func (s *stdLogger) Fatalf(format string, v ...interface{}) {
284 output := fmt.Sprintf(format, v...)
285 s.Output(2, output)
Dan Willemsenef661b72019-01-28 20:00:01 -0800286 panic(fatalLog{errors.New(output)})
Dan Willemsen1e704462016-08-21 15:17:17 -0700287}
288
289// Fatalln is equivalent to Println() followed by a call to panic() that
290// Cleanup will convert to a os.Exit(1).
291func (s *stdLogger) Fatalln(v ...interface{}) {
292 output := fmt.Sprintln(v...)
293 s.Output(2, output)
Dan Willemsenef661b72019-01-28 20:00:01 -0800294 panic(fatalLog{errors.New(output)})
Dan Willemsen1e704462016-08-21 15:17:17 -0700295}
296
297// Panic is equivalent to Print() followed by a call to panic().
298func (s *stdLogger) Panic(v ...interface{}) {
299 output := fmt.Sprint(v...)
300 s.Output(2, output)
301 panic(output)
302}
303
304// Panicf is equivalent to Printf() followed by a call to panic().
305func (s *stdLogger) Panicf(format string, v ...interface{}) {
306 output := fmt.Sprintf(format, v...)
307 s.Output(2, output)
308 panic(output)
309}
310
311// Panicln is equivalent to Println() followed by a call to panic().
312func (s *stdLogger) Panicln(v ...interface{}) {
313 output := fmt.Sprintln(v...)
314 s.Output(2, output)
315 panic(output)
316}