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.
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	"syscall"
42)
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]
76type fatalLog struct {
77	error
78}
79
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 {
90		return fmt.Errorf("Failed to rotate %s to %s. %s", from, newName, err)
91	}
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.
99func CreateFileWithRotation(filename string, maxCount int) (*os.File, error) {
100	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
112	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.
160func (s *stdLogger) SetVerbose(v bool) *stdLogger {
161	s.verbose = v
162	return s
163}
164
165// SetOutput controls where the file-backed log will be saved. It will keep
166// some number of backups of old log files.
167func (s *stdLogger) SetOutput(path string) *stdLogger {
168	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	}
180	return s
181}
182
183type panicWriter struct{}
184
185func (panicWriter) Write([]byte) (int, error) { panic("write to panicWriter") }
186
187// Close disables logging to the file and closes the file handle.
188func (s *stdLogger) Close() {
189	s.mutex.Lock()
190	defer s.mutex.Unlock()
191	if s.file != nil {
192		s.fileLogger.SetOutput(panicWriter{})
193		s.file.Close()
194		s.file = nil
195	}
196}
197
198// Cleanup should be used with defer in your main function. It will close the
199// log file and convert any Fatal panics back to os.Exit(1)
200func (s *stdLogger) Cleanup() {
201	fatal := false
202	p := recover()
203
204	if _, ok := p.(fatalLog); ok {
205		fatal = true
206		p = nil
207	} else if p != nil {
208		s.Println(p)
209	}
210
211	s.Close()
212
213	if p != nil {
214		panic(p)
215	} else if fatal {
216		os.Exit(1)
217	}
218}
219
220// Output writes string to both stderr and the file log.
221func (s *stdLogger) Output(calldepth int, str string) error {
222	s.stderr.Output(calldepth+1, str)
223	return s.fileLogger.Output(calldepth+1, str)
224}
225
226// VerboseOutput is equivalent to Output, but only goes to the file log
227// unless SetVerbose(true) has been called.
228func (s *stdLogger) VerboseOutput(calldepth int, str string) error {
229	if s.verbose {
230		s.stderr.Output(calldepth+1, str)
231	}
232	return s.fileLogger.Output(calldepth+1, str)
233}
234
235// Print prints to both stderr and the file log.
236// Arguments are handled in the manner of fmt.Print.
237func (s *stdLogger) Print(v ...interface{}) {
238	output := fmt.Sprint(v...)
239	s.Output(2, output)
240}
241
242// Printf prints to both stderr and the file log.
243// Arguments are handled in the manner of fmt.Printf.
244func (s *stdLogger) Printf(format string, v ...interface{}) {
245	output := fmt.Sprintf(format, v...)
246	s.Output(2, output)
247}
248
249// Println prints to both stderr and the file log.
250// Arguments are handled in the manner of fmt.Println.
251func (s *stdLogger) Println(v ...interface{}) {
252	output := fmt.Sprintln(v...)
253	s.Output(2, output)
254}
255
256// Verbose is equivalent to Print, but only goes to the file log unless
257// SetVerbose(true) has been called.
258func (s *stdLogger) Verbose(v ...interface{}) {
259	output := fmt.Sprint(v...)
260	s.VerboseOutput(2, output)
261}
262
263// Verbosef is equivalent to Printf, but only goes to the file log unless
264// SetVerbose(true) has been called.
265func (s *stdLogger) Verbosef(format string, v ...interface{}) {
266	output := fmt.Sprintf(format, v...)
267	s.VerboseOutput(2, output)
268}
269
270// Verboseln is equivalent to Println, but only goes to the file log unless
271// SetVerbose(true) has been called.
272func (s *stdLogger) Verboseln(v ...interface{}) {
273	output := fmt.Sprintln(v...)
274	s.VerboseOutput(2, output)
275}
276
277// Fatal is equivalent to Print() followed by a call to panic() that
278// Cleanup will convert to a os.Exit(1).
279func (s *stdLogger) Fatal(v ...interface{}) {
280	output := fmt.Sprint(v...)
281	s.Output(2, output)
282	panic(fatalLog{errors.New(output)})
283}
284
285// Fatalf is equivalent to Printf() followed by a call to panic() that
286// Cleanup will convert to a os.Exit(1).
287func (s *stdLogger) Fatalf(format string, v ...interface{}) {
288	output := fmt.Sprintf(format, v...)
289	s.Output(2, output)
290	panic(fatalLog{errors.New(output)})
291}
292
293// Fatalln is equivalent to Println() followed by a call to panic() that
294// Cleanup will convert to a os.Exit(1).
295func (s *stdLogger) Fatalln(v ...interface{}) {
296	output := fmt.Sprintln(v...)
297	s.Output(2, output)
298	panic(fatalLog{errors.New(output)})
299}
300
301// Panic is equivalent to Print() followed by a call to panic().
302func (s *stdLogger) Panic(v ...interface{}) {
303	output := fmt.Sprint(v...)
304	s.Output(2, output)
305	panic(output)
306}
307
308// Panicf is equivalent to Printf() followed by a call to panic().
309func (s *stdLogger) Panicf(format string, v ...interface{}) {
310	output := fmt.Sprintf(format, v...)
311	s.Output(2, output)
312	panic(output)
313}
314
315// Panicln is equivalent to Println() followed by a call to panic().
316func (s *stdLogger) Panicln(v ...interface{}) {
317	output := fmt.Sprintln(v...)
318	s.Output(2, output)
319	panic(output)
320}
321