fixed dependencies
This commit is contained in:
407
vendor/github.com/lestrrat-go/file-rotatelogs/rotatelogs.go
generated
vendored
Normal file
407
vendor/github.com/lestrrat-go/file-rotatelogs/rotatelogs.go
generated
vendored
Normal file
@@ -0,0 +1,407 @@
|
||||
// package rotatelogs is a port of File-RotateLogs from Perl
|
||||
// (https://metacpan.org/release/File-RotateLogs), and it allows
|
||||
// you to automatically rotate output files when you write to them
|
||||
// according to the filename pattern that you can specify.
|
||||
package rotatelogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
strftime "github.com/lestrrat-go/strftime"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c clockFn) Now() time.Time {
|
||||
return c()
|
||||
}
|
||||
|
||||
// New creates a new RotateLogs object. A log filename pattern
|
||||
// must be passed. Optional `Option` parameters may be passed
|
||||
func New(p string, options ...Option) (*RotateLogs, error) {
|
||||
globPattern := p
|
||||
for _, re := range patternConversionRegexps {
|
||||
globPattern = re.ReplaceAllString(globPattern, "*")
|
||||
}
|
||||
|
||||
pattern, err := strftime.New(p)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, `invalid strftime pattern`)
|
||||
}
|
||||
|
||||
var clock Clock = Local
|
||||
rotationTime := 24 * time.Hour
|
||||
var rotationSize int64
|
||||
var rotationCount uint
|
||||
var linkName string
|
||||
var maxAge time.Duration
|
||||
var handler Handler
|
||||
var forceNewFile bool
|
||||
|
||||
for _, o := range options {
|
||||
switch o.Name() {
|
||||
case optkeyClock:
|
||||
clock = o.Value().(Clock)
|
||||
case optkeyLinkName:
|
||||
linkName = o.Value().(string)
|
||||
case optkeyMaxAge:
|
||||
maxAge = o.Value().(time.Duration)
|
||||
if maxAge < 0 {
|
||||
maxAge = 0
|
||||
}
|
||||
case optkeyRotationTime:
|
||||
rotationTime = o.Value().(time.Duration)
|
||||
if rotationTime < 0 {
|
||||
rotationTime = 0
|
||||
}
|
||||
case optkeyRotationSize:
|
||||
rotationSize = o.Value().(int64)
|
||||
if rotationSize < 0 {
|
||||
rotationSize = 0
|
||||
}
|
||||
case optkeyRotationCount:
|
||||
rotationCount = o.Value().(uint)
|
||||
case optkeyHandler:
|
||||
handler = o.Value().(Handler)
|
||||
case optkeyForceNewFile:
|
||||
forceNewFile = true
|
||||
}
|
||||
}
|
||||
|
||||
if maxAge > 0 && rotationCount > 0 {
|
||||
return nil, errors.New("options MaxAge and RotationCount cannot be both set")
|
||||
}
|
||||
|
||||
if maxAge == 0 && rotationCount == 0 {
|
||||
// if both are 0, give maxAge a sane default
|
||||
maxAge = 7 * 24 * time.Hour
|
||||
}
|
||||
|
||||
return &RotateLogs{
|
||||
clock: clock,
|
||||
eventHandler: handler,
|
||||
globPattern: globPattern,
|
||||
linkName: linkName,
|
||||
maxAge: maxAge,
|
||||
pattern: pattern,
|
||||
rotationTime: rotationTime,
|
||||
rotationSize: rotationSize,
|
||||
rotationCount: rotationCount,
|
||||
forceNewFile: forceNewFile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rl *RotateLogs) genFilename() string {
|
||||
now := rl.clock.Now()
|
||||
|
||||
// XXX HACK: Truncate only happens in UTC semantics, apparently.
|
||||
// observed values for truncating given time with 86400 secs:
|
||||
//
|
||||
// before truncation: 2018/06/01 03:54:54 2018-06-01T03:18:00+09:00
|
||||
// after truncation: 2018/06/01 03:54:54 2018-05-31T09:00:00+09:00
|
||||
//
|
||||
// This is really annoying when we want to truncate in local time
|
||||
// so we hack: we take the apparent local time in the local zone,
|
||||
// and pretend that it's in UTC. do our math, and put it back to
|
||||
// the local zone
|
||||
var base time.Time
|
||||
if now.Location() != time.UTC {
|
||||
base = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), time.UTC)
|
||||
base = base.Truncate(time.Duration(rl.rotationTime))
|
||||
base = time.Date(base.Year(), base.Month(), base.Day(), base.Hour(), base.Minute(), base.Second(), base.Nanosecond(), base.Location())
|
||||
} else {
|
||||
base = now.Truncate(time.Duration(rl.rotationTime))
|
||||
}
|
||||
return rl.pattern.FormatString(base)
|
||||
}
|
||||
|
||||
// Write satisfies the io.Writer interface. It writes to the
|
||||
// appropriate file handle that is currently being used.
|
||||
// If we have reached rotation time, the target file gets
|
||||
// automatically rotated, and also purged if necessary.
|
||||
func (rl *RotateLogs) Write(p []byte) (n int, err error) {
|
||||
// Guard against concurrent writes
|
||||
rl.mutex.Lock()
|
||||
defer rl.mutex.Unlock()
|
||||
|
||||
out, err := rl.getWriter_nolock(false, false)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, `failed to acquite target io.Writer`)
|
||||
}
|
||||
|
||||
return out.Write(p)
|
||||
}
|
||||
|
||||
// must be locked during this operation
|
||||
func (rl *RotateLogs) getWriter_nolock(bailOnRotateFail, useGenerationalNames bool) (io.Writer, error) {
|
||||
generation := rl.generation
|
||||
previousFn := rl.curFn
|
||||
// This filename contains the name of the "NEW" filename
|
||||
// to log to, which may be newer than rl.currentFilename
|
||||
baseFn := rl.genFilename()
|
||||
filename := baseFn
|
||||
var forceNewFile bool
|
||||
|
||||
fi, err := os.Stat(rl.curFn)
|
||||
sizeRotation := false
|
||||
if err == nil && rl.rotationSize > 0 && rl.rotationSize <= fi.Size() {
|
||||
forceNewFile = true
|
||||
sizeRotation = true
|
||||
}
|
||||
|
||||
if baseFn != rl.curBaseFn {
|
||||
generation = 0
|
||||
// even though this is the first write after calling New(),
|
||||
// check if a new file needs to be created
|
||||
if rl.forceNewFile {
|
||||
forceNewFile = true
|
||||
}
|
||||
} else {
|
||||
if !useGenerationalNames && !sizeRotation {
|
||||
// nothing to do
|
||||
return rl.outFh, nil
|
||||
}
|
||||
forceNewFile = true
|
||||
generation++
|
||||
}
|
||||
if forceNewFile {
|
||||
// A new file has been requested. Instead of just using the
|
||||
// regular strftime pattern, we create a new file name using
|
||||
// generational names such as "foo.1", "foo.2", "foo.3", etc
|
||||
var name string
|
||||
for {
|
||||
if generation == 0 {
|
||||
name = filename
|
||||
} else {
|
||||
name = fmt.Sprintf("%s.%d", filename, generation)
|
||||
}
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
filename = name
|
||||
break
|
||||
}
|
||||
generation++
|
||||
}
|
||||
}
|
||||
// make sure the dir is existed, eg:
|
||||
// ./foo/bar/baz/hello.log must make sure ./foo/bar/baz is existed
|
||||
dirname := filepath.Dir(filename)
|
||||
if err := os.MkdirAll(dirname, 0755); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to create directory %s", dirname)
|
||||
}
|
||||
// if we got here, then we need to create a file
|
||||
fh, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to open file %s: %s", rl.pattern, err)
|
||||
}
|
||||
|
||||
if err := rl.rotate_nolock(filename); err != nil {
|
||||
err = errors.Wrap(err, "failed to rotate")
|
||||
if bailOnRotateFail {
|
||||
// Failure to rotate is a problem, but it's really not a great
|
||||
// idea to stop your application just because you couldn't rename
|
||||
// your log.
|
||||
//
|
||||
// We only return this error when explicitly needed (as specified by bailOnRotateFail)
|
||||
//
|
||||
// However, we *NEED* to close `fh` here
|
||||
if fh != nil { // probably can't happen, but being paranoid
|
||||
fh.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||
}
|
||||
|
||||
rl.outFh.Close()
|
||||
rl.outFh = fh
|
||||
rl.curBaseFn = baseFn
|
||||
rl.curFn = filename
|
||||
rl.generation = generation
|
||||
|
||||
if h := rl.eventHandler; h != nil {
|
||||
go h.Handle(&FileRotatedEvent{
|
||||
prev: previousFn,
|
||||
current: filename,
|
||||
})
|
||||
}
|
||||
return fh, nil
|
||||
}
|
||||
|
||||
// CurrentFileName returns the current file name that
|
||||
// the RotateLogs object is writing to
|
||||
func (rl *RotateLogs) CurrentFileName() string {
|
||||
rl.mutex.RLock()
|
||||
defer rl.mutex.RUnlock()
|
||||
return rl.curFn
|
||||
}
|
||||
|
||||
var patternConversionRegexps = []*regexp.Regexp{
|
||||
regexp.MustCompile(`%[%+A-Za-z]`),
|
||||
regexp.MustCompile(`\*+`),
|
||||
}
|
||||
|
||||
type cleanupGuard struct {
|
||||
enable bool
|
||||
fn func()
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (g *cleanupGuard) Enable() {
|
||||
g.mutex.Lock()
|
||||
defer g.mutex.Unlock()
|
||||
g.enable = true
|
||||
}
|
||||
func (g *cleanupGuard) Run() {
|
||||
g.fn()
|
||||
}
|
||||
|
||||
// Rotate forcefully rotates the log files. If the generated file name
|
||||
// clash because file already exists, a numeric suffix of the form
|
||||
// ".1", ".2", ".3" and so forth are appended to the end of the log file
|
||||
//
|
||||
// Thie method can be used in conjunction with a signal handler so to
|
||||
// emulate servers that generate new log files when they receive a
|
||||
// SIGHUP
|
||||
func (rl *RotateLogs) Rotate() error {
|
||||
rl.mutex.Lock()
|
||||
defer rl.mutex.Unlock()
|
||||
if _, err := rl.getWriter_nolock(true, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rl *RotateLogs) rotate_nolock(filename string) error {
|
||||
lockfn := filename + `_lock`
|
||||
fh, err := os.OpenFile(lockfn, os.O_CREATE|os.O_EXCL, 0644)
|
||||
if err != nil {
|
||||
// Can't lock, just return
|
||||
return err
|
||||
}
|
||||
|
||||
var guard cleanupGuard
|
||||
guard.fn = func() {
|
||||
fh.Close()
|
||||
os.Remove(lockfn)
|
||||
}
|
||||
defer guard.Run()
|
||||
|
||||
if rl.linkName != "" {
|
||||
tmpLinkName := filename + `_symlink`
|
||||
|
||||
// Change how the link name is generated based on where the
|
||||
// target location is. if the location is directly underneath
|
||||
// the main filename's parent directory, then we create a
|
||||
// symlink with a relative path
|
||||
linkDest := filename
|
||||
linkDir := filepath.Dir(rl.linkName)
|
||||
|
||||
baseDir := filepath.Dir(filename)
|
||||
if strings.Contains(rl.linkName, baseDir) {
|
||||
tmp, err := filepath.Rel(linkDir, filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `failed to evaluate relative path from %#v to %#v`, baseDir, rl.linkName)
|
||||
}
|
||||
|
||||
linkDest = tmp
|
||||
}
|
||||
|
||||
if err := os.Symlink(linkDest, tmpLinkName); err != nil {
|
||||
return errors.Wrap(err, `failed to create new symlink`)
|
||||
}
|
||||
|
||||
// the directory where rl.linkName should be created must exist
|
||||
_, err := os.Stat(linkDir)
|
||||
if err != nil { // Assume err != nil means the directory doesn't exist
|
||||
if err := os.MkdirAll(linkDir, 0755); err != nil {
|
||||
return errors.Wrapf(err, `failed to create directory %s`, linkDir)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpLinkName, rl.linkName); err != nil {
|
||||
return errors.Wrap(err, `failed to rename new symlink`)
|
||||
}
|
||||
}
|
||||
|
||||
if rl.maxAge <= 0 && rl.rotationCount <= 0 {
|
||||
return errors.New("panic: maxAge and rotationCount are both set")
|
||||
}
|
||||
|
||||
matches, err := filepath.Glob(rl.globPattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cutoff := rl.clock.Now().Add(-1 * rl.maxAge)
|
||||
var toUnlink []string
|
||||
for _, path := range matches {
|
||||
// Ignore lock files
|
||||
if strings.HasSuffix(path, "_lock") || strings.HasSuffix(path, "_symlink") {
|
||||
continue
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fl, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if rl.maxAge > 0 && fi.ModTime().After(cutoff) {
|
||||
continue
|
||||
}
|
||||
|
||||
if rl.rotationCount > 0 && fl.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
continue
|
||||
}
|
||||
toUnlink = append(toUnlink, path)
|
||||
}
|
||||
|
||||
if rl.rotationCount > 0 {
|
||||
// Only delete if we have more than rotationCount
|
||||
if rl.rotationCount >= uint(len(toUnlink)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
toUnlink = toUnlink[:len(toUnlink)-int(rl.rotationCount)]
|
||||
}
|
||||
|
||||
if len(toUnlink) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard.Enable()
|
||||
go func() {
|
||||
// unlink files on a separate goroutine
|
||||
for _, path := range toUnlink {
|
||||
os.Remove(path)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close satisfies the io.Closer interface. You must
|
||||
// call this method if you performed any writes to
|
||||
// the object.
|
||||
func (rl *RotateLogs) Close() error {
|
||||
rl.mutex.Lock()
|
||||
defer rl.mutex.Unlock()
|
||||
|
||||
if rl.outFh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rl.outFh.Close()
|
||||
rl.outFh = nil
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user