diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a062049654a..84980597a10 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -268,11 +268,6 @@ "Comment": "v1.2.0", "Rev": "96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0" }, - { - "ImportPath": "gopkg.in/natefinch/lumberjack.v2", - "Comment": "v1.0-12-gd28785c", - "Rev": "d28785c2f27cd682d872df46ccd8232843629f54" - }, { "ImportPath": "gopkg.in/tomb.v1", "Rev": "dd632973f1e7218eb1089048e0798ec9ae7dceb8" @@ -281,5 +276,6 @@ "ImportPath": "github.com/chriscool/go-sleep", "Rev": "743ab5f1bb487edf1772bc29ca0bdf572b40785e" } + ] } diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/.gitignore b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/.gitignore deleted file mode 100644 index 836562412fe..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/LICENSE b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/LICENSE deleted file mode 100644 index c3d4cc307d2..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Nate Finch - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/README.md b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/README.md deleted file mode 100644 index f7d8132dea2..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/README.md +++ /dev/null @@ -1,166 +0,0 @@ -# lumberjack [![GoDoc](https://godoc.org/gopkg.in/natefinch/lumberjack.v2?status.png)](https://godoc.org/gopkg.in/natefinch/lumberjack.v2) [![Build Status](https://drone.io/github.com/natefinch/lumberjack/status.png)](https://drone.io/github.com/natefinch/lumberjack/latest) [![Build status](https://ci.appveyor.com/api/projects/status/00gchpxtg4gkrt5d)](https://ci.appveyor.com/project/natefinch/lumberjack) - -### Lumberjack is a Go package for writing logs to rolling files. - -Package lumberjack provides a rolling logger. - -Note that this is v2.0 of lumberjack, and should be imported using gopkg.in -thusly: - - import "gopkg.in/natefinch/lumberjack.v2" - -The package name remains simply lumberjack, and the code resides at -https://github.com/natefinch/lumberjack under the v2.0 branch. - -Lumberjack is intended to be one part of a logging infrastructure. -It is not an all-in-one solution, but instead is a pluggable -component at the bottom of the logging stack that simply controls the files -to which logs are written. - -Lumberjack plays well with any logging package that can write to an -io.Writer, including the standard library's log package. - -Lumberjack assumes that only one process is writing to the output files. -Using the same lumberjack configuration from multiple processes on the same -machine will result in improper behavior. - - -**Example** - -To use lumberjack with the standard library's log package, just pass it into the SetOutput function when your application starts. - -Code: - -```go -log.SetOutput(&lumberjack.Logger{ - Filename: "/var/log/myapp/foo.log", - MaxSize: 500, // megabytes - MaxBackups: 3, - MaxAge: 28, //days -}) -``` - - - -## type Logger -``` go -type Logger struct { - // Filename is the file to write logs to. Backup log files will be retained - // in the same directory. It uses -lumberjack.log in - // os.TempDir() if empty. - Filename string `json:"filename" yaml:"filename"` - - // MaxSize is the maximum size in megabytes of the log file before it gets - // rotated. It defaults to 100 megabytes. - MaxSize int `json:"maxsize" yaml:"maxsize"` - - // MaxAge is the maximum number of days to retain old log files based on the - // timestamp encoded in their filename. Note that a day is defined as 24 - // hours and may not exactly correspond to calendar days due to daylight - // savings, leap seconds, etc. The default is not to remove old log files - // based on age. - MaxAge int `json:"maxage" yaml:"maxage"` - - // MaxBackups is the maximum number of old log files to retain. The default - // is to retain all old log files (though MaxAge may still cause them to get - // deleted.) - MaxBackups int `json:"maxbackups" yaml:"maxbackups"` - - // LocalTime determines if the time used for formatting the timestamps in - // backup files is the computer's local time. The default is to use UTC - // time. - LocalTime bool `json:"localtime" yaml:"localtime"` - // contains filtered or unexported fields -} -``` -Logger is an io.WriteCloser that writes to the specified filename. - -Logger opens or creates the logfile on first Write. If the file exists and -is less than MaxSize megabytes, lumberjack will open and append to that file. -If the file exists and its size is >= MaxSize megabytes, the file is renamed -by putting the current time in a timestamp in the name immediately before the -file's extension (or the end of the filename if there's no extension). A new -log file is then created using original filename. - -Whenever a write would cause the current log file exceed MaxSize megabytes, -the current file is closed, renamed, and a new log file created with the -original name. Thus, the filename you give Logger is always the "current" log -file. - -### Cleaning Up Old Log Files -Whenever a new logfile gets created, old log files may be deleted. The most -recent files according to the encoded timestamp will be retained, up to a -number equal to MaxBackups (or all of them if MaxBackups is 0). Any files -with an encoded timestamp older than MaxAge days are deleted, regardless of -MaxBackups. Note that the time encoded in the timestamp is the rotation -time, which may differ from the last time that file was written to. - -If MaxBackups and MaxAge are both 0, no old log files will be deleted. - - - - - - - - - - - -### func (\*Logger) Close -``` go -func (l *Logger) Close() error -``` -Close implements io.Closer, and closes the current logfile. - - - -### func (\*Logger) Rotate -``` go -func (l *Logger) Rotate() error -``` -Rotate causes Logger to close the existing log file and immediately create a -new one. This is a helper function for applications that want to initiate -rotations outside of the normal rotation rules, such as in response to -SIGHUP. After rotating, this initiates a cleanup of old log files according -to the normal rules. - -**Example** - -Example of how to rotate in response to SIGHUP. - -Code: - -```go -l := &lumberjack.Logger{} -log.SetOutput(l) -c := make(chan os.Signal, 1) -signal.Notify(c, syscall.SIGHUP) - -go func() { - for { - <-c - l.Rotate() - } -}() -``` - -### func (\*Logger) Write -``` go -func (l *Logger) Write(p []byte) (n int, err error) -``` -Write implements io.Writer. If a write would cause the log file to be larger -than MaxSize, the file is closed, renamed to include a timestamp of the -current time, and a new log file is created using the original log file name. -If the length of the write is greater than MaxSize, an error is returned. - - - - - - - - - -- - - -Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/chown.go b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/chown.go deleted file mode 100644 index 11d06697232..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/chown.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build !linux - -package lumberjack - -import ( - "os" -) - -func chown(_ string, _ os.FileInfo) error { - return nil -} diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/chown_linux.go b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/chown_linux.go deleted file mode 100644 index 2758ec9cedd..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/chown_linux.go +++ /dev/null @@ -1,19 +0,0 @@ -package lumberjack - -import ( - "os" - "syscall" -) - -// os_Chown is a var so we can mock it out during tests. -var os_Chown = os.Chown - -func chown(name string, info os.FileInfo) error { - f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode()) - if err != nil { - return err - } - f.Close() - stat := info.Sys().(*syscall.Stat_t) - return os_Chown(name, int(stat.Uid), int(stat.Gid)) -} diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/example_test.go b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/example_test.go deleted file mode 100644 index acc96c92e02..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/example_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package lumberjack_test - -import ( - "log" - - "github.com/natefinch/lumberjack" -) - -// To use lumberjack with the standard library's log package, just pass it into -// the SetOutput function when your application starts. -func Example() { - log.SetOutput(&lumberjack.Logger{ - Filename: "/var/log/myapp/foo.log", - MaxSize: 500, // megabytes - MaxBackups: 3, - MaxAge: 28, // days - }) -} diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/linux_test.go b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/linux_test.go deleted file mode 100644 index 40f3446685c..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/linux_test.go +++ /dev/null @@ -1,104 +0,0 @@ -// +build linux - -package lumberjack - -import ( - "os" - "syscall" - "testing" -) - -func TestMaintainMode(t *testing.T) { - currentTime = fakeTime - dir := makeTempDir("TestMaintainMode", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - - mode := os.FileMode(0770) - f, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, mode) - isNil(err, t) - f.Close() - - l := &Logger{ - Filename: filename, - MaxBackups: 1, - MaxSize: 100, // megabytes - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - - newFakeTime() - - err = l.Rotate() - isNil(err, t) - - filename2 := backupFile(dir) - info, err := os.Stat(filename) - isNil(err, t) - info2, err := os.Stat(filename2) - isNil(err, t) - equals(mode, info.Mode(), t) - equals(mode, info2.Mode(), t) -} - -func TestMaintainOwner(t *testing.T) { - fakeC := fakeChown{} - os_Chown = fakeC.Set - os_Stat = fakeStat - defer func() { - os_Chown = os.Chown - os_Stat = os.Stat - }() - currentTime = fakeTime - dir := makeTempDir("TestMaintainOwner", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - - l := &Logger{ - Filename: filename, - MaxBackups: 1, - MaxSize: 100, // megabytes - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - - newFakeTime() - - err = l.Rotate() - isNil(err, t) - - equals(555, fakeC.uid, t) - equals(666, fakeC.gid, t) -} - -type fakeChown struct { - name string - uid int - gid int -} - -func (f *fakeChown) Set(name string, uid, gid int) error { - f.name = name - f.uid = uid - f.gid = gid - return nil -} - -func fakeStat(name string) (os.FileInfo, error) { - info, err := os.Stat(name) - if err != nil { - return info, err - } - stat := info.Sys().(*syscall.Stat_t) - stat.Uid = 555 - stat.Gid = 666 - return info, nil -} diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/lumberjack.go b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/lumberjack.go deleted file mode 100644 index 2e5d28302ca..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/lumberjack.go +++ /dev/null @@ -1,415 +0,0 @@ -// Package lumberjack provides a rolling logger. -// -// Note that this is v2.0 of lumberjack, and should be imported using gopkg.in -// thusly: -// -// import "gopkg.in/natefinch/lumberjack.v2" -// -// The package name remains simply lumberjack, and the code resides at -// https://github.com/natefinch/lumberjack under the v2.0 branch. -// -// Lumberjack is intended to be one part of a logging infrastructure. -// It is not an all-in-one solution, but instead is a pluggable -// component at the bottom of the logging stack that simply controls the files -// to which logs are written. -// -// Lumberjack plays well with any logging package that can write to an -// io.Writer, including the standard library's log package. -// -// Lumberjack assumes that only one process is writing to the output files. -// Using the same lumberjack configuration from multiple processes on the same -// machine will result in improper behavior. -package lumberjack - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strings" - "sync" - "time" -) - -const ( - backupTimeFormat = "2006-01-02T15-04-05.000" - defaultMaxSize = 100 -) - -// ensure we always implement io.WriteCloser -var _ io.WriteCloser = (*Logger)(nil) - -// Logger is an io.WriteCloser that writes to the specified filename. -// -// Logger opens or creates the logfile on first Write. If the file exists and -// is less than MaxSize megabytes, lumberjack will open and append to that file. -// If the file exists and its size is >= MaxSize megabytes, the file is renamed -// by putting the current time in a timestamp in the name immediately before the -// file's extension (or the end of the filename if there's no extension). A new -// log file is then created using original filename. -// -// Whenever a write would cause the current log file exceed MaxSize megabytes, -// the current file is closed, renamed, and a new log file created with the -// original name. Thus, the filename you give Logger is always the "current" log -// file. -// -// Cleaning Up Old Log Files -// -// Whenever a new logfile gets created, old log files may be deleted. The most -// recent files according to the encoded timestamp will be retained, up to a -// number equal to MaxBackups (or all of them if MaxBackups is 0). Any files -// with an encoded timestamp older than MaxAge days are deleted, regardless of -// MaxBackups. Note that the time encoded in the timestamp is the rotation -// time, which may differ from the last time that file was written to. -// -// If MaxBackups and MaxAge are both 0, no old log files will be deleted. -type Logger struct { - // Filename is the file to write logs to. Backup log files will be retained - // in the same directory. It uses -lumberjack.log in - // os.TempDir() if empty. - Filename string `json:"filename" yaml:"filename"` - - // MaxSize is the maximum size in megabytes of the log file before it gets - // rotated. It defaults to 100 megabytes. - MaxSize int `json:"maxsize" yaml:"maxsize"` - - // MaxAge is the maximum number of days to retain old log files based on the - // timestamp encoded in their filename. Note that a day is defined as 24 - // hours and may not exactly correspond to calendar days due to daylight - // savings, leap seconds, etc. The default is not to remove old log files - // based on age. - MaxAge int `json:"maxage" yaml:"maxage"` - - // MaxBackups is the maximum number of old log files to retain. The default - // is to retain all old log files (though MaxAge may still cause them to get - // deleted.) - MaxBackups int `json:"maxbackups" yaml:"maxbackups"` - - // LocalTime determines if the time used for formatting the timestamps in - // backup files is the computer's local time. The default is to use UTC - // time. - LocalTime bool `json:"localtime" yaml:"localtime"` - - size int64 - file *os.File - mu sync.Mutex -} - -var ( - // currentTime exists so it can be mocked out by tests. - currentTime = time.Now - - // os_Stat exists so it can be mocked out by tests. - os_Stat = os.Stat - - // megabyte is the conversion factor between MaxSize and bytes. It is a - // variable so tests can mock it out and not need to write megabytes of data - // to disk. - megabyte = 1024 * 1024 -) - -// Write implements io.Writer. If a write would cause the log file to be larger -// than MaxSize, the file is closed, renamed to include a timestamp of the -// current time, and a new log file is created using the original log file name. -// If the length of the write is greater than MaxSize, an error is returned. -func (l *Logger) Write(p []byte) (n int, err error) { - l.mu.Lock() - defer l.mu.Unlock() - - writeLen := int64(len(p)) - if writeLen > l.max() { - return 0, fmt.Errorf( - "write length %d exceeds maximum file size %d", writeLen, l.max(), - ) - } - - if l.file == nil { - if err = l.openExistingOrNew(len(p)); err != nil { - return 0, err - } - } - - if l.size+writeLen > l.max() { - if err := l.rotate(); err != nil { - return 0, err - } - } - - n, err = l.file.Write(p) - l.size += int64(n) - - return n, err -} - -// Close implements io.Closer, and closes the current logfile. -func (l *Logger) Close() error { - l.mu.Lock() - defer l.mu.Unlock() - return l.close() -} - -// close closes the file if it is open. -func (l *Logger) close() error { - if l.file == nil { - return nil - } - err := l.file.Close() - l.file = nil - return err -} - -// Rotate causes Logger to close the existing log file and immediately create a -// new one. This is a helper function for applications that want to initiate -// rotations outside of the normal rotation rules, such as in response to -// SIGHUP. After rotating, this initiates a cleanup of old log files according -// to the normal rules. -func (l *Logger) Rotate() error { - l.mu.Lock() - defer l.mu.Unlock() - return l.rotate() -} - -// rotate closes the current file, moves it aside with a timestamp in the name, -// (if it exists), opens a new file with the original filename, and then runs -// cleanup. -func (l *Logger) rotate() error { - if err := l.close(); err != nil { - return err - } - - if err := l.openNew(); err != nil { - return err - } - return l.cleanup() -} - -// openNew opens a new log file for writing, moving any old log file out of the -// way. This methods assumes the file has already been closed. -func (l *Logger) openNew() error { - err := os.MkdirAll(l.dir(), 0744) - if err != nil { - return fmt.Errorf("can't make directories for new logfile: %s", err) - } - - name := l.filename() - mode := os.FileMode(0644) - info, err := os_Stat(name) - if err == nil { - // Copy the mode off the old logfile. - mode = info.Mode() - // move the existing file - newname := backupName(name, l.LocalTime) - if err := os.Rename(name, newname); err != nil { - return fmt.Errorf("can't rename log file: %s", err) - } - - // this is a no-op anywhere but linux - if err := chown(name, info); err != nil { - return err - } - } - - // we use truncate here because this should only get called when we've moved - // the file ourselves. if someone else creates the file in the meantime, - // just wipe out the contents. - f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode) - if err != nil { - return fmt.Errorf("can't open new logfile: %s", err) - } - l.file = f - l.size = 0 - return nil -} - -// backupName creates a new filename from the given name, inserting a timestamp -// between the filename and the extension, using the local time if requested -// (otherwise UTC). -func backupName(name string, local bool) string { - dir := filepath.Dir(name) - filename := filepath.Base(name) - ext := filepath.Ext(filename) - prefix := filename[:len(filename)-len(ext)] - t := currentTime() - if !local { - t = t.UTC() - } - - timestamp := t.Format(backupTimeFormat) - return filepath.Join(dir, fmt.Sprintf("%s-%s%s", prefix, timestamp, ext)) -} - -// openExistingOrNew opens the logfile if it exists and if the current write -// would not put it over MaxSize. If there is no such file or the write would -// put it over the MaxSize, a new file is created. -func (l *Logger) openExistingOrNew(writeLen int) error { - filename := l.filename() - info, err := os_Stat(filename) - if os.IsNotExist(err) { - return l.openNew() - } - if err != nil { - return fmt.Errorf("error getting log file info: %s", err) - } - // the first file we find that matches our pattern will be the most - // recently modified log file. - if info.Size()+int64(writeLen) < l.max() { - file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644) - if err == nil { - l.file = file - l.size = info.Size() - return nil - } - // if we fail to open the old log file for some reason, just ignore - // it and open a new log file. - } - return l.openNew() -} - -// genFilename generates the name of the logfile from the current time. -func (l *Logger) filename() string { - if l.Filename != "" { - return l.Filename - } - name := filepath.Base(os.Args[0]) + "-lumberjack.log" - return filepath.Join(os.TempDir(), name) -} - -// cleanup deletes old log files, keeping at most l.MaxBackups files, as long as -// none of them are older than MaxAge. -func (l *Logger) cleanup() error { - if l.MaxBackups == 0 && l.MaxAge == 0 { - return nil - } - - files, err := l.oldLogFiles() - if err != nil { - return err - } - - var deletes []logInfo - - if l.MaxBackups > 0 && l.MaxBackups < len(files) { - deletes = files[l.MaxBackups:] - files = files[:l.MaxBackups] - } - if l.MaxAge > 0 { - diff := time.Duration(int64(24*time.Hour) * int64(l.MaxAge)) - - cutoff := currentTime().Add(-1 * diff) - - for _, f := range files { - if f.timestamp.Before(cutoff) { - deletes = append(deletes, f) - } - } - } - - if len(deletes) == 0 { - return nil - } - - go deleteAll(l.dir(), deletes) - - return nil -} - -func deleteAll(dir string, files []logInfo) { - // remove files on a separate goroutine - for _, f := range files { - // what am I going to do, log this? - _ = os.Remove(filepath.Join(dir, f.Name())) - } -} - -// oldLogFiles returns the list of backup log files stored in the same -// directory as the current log file, sorted by ModTime -func (l *Logger) oldLogFiles() ([]logInfo, error) { - files, err := ioutil.ReadDir(l.dir()) - if err != nil { - return nil, fmt.Errorf("can't read log file directory: %s", err) - } - logFiles := []logInfo{} - - prefix, ext := l.prefixAndExt() - - for _, f := range files { - if f.IsDir() { - continue - } - - name := l.timeFromName(f.Name(), prefix, ext) - if name == "" { - continue - } - t, err := time.Parse(backupTimeFormat, name) - if err == nil { - logFiles = append(logFiles, logInfo{t, f}) - } - } - - sort.Sort(byFormatTime(logFiles)) - - return logFiles, nil -} - -// timeFromName extracts the formatted time from the filename by stripping off -// the filename's prefix and extension. This prevents someone's filename from -// confusing time.parse. -func (l *Logger) timeFromName(filename, prefix, ext string) string { - if !strings.HasPrefix(filename, prefix) { - return "" - } - filename = filename[len(prefix):] - - if !strings.HasSuffix(filename, ext) { - return "" - } - filename = filename[:len(filename)-len(ext)] - return filename -} - -// max returns the maximum size in bytes of log files before rolling. -func (l *Logger) max() int64 { - if l.MaxSize == 0 { - return int64(defaultMaxSize * megabyte) - } - return int64(l.MaxSize) * int64(megabyte) -} - -// dir returns the directory for the current filename. -func (l *Logger) dir() string { - return filepath.Dir(l.filename()) -} - -// prefixAndExt returns the filename part and extension part from the Logger's -// filename. -func (l *Logger) prefixAndExt() (prefix, ext string) { - filename := filepath.Base(l.filename()) - ext = filepath.Ext(filename) - prefix = filename[:len(filename)-len(ext)] + "-" - return prefix, ext -} - -// logInfo is a convenience struct to return the filename and its embedded -// timestamp. -type logInfo struct { - timestamp time.Time - os.FileInfo -} - -// byFormatTime sorts by newest time formatted in the name. -type byFormatTime []logInfo - -func (b byFormatTime) Less(i, j int) bool { - return b[i].timestamp.After(b[j].timestamp) -} - -func (b byFormatTime) Swap(i, j int) { - b[i], b[j] = b[j], b[i] -} - -func (b byFormatTime) Len() int { - return len(b) -} diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/lumberjack_test.go b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/lumberjack_test.go deleted file mode 100644 index e9984f807ce..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/lumberjack_test.go +++ /dev/null @@ -1,634 +0,0 @@ -package lumberjack - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "testing" - "time" - - "github.com/BurntSushi/toml" - "gopkg.in/yaml.v1" -) - -// !!!NOTE!!! -// -// Running these tests in parallel will almost certainly cause sporadic (or even -// regular) failures, because they're all messing with the same global variable -// that controls the logic's mocked time.Now. So... don't do that. - -// Since all the tests uses the time to determine filenames etc, we need to -// control the wall clock as much as possible, which means having a wall clock -// that doesn't change unless we want it to. -var fakeCurrentTime = time.Now() - -func fakeTime() time.Time { - return fakeCurrentTime -} - -func TestNewFile(t *testing.T) { - currentTime = fakeTime - - dir := makeTempDir("TestNewFile", t) - defer os.RemoveAll(dir) - l := &Logger{ - Filename: logFile(dir), - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - existsWithLen(logFile(dir), n, t) - fileCount(dir, 1, t) -} - -func TestOpenExisting(t *testing.T) { - currentTime = fakeTime - dir := makeTempDir("TestOpenExisting", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - data := []byte("foo!") - err := ioutil.WriteFile(filename, data, 0644) - isNil(err, t) - existsWithLen(filename, len(data), t) - - l := &Logger{ - Filename: filename, - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - - // make sure the file got appended - existsWithLen(filename, len(data)+n, t) - - // make sure no other files were created - fileCount(dir, 1, t) -} - -func TestWriteTooLong(t *testing.T) { - currentTime = fakeTime - megabyte = 1 - dir := makeTempDir("TestWriteTooLong", t) - defer os.RemoveAll(dir) - l := &Logger{ - Filename: logFile(dir), - MaxSize: 5, - } - defer l.Close() - b := []byte("booooooooooooooo!") - n, err := l.Write(b) - notNil(err, t) - equals(0, n, t) - equals(err.Error(), - fmt.Sprintf("write length %d exceeds maximum file size %d", len(b), l.MaxSize), t) - _, err = os.Stat(logFile(dir)) - assert(os.IsNotExist(err), t, "File exists, but should not have been created") -} - -func TestMakeLogDir(t *testing.T) { - currentTime = fakeTime - dir := time.Now().Format("TestMakeLogDir" + backupTimeFormat) - dir = filepath.Join(os.TempDir(), dir) - defer os.RemoveAll(dir) - filename := logFile(dir) - l := &Logger{ - Filename: filename, - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - existsWithLen(logFile(dir), n, t) - fileCount(dir, 1, t) -} - -func TestDefaultFilename(t *testing.T) { - currentTime = fakeTime - dir := os.TempDir() - filename := filepath.Join(dir, filepath.Base(os.Args[0])+"-lumberjack.log") - defer os.Remove(filename) - l := &Logger{} - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - - isNil(err, t) - equals(len(b), n, t) - existsWithLen(filename, n, t) -} - -func TestAutoRotate(t *testing.T) { - currentTime = fakeTime - megabyte = 1 - - dir := makeTempDir("TestAutoRotate", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - l := &Logger{ - Filename: filename, - MaxSize: 10, - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - - existsWithLen(filename, n, t) - fileCount(dir, 1, t) - - newFakeTime() - - b2 := []byte("foooooo!") - n, err = l.Write(b2) - isNil(err, t) - equals(len(b2), n, t) - - // the old logfile should be moved aside and the main logfile should have - // only the last write in it. - existsWithLen(filename, n, t) - - // the backup file will use the current fake time and have the old contents. - existsWithLen(backupFile(dir), len(b), t) - - fileCount(dir, 2, t) -} - -func TestFirstWriteRotate(t *testing.T) { - currentTime = fakeTime - megabyte = 1 - dir := makeTempDir("TestFirstWriteRotate", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - l := &Logger{ - Filename: filename, - MaxSize: 10, - } - defer l.Close() - - start := []byte("boooooo!") - err := ioutil.WriteFile(filename, start, 0600) - isNil(err, t) - - newFakeTime() - - // this would make us rotate - b := []byte("fooo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - - existsWithLen(filename, n, t) - existsWithLen(backupFile(dir), len(start), t) - - fileCount(dir, 2, t) -} - -func TestMaxBackups(t *testing.T) { - currentTime = fakeTime - megabyte = 1 - dir := makeTempDir("TestMaxBackups", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - l := &Logger{ - Filename: filename, - MaxSize: 10, - MaxBackups: 1, - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - - existsWithLen(filename, n, t) - fileCount(dir, 1, t) - - newFakeTime() - - // this will put us over the max - b2 := []byte("foooooo!") - n, err = l.Write(b2) - isNil(err, t) - equals(len(b2), n, t) - - // this will use the new fake time - secondFilename := backupFile(dir) - existsWithLen(secondFilename, len(b), t) - - // make sure the old file still exists with the same size. - existsWithLen(filename, n, t) - - fileCount(dir, 2, t) - - newFakeTime() - - // this will make us rotate again - n, err = l.Write(b2) - isNil(err, t) - equals(len(b2), n, t) - - // this will use the new fake time - thirdFilename := backupFile(dir) - existsWithLen(thirdFilename, len(b2), t) - - existsWithLen(filename, n, t) - - // we need to wait a little bit since the files get deleted on a different - // goroutine. - <-time.After(time.Millisecond * 10) - - // should only have two files in the dir still - fileCount(dir, 2, t) - - // second file name should still exist - existsWithLen(thirdFilename, len(b2), t) - - // should have deleted the first backup - notExist(secondFilename, t) - - // now test that we don't delete directories or non-logfile files - - newFakeTime() - - // create a file that is close to but different from the logfile name. - // It shouldn't get caught by our deletion filters. - notlogfile := logFile(dir) + ".foo" - err = ioutil.WriteFile(notlogfile, []byte("data"), 0644) - isNil(err, t) - - // Make a directory that exactly matches our log file filters... it still - // shouldn't get caught by the deletion filter since it's a directory. - notlogfiledir := backupFile(dir) - err = os.Mkdir(notlogfiledir, 0700) - isNil(err, t) - - newFakeTime() - - // this will make us rotate again - n, err = l.Write(b2) - isNil(err, t) - equals(len(b2), n, t) - - // this will use the new fake time - fourthFilename := backupFile(dir) - existsWithLen(fourthFilename, len(b2), t) - - // we need to wait a little bit since the files get deleted on a different - // goroutine. - <-time.After(time.Millisecond * 10) - - // We should have four things in the directory now - the 2 log files, the - // not log file, and the directory - fileCount(dir, 4, t) - - // third file name should still exist - existsWithLen(filename, n, t) - - existsWithLen(fourthFilename, len(b2), t) - - // should have deleted the first filename - notExist(thirdFilename, t) - - // the not-a-logfile should still exist - exists(notlogfile, t) - - // the directory - exists(notlogfiledir, t) -} - -func TestMaxAge(t *testing.T) { - currentTime = fakeTime - megabyte = 1 - - dir := makeTempDir("TestMaxAge", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - l := &Logger{ - Filename: filename, - MaxSize: 10, - MaxAge: 1, - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - - existsWithLen(filename, n, t) - fileCount(dir, 1, t) - - // two days later - newFakeTime() - - b2 := []byte("foooooo!") - n, err = l.Write(b2) - isNil(err, t) - equals(len(b2), n, t) - existsWithLen(backupFile(dir), len(b), t) - - // we need to wait a little bit since the files get deleted on a different - // goroutine. - <-time.After(10 * time.Millisecond) - - // We should still have 2 log files, since the most recent backup was just - // created. - fileCount(dir, 2, t) - - existsWithLen(filename, len(b2), t) - - // we should have deleted the old file due to being too old - existsWithLen(backupFile(dir), len(b), t) - - // two days later - newFakeTime() - - b3 := []byte("foooooo!") - n, err = l.Write(b2) - isNil(err, t) - equals(len(b3), n, t) - existsWithLen(backupFile(dir), len(b2), t) - - // we need to wait a little bit since the files get deleted on a different - // goroutine. - <-time.After(10 * time.Millisecond) - - // We should have 2 log files - the main log file, and the most recent - // backup. The earlier backup is past the cutoff and should be gone. - fileCount(dir, 2, t) - - existsWithLen(filename, len(b3), t) - - // we should have deleted the old file due to being too old - existsWithLen(backupFile(dir), len(b2), t) - -} - -func TestOldLogFiles(t *testing.T) { - currentTime = fakeTime - megabyte = 1 - - dir := makeTempDir("TestOldLogFiles", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - data := []byte("data") - err := ioutil.WriteFile(filename, data, 07) - isNil(err, t) - - // This gives us a time with the same precision as the time we get from the - // timestamp in the name. - t1, err := time.Parse(backupTimeFormat, fakeTime().UTC().Format(backupTimeFormat)) - isNil(err, t) - - backup := backupFile(dir) - err = ioutil.WriteFile(backup, data, 07) - isNil(err, t) - - newFakeTime() - - t2, err := time.Parse(backupTimeFormat, fakeTime().UTC().Format(backupTimeFormat)) - isNil(err, t) - - backup2 := backupFile(dir) - err = ioutil.WriteFile(backup2, data, 07) - isNil(err, t) - - l := &Logger{Filename: filename} - files, err := l.oldLogFiles() - isNil(err, t) - equals(2, len(files), t) - - // should be sorted by newest file first, which would be t2 - equals(t2, files[0].timestamp, t) - equals(t1, files[1].timestamp, t) -} - -func TestTimeFromName(t *testing.T) { - l := &Logger{Filename: "/var/log/myfoo/foo.log"} - prefix, ext := l.prefixAndExt() - val := l.timeFromName("foo-2014-05-04T14-44-33.555.log", prefix, ext) - equals("2014-05-04T14-44-33.555", val, t) - - val = l.timeFromName("foo-2014-05-04T14-44-33.555", prefix, ext) - equals("", val, t) - - val = l.timeFromName("2014-05-04T14-44-33.555.log", prefix, ext) - equals("", val, t) - - val = l.timeFromName("foo.log", prefix, ext) - equals("", val, t) -} - -func TestLocalTime(t *testing.T) { - currentTime = fakeTime - megabyte = 1 - - dir := makeTempDir("TestLocalTime", t) - defer os.RemoveAll(dir) - - l := &Logger{ - Filename: logFile(dir), - MaxSize: 10, - LocalTime: true, - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - - b2 := []byte("fooooooo!") - n2, err := l.Write(b2) - isNil(err, t) - equals(len(b2), n2, t) - - existsWithLen(logFile(dir), n2, t) - existsWithLen(backupFileLocal(dir), n, t) -} - -func TestRotate(t *testing.T) { - currentTime = fakeTime - dir := makeTempDir("TestRotate", t) - defer os.RemoveAll(dir) - - filename := logFile(dir) - - l := &Logger{ - Filename: filename, - MaxBackups: 1, - MaxSize: 100, // megabytes - } - defer l.Close() - b := []byte("boo!") - n, err := l.Write(b) - isNil(err, t) - equals(len(b), n, t) - - existsWithLen(filename, n, t) - fileCount(dir, 1, t) - - newFakeTime() - - err = l.Rotate() - isNil(err, t) - - // we need to wait a little bit since the files get deleted on a different - // goroutine. - <-time.After(10 * time.Millisecond) - - filename2 := backupFile(dir) - existsWithLen(filename2, n, t) - existsWithLen(filename, 0, t) - fileCount(dir, 2, t) - newFakeTime() - - err = l.Rotate() - isNil(err, t) - - // we need to wait a little bit since the files get deleted on a different - // goroutine. - <-time.After(10 * time.Millisecond) - - filename3 := backupFile(dir) - existsWithLen(filename3, 0, t) - existsWithLen(filename, 0, t) - fileCount(dir, 2, t) - - b2 := []byte("foooooo!") - n, err = l.Write(b2) - isNil(err, t) - equals(len(b2), n, t) - - // this will use the new fake time - existsWithLen(filename, n, t) -} - -func TestJson(t *testing.T) { - data := []byte(` -{ - "filename": "foo", - "maxsize": 5, - "maxage": 10, - "maxbackups": 3, - "localtime": true -}`[1:]) - - l := Logger{} - err := json.Unmarshal(data, &l) - isNil(err, t) - equals("foo", l.Filename, t) - equals(5, l.MaxSize, t) - equals(10, l.MaxAge, t) - equals(3, l.MaxBackups, t) - equals(true, l.LocalTime, t) -} - -func TestYaml(t *testing.T) { - data := []byte(` -filename: foo -maxsize: 5 -maxage: 10 -maxbackups: 3 -localtime: true`[1:]) - - l := Logger{} - err := yaml.Unmarshal(data, &l) - isNil(err, t) - equals("foo", l.Filename, t) - equals(5, l.MaxSize, t) - equals(10, l.MaxAge, t) - equals(3, l.MaxBackups, t) - equals(true, l.LocalTime, t) -} - -func TestToml(t *testing.T) { - data := ` -filename = "foo" -maxsize = 5 -maxage = 10 -maxbackups = 3 -localtime = true`[1:] - - l := Logger{} - md, err := toml.Decode(data, &l) - isNil(err, t) - equals("foo", l.Filename, t) - equals(5, l.MaxSize, t) - equals(10, l.MaxAge, t) - equals(3, l.MaxBackups, t) - equals(true, l.LocalTime, t) - equals(0, len(md.Undecoded()), t) -} - -// makeTempDir creates a file with a semi-unique name in the OS temp directory. -// It should be based on the name of the test, to keep parallel tests from -// colliding, and must be cleaned up after the test is finished. -func makeTempDir(name string, t testing.TB) string { - dir := time.Now().Format(name + backupTimeFormat) - dir = filepath.Join(os.TempDir(), dir) - isNilUp(os.Mkdir(dir, 0777), t, 1) - return dir -} - -// existsWithLen checks that the given file exists and has the correct length. -func existsWithLen(path string, length int, t testing.TB) { - info, err := os.Stat(path) - isNilUp(err, t, 1) - equalsUp(int64(length), info.Size(), t, 1) -} - -// logFile returns the log file name in the given directory for the current fake -// time. -func logFile(dir string) string { - return filepath.Join(dir, "foobar.log") -} - -func backupFile(dir string) string { - return filepath.Join(dir, "foobar-"+fakeTime().UTC().Format(backupTimeFormat)+".log") -} - -func backupFileLocal(dir string) string { - return filepath.Join(dir, "foobar-"+fakeTime().Format(backupTimeFormat)+".log") -} - -// logFileLocal returns the log file name in the given directory for the current -// fake time using the local timezone. -func logFileLocal(dir string) string { - return filepath.Join(dir, fakeTime().Format(backupTimeFormat)) -} - -// fileCount checks that the number of files in the directory is exp. -func fileCount(dir string, exp int, t testing.TB) { - files, err := ioutil.ReadDir(dir) - isNilUp(err, t, 1) - // Make sure no other files were created. - equalsUp(exp, len(files), t, 1) -} - -// newFakeTime sets the fake "current time" to two days later. -func newFakeTime() { - fakeCurrentTime = fakeCurrentTime.Add(time.Hour * 24 * 2) -} - -func notExist(path string, t testing.TB) { - _, err := os.Stat(path) - assertUp(os.IsNotExist(err), t, 1, "expected to get os.IsNotExist, but instead got %v", err) -} - -func exists(path string, t testing.TB) { - _, err := os.Stat(path) - assertUp(err == nil, t, 1, "expected file to exist, but got error from os.Stat: %v", err) -} diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/rotate_test.go b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/rotate_test.go deleted file mode 100644 index 0561464ac0d..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/rotate_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// +build linux - -package lumberjack_test - -import ( - "log" - "os" - "os/signal" - "syscall" - - "github.com/natefinch/lumberjack" -) - -// Example of how to rotate in response to SIGHUP. -func ExampleLogger_Rotate() { - l := &lumberjack.Logger{} - log.SetOutput(l) - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGHUP) - - go func() { - for { - <-c - l.Rotate() - } - }() -} diff --git a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/testing_test.go b/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/testing_test.go deleted file mode 100644 index 8e89c083198..00000000000 --- a/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2/testing_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package lumberjack - -import ( - "fmt" - "path/filepath" - "reflect" - "runtime" - "testing" -) - -// assert will log the given message if condition is false. -func assert(condition bool, t testing.TB, msg string, v ...interface{}) { - assertUp(condition, t, 1, msg, v...) -} - -// assertUp is like assert, but used inside helper functions, to ensure that -// the file and line number reported by failures corresponds to one or more -// levels up the stack. -func assertUp(condition bool, t testing.TB, caller int, msg string, v ...interface{}) { - if !condition { - _, file, line, _ := runtime.Caller(caller + 1) - v = append([]interface{}{filepath.Base(file), line}, v...) - fmt.Printf("%s:%d: "+msg+"\n", v...) - t.FailNow() - } -} - -// equals tests that the two values are equal according to reflect.DeepEqual. -func equals(exp, act interface{}, t testing.TB) { - equalsUp(exp, act, t, 1) -} - -// equalsUp is like equals, but used inside helper functions, to ensure that the -// file and line number reported by failures corresponds to one or more levels -// up the stack. -func equalsUp(exp, act interface{}, t testing.TB, caller int) { - if !reflect.DeepEqual(exp, act) { - _, file, line, _ := runtime.Caller(caller + 1) - fmt.Printf("%s:%d: exp: %v (%T), got: %v (%T)\n", - filepath.Base(file), line, exp, exp, act, act) - t.FailNow() - } -} - -// isNil reports a failure if the given value is not nil. Note that values -// which cannot be nil will always fail this check. -func isNil(obtained interface{}, t testing.TB) { - isNilUp(obtained, t, 1) -} - -// isNilUp is like isNil, but used inside helper functions, to ensure that the -// file and line number reported by failures corresponds to one or more levels -// up the stack. -func isNilUp(obtained interface{}, t testing.TB, caller int) { - if !_isNil(obtained) { - _, file, line, _ := runtime.Caller(caller + 1) - fmt.Printf("%s:%d: expected nil, got: %v\n", filepath.Base(file), line, obtained) - t.FailNow() - } -} - -// notNil reports a failure if the given value is nil. -func notNil(obtained interface{}, t testing.TB) { - notNilUp(obtained, t, 1) -} - -// notNilUp is like notNil, but used inside helper functions, to ensure that the -// file and line number reported by failures corresponds to one or more levels -// up the stack. -func notNilUp(obtained interface{}, t testing.TB, caller int) { - if _isNil(obtained) { - _, file, line, _ := runtime.Caller(caller + 1) - fmt.Printf("%s:%d: expected non-nil, got: %v\n", filepath.Base(file), line, obtained) - t.FailNow() - } -} - -// _isNil is a helper function for isNil and notNil, and should not be used -// directly. -func _isNil(obtained interface{}) bool { - if obtained == nil { - return true - } - - switch v := reflect.ValueOf(obtained); v.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: - return v.IsNil() - } - - return false -} diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index ba5d6574666..61c5617a1ea 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -285,6 +285,7 @@ func serveHTTPApi(req cmds.Request) (error, <-chan error) { corehttp.VersionOption(), defaultMux("/debug/vars"), defaultMux("/debug/pprof/"), + corehttp.LogOption(), } if len(cfg.Gateway.RootRedirect) > 0 { diff --git a/core/corehttp/logs.go b/core/corehttp/logs.go new file mode 100644 index 00000000000..7624644cf56 --- /dev/null +++ b/core/corehttp/logs.go @@ -0,0 +1,49 @@ +package corehttp + +import ( + "io" + "net/http" + + core "github.com/ipfs/go-ipfs/core" + "github.com/ipfs/go-ipfs/thirdparty/eventlog" +) + +type writeErrNotifier struct { + w io.Writer + errs chan error +} + +func newWriteErrNotifier(w io.Writer) (io.Writer, <-chan error) { + ch := make(chan error, 1) + return &writeErrNotifier{ + w: w, + errs: ch, + }, ch +} + +func (w *writeErrNotifier) Write(b []byte) (int, error) { + n, err := w.w.Write(b) + if err != nil { + select { + case w.errs <- err: + default: + } + } + if f, ok := w.w.(http.Flusher); ok { + f.Flush() + } + return n, err +} + +func LogOption() ServeOption { + return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) { + mux.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + wnf, errs := newWriteErrNotifier(w) + eventlog.WriterGroup.AddWriter(wnf) + log.Event(n.Context(), "log API client connected") + <-errs + }) + return mux, nil + } +} diff --git a/repo/fsrepo/fsrepo.go b/repo/fsrepo/fsrepo.go index d35a3525c5c..4c473539622 100644 --- a/repo/fsrepo/fsrepo.go +++ b/repo/fsrepo/fsrepo.go @@ -368,13 +368,7 @@ func (r *FSRepo) openDatastore() error { func configureEventLoggerAtRepoPath(c *config.Config, repoPath string) { eventlog.Configure(eventlog.LevelInfo) eventlog.Configure(eventlog.LdJSONFormatter) - rotateConf := eventlog.LogRotatorConfig{ - Filename: path.Join(repoPath, "logs", "events.log"), - MaxSizeMB: c.Log.MaxSizeMB, - MaxBackups: c.Log.MaxBackups, - MaxAgeDays: c.Log.MaxAgeDays, - } - eventlog.Configure(eventlog.OutputRotatingLogFile(rotateConf)) + eventlog.Configure(eventlog.Output(eventlog.WriterGroup)) } // Close closes the FSRepo, releasing held resources. diff --git a/test/sharness/t0110-gateway.sh b/test/sharness/t0110-gateway.sh index ac421d652a5..36a94c671c1 100755 --- a/test/sharness/t0110-gateway.sh +++ b/test/sharness/t0110-gateway.sh @@ -75,6 +75,14 @@ test_expect_success "GET /webui/ returns code expected" ' test_curl_resp_http_code "http://127.0.0.1:$apiport/webui/" "HTTP/1.1 302 Found" "HTTP/1.1 301 Moved Permanently" ' +test_expect_success "GET /logs returns logs" ' + test_expect_code 28 curl http://127.0.0.1:$apiport/logs -m1 > log_out +' + +test_expect_success "log output looks good" ' + grep "log API client connected" log_out +' + test_kill_ipfs_daemon test_done diff --git a/thirdparty/eventlog/log.go b/thirdparty/eventlog/log.go index 6fab7383c2a..298350aae84 100644 --- a/thirdparty/eventlog/log.go +++ b/thirdparty/eventlog/log.go @@ -83,6 +83,11 @@ func (el *eventLogger) EventBegin(ctx context.Context, event string, metadata .. func (el *eventLogger) Event(ctx context.Context, event string, metadata ...Loggable) { + // short circuit if theres nothing to write to + if !WriterGroup.Active() { + return + } + // Collect loggables for later logging var loggables []Loggable diff --git a/thirdparty/eventlog/option.go b/thirdparty/eventlog/option.go index 6d297bb3bd9..3fa77b61e5b 100644 --- a/thirdparty/eventlog/option.go +++ b/thirdparty/eventlog/option.go @@ -1,12 +1,10 @@ package eventlog import ( - "bufio" "io" "os" "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/Sirupsen/logrus" - "github.com/ipfs/go-ipfs/Godeps/_workspace/src/gopkg.in/natefinch/lumberjack.v2" ) // init sets up sane defaults @@ -18,6 +16,9 @@ func init() { Configure(LevelError) } +// Global writer group for logs to output to +var WriterGroup = new(MirrorWriter) + type Option func() // Configure applies the provided options sequentially from left to right @@ -37,13 +38,6 @@ var TextFormatter = func() { logrus.SetFormatter(&logrus.TextFormatter{}) } -type LogRotatorConfig struct { - Filename string - MaxSizeMB int - MaxBackups int - MaxAgeDays int -} - func Output(w io.Writer) Option { return func() { logrus.SetOutput(w) @@ -51,18 +45,6 @@ func Output(w io.Writer) Option { } } -func OutputRotatingLogFile(config LogRotatorConfig) Option { - return func() { - logrus.SetOutput( - bufio.NewWriter(&lumberjack.Logger{ - Filename: config.Filename, - MaxSize: int(config.MaxSizeMB), - MaxBackups: int(config.MaxBackups), - MaxAge: int(config.MaxAgeDays), - })) - } -} - // LevelDebug Option sets the log level to debug var LevelDebug = func() { logrus.SetLevel(logrus.DebugLevel) diff --git a/thirdparty/eventlog/writer.go b/thirdparty/eventlog/writer.go new file mode 100644 index 00000000000..9a7728a1924 --- /dev/null +++ b/thirdparty/eventlog/writer.go @@ -0,0 +1,53 @@ +package eventlog + +import ( + "io" + "sync" +) + +type MirrorWriter struct { + writers []io.Writer + lk sync.Mutex +} + +func (mw *MirrorWriter) Write(b []byte) (int, error) { + mw.lk.Lock() + // write to all writers, and nil out the broken ones. + for i, w := range mw.writers { + _, err := w.Write(b) + if err != nil { + mw.writers[i] = nil + } + } + + // consolidate the slice + for i := 0; i < len(mw.writers); i++ { + if mw.writers[i] != nil { + continue + } + + j := len(mw.writers) - 1 + for ; j > i; j-- { + if mw.writers[j] != nil { + mw.writers[i], mw.writers[j] = mw.writers[j], nil // swap + break + } + } + mw.writers = mw.writers[:j] + } + mw.lk.Unlock() + return len(b), nil +} + +func (mw *MirrorWriter) AddWriter(w io.Writer) { + mw.lk.Lock() + mw.writers = append(mw.writers, w) + mw.lk.Unlock() +} + +func (mw *MirrorWriter) Active() (active bool) { + mw.lk.Lock() + active = len(mw.writers) > 0 + mw.lk.Unlock() + return +}