blob: 7d9687becf49aa38f95c67f3ff7e13ff4edbc4e8 [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"
41)
42
43type Logger interface {
44 // Print* prints to both stderr and the file log.
45 // Arguments to Print are handled in the manner of fmt.Print.
46 Print(v ...interface{})
47 // Arguments to Printf are handled in the manner of fmt.Printf
48 Printf(format string, v ...interface{})
49 // Arguments to Println are handled in the manner of fmt.Println
50 Println(v ...interface{})
51
52 // Verbose* is equivalent to Print*, but skips stderr unless the
53 // logger has been configured in verbose mode.
54 Verbose(v ...interface{})
55 Verbosef(format string, v ...interface{})
56 Verboseln(v ...interface{})
57
58 // Fatal* is equivalent to Print* followed by a call to panic that
59 // can be converted to an error using Recover, or will be converted
60 // to a call to os.Exit(1) with a deferred call to Cleanup()
61 Fatal(v ...interface{})
62 Fatalf(format string, v ...interface{})
63 Fatalln(v ...interface{})
64
65 // Panic is equivalent to Print* followed by a call to panic.
66 Panic(v ...interface{})
67 Panicf(format string, v ...interface{})
68 Panicln(v ...interface{})
69
70 // Output writes the string to both stderr and the file log.
71 Output(calldepth int, str string) error
72}
73
74// fatalLog is the type used when Fatal[f|ln]
75type fatalLog error
76
77func fileRotation(from, baseName, ext string, cur, max int) error {
78 newName := baseName + "." + strconv.Itoa(cur) + ext
79
80 if _, err := os.Lstat(newName); err == nil {
81 if cur+1 <= max {
82 fileRotation(newName, baseName, ext, cur+1, max)
83 }
84 }
85
86 if err := os.Rename(from, newName); err != nil {
87 return fmt.Errorf("Failed to rotate", from, "to", newName, ".", err)
88 }
89 return nil
90}
91
92// CreateFileWithRotation returns a new os.File using os.Create, renaming any
93// existing files to <filename>.#.<ext>, keeping up to maxCount files.
94// <filename>.1.<ext> is the most recent backup, <filename>.2.<ext> is the
95// second most recent backup, etc.
96//
97// TODO: This function is not guaranteed to be atomic, if there are multiple
98// users attempting to do the same operation, the result is undefined.
99func CreateFileWithRotation(filename string, maxCount int) (*os.File, error) {
100 if _, err := os.Lstat(filename); err == nil {
101 ext := filepath.Ext(filename)
102 basename := filename[:len(filename)-len(ext)]
103 if err = fileRotation(filename, basename, ext, 1, maxCount); err != nil {
104 return nil, err
105 }
106 }
107
108 return os.Create(filename)
109}
110
111// Recover can be used with defer in a GoRoutine to convert a Fatal panics to
112// an error that can be handled.
113func Recover(fn func(err error)) {
114 p := recover()
115
116 if p == nil {
117 return
118 } else if log, ok := p.(fatalLog); ok {
119 fn(error(log))
120 } else {
121 panic(p)
122 }
123}
124
125type stdLogger struct {
126 stderr *log.Logger
127 verbose bool
128
129 fileLogger *log.Logger
130 mutex sync.Mutex
131 file *os.File
132}
133
134var _ Logger = &stdLogger{}
135
136// New creates a new Logger. The out variable sets the destination, commonly
137// os.Stderr, but it may be a buffer for tests, or a separate log file if
138// the user doesn't need to see the output.
139func New(out io.Writer) *stdLogger {
140 return &stdLogger{
141 stderr: log.New(out, "", log.Ltime),
142 fileLogger: log.New(ioutil.Discard, "", log.Ldate|log.Lmicroseconds|log.Llongfile),
143 }
144}
145
146// SetVerbose controls whether Verbose[f|ln] logs to stderr as well as the
147// file-backed log.
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700148func (s *stdLogger) SetVerbose(v bool) *stdLogger {
Dan Willemsen1e704462016-08-21 15:17:17 -0700149 s.verbose = v
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700150 return s
Dan Willemsen1e704462016-08-21 15:17:17 -0700151}
152
153// SetOutput controls where the file-backed log will be saved. It will keep
154// some number of backups of old log files.
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700155func (s *stdLogger) SetOutput(path string) *stdLogger {
Dan Willemsen1e704462016-08-21 15:17:17 -0700156 if f, err := CreateFileWithRotation(path, 5); err == nil {
157 s.mutex.Lock()
158 defer s.mutex.Unlock()
159
160 if s.file != nil {
161 s.file.Close()
162 }
163 s.file = f
164 s.fileLogger.SetOutput(f)
165 } else {
166 s.Fatal(err.Error())
167 }
Jeff Gastonf1fd45e2017-08-09 18:25:28 -0700168 return s
Dan Willemsen1e704462016-08-21 15:17:17 -0700169}
170
171// Close disables logging to the file and closes the file handle.
172func (s *stdLogger) Close() {
173 s.mutex.Lock()
174 defer s.mutex.Unlock()
175 if s.file != nil {
176 s.fileLogger.SetOutput(ioutil.Discard)
177 s.file.Close()
178 s.file = nil
179 }
180}
181
182// Cleanup should be used with defer in your main function. It will close the
183// log file and convert any Fatal panics back to os.Exit(1)
184func (s *stdLogger) Cleanup() {
185 fatal := false
186 p := recover()
187
188 if _, ok := p.(fatalLog); ok {
189 fatal = true
190 p = nil
191 } else if p != nil {
192 s.Println(p)
193 }
194
195 s.Close()
196
197 if p != nil {
198 panic(p)
199 } else if fatal {
200 os.Exit(1)
201 }
202}
203
204// Output writes string to both stderr and the file log.
205func (s *stdLogger) Output(calldepth int, str string) error {
206 s.stderr.Output(calldepth+1, str)
207 return s.fileLogger.Output(calldepth+1, str)
208}
209
210// VerboseOutput is equivalent to Output, but only goes to the file log
211// unless SetVerbose(true) has been called.
212func (s *stdLogger) VerboseOutput(calldepth int, str string) error {
213 if s.verbose {
214 s.stderr.Output(calldepth+1, str)
215 }
216 return s.fileLogger.Output(calldepth+1, str)
217}
218
219// Print prints to both stderr and the file log.
220// Arguments are handled in the manner of fmt.Print.
221func (s *stdLogger) Print(v ...interface{}) {
222 output := fmt.Sprint(v...)
223 s.Output(2, output)
224}
225
226// Printf prints to both stderr and the file log.
227// Arguments are handled in the manner of fmt.Printf.
228func (s *stdLogger) Printf(format string, v ...interface{}) {
229 output := fmt.Sprintf(format, v...)
230 s.Output(2, output)
231}
232
233// Println prints to both stderr and the file log.
234// Arguments are handled in the manner of fmt.Println.
235func (s *stdLogger) Println(v ...interface{}) {
236 output := fmt.Sprintln(v...)
237 s.Output(2, output)
238}
239
240// Verbose is equivalent to Print, but only goes to the file log unless
241// SetVerbose(true) has been called.
242func (s *stdLogger) Verbose(v ...interface{}) {
243 output := fmt.Sprint(v...)
244 s.VerboseOutput(2, output)
245}
246
247// Verbosef is equivalent to Printf, but only goes to the file log unless
248// SetVerbose(true) has been called.
249func (s *stdLogger) Verbosef(format string, v ...interface{}) {
250 output := fmt.Sprintf(format, v...)
251 s.VerboseOutput(2, output)
252}
253
254// Verboseln is equivalent to Println, but only goes to the file log unless
255// SetVerbose(true) has been called.
256func (s *stdLogger) Verboseln(v ...interface{}) {
257 output := fmt.Sprintln(v...)
258 s.VerboseOutput(2, output)
259}
260
261// Fatal is equivalent to Print() followed by a call to panic() that
262// Cleanup will convert to a os.Exit(1).
263func (s *stdLogger) Fatal(v ...interface{}) {
264 output := fmt.Sprint(v...)
265 s.Output(2, output)
266 panic(fatalLog(errors.New(output)))
267}
268
269// Fatalf is equivalent to Printf() followed by a call to panic() that
270// Cleanup will convert to a os.Exit(1).
271func (s *stdLogger) Fatalf(format string, v ...interface{}) {
272 output := fmt.Sprintf(format, v...)
273 s.Output(2, output)
274 panic(fatalLog(errors.New(output)))
275}
276
277// Fatalln is equivalent to Println() followed by a call to panic() that
278// Cleanup will convert to a os.Exit(1).
279func (s *stdLogger) Fatalln(v ...interface{}) {
280 output := fmt.Sprintln(v...)
281 s.Output(2, output)
282 panic(fatalLog(errors.New(output)))
283}
284
285// Panic is equivalent to Print() followed by a call to panic().
286func (s *stdLogger) Panic(v ...interface{}) {
287 output := fmt.Sprint(v...)
288 s.Output(2, output)
289 panic(output)
290}
291
292// Panicf is equivalent to Printf() followed by a call to panic().
293func (s *stdLogger) Panicf(format string, v ...interface{}) {
294 output := fmt.Sprintf(format, v...)
295 s.Output(2, output)
296 panic(output)
297}
298
299// Panicln is equivalent to Println() followed by a call to panic().
300func (s *stdLogger) Panicln(v ...interface{}) {
301 output := fmt.Sprintln(v...)
302 s.Output(2, output)
303 panic(output)
304}