123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- 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()
- }
- 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 {
-
- 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()
-
-
-
-
-
-
-
-
-
-
- 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)
- }
- func (rl *RotateLogs) Write(p []byte) (n int, err error) {
-
- 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)
- }
- func (rl *RotateLogs) getWriter_nolock(bailOnRotateFail, useGenerationalNames bool) (io.Writer, error) {
- generation := rl.generation
- previousFn := rl.curFn
-
-
- 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
-
-
- if rl.forceNewFile {
- forceNewFile = true
- }
- } else {
- if !useGenerationalNames && !sizeRotation {
-
- return rl.outFh, nil
- }
- forceNewFile = true
- generation++
- }
- if forceNewFile {
-
-
-
- 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++
- }
- }
-
-
- dirname := filepath.Dir(filename)
- if err := os.MkdirAll(dirname, 0755); err != nil {
- return nil, errors.Wrapf(err, "failed to create directory %s", dirname)
- }
-
- 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 {
-
-
-
-
-
-
-
- if fh != nil {
- 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
- }
- 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()
- }
- 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 {
-
- return err
- }
- var guard cleanupGuard
- guard.fn = func() {
- fh.Close()
- os.Remove(lockfn)
- }
- defer guard.Run()
- if rl.linkName != "" {
- tmpLinkName := filename + `_symlink`
-
-
-
-
- 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`)
- }
-
- _, err := os.Stat(linkDir)
- if err != nil {
- 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 {
-
- 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 {
-
- 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() {
-
- for _, path := range toUnlink {
- os.Remove(path)
- }
- }()
- return nil
- }
- 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
- }
|