Created
October 14, 2021 02:02
-
-
Save tsingakbar/a69a5ea577966488d348213e407c619c to your computer and use it in GitHub Desktop.
simple golang log rotator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package goutils | |
import ( | |
"errors" | |
"fmt" | |
"io" | |
"os" | |
"path/filepath" | |
"strconv" | |
"time" | |
) | |
var ( | |
ErrorNilFD = errors.New("underlying fd in rotateFile is nil") | |
) | |
type rotateFile struct { | |
date time.Time // will rotate after this day ends | |
maxFragSize uint64 // will rotate after file size exceeds this | |
curFragSize uint64 | |
fragidx uint32 | |
basePath string // will rotate as basePath.date.fragidx | |
fd *os.File | |
} | |
func NewRotateFile(basePath string, maxFragSize uint64) io.WriteCloser { | |
var rf = &rotateFile{ | |
date: time.Now(), | |
maxFragSize: maxFragSize, | |
fragidx: 0, | |
basePath: basePath, | |
fd: nil, | |
} | |
rf.rotate(false) | |
return rf | |
} | |
func (self *rotateFile) dateStr() string { | |
return fmt.Sprintf("%d-%02d-%02d", self.date.Year(), self.date.Month(), self.date.Day()) | |
} | |
func (self *rotateFile) archive() { | |
var rotatePath = fmt.Sprintf("%s.%s.%03d", self.basePath, self.dateStr(), self.fragidx) | |
if err := os.Rename(self.basePath, rotatePath); err != nil { | |
fmt.Fprintf(os.Stderr, "Failed to rename %s to %s: %v\n", self.basePath, rotatePath, err) | |
} | |
} | |
func (self *rotateFile) createfile() { | |
var err error | |
if self.fd, err = os.OpenFile(self.basePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666); err != nil { | |
fmt.Fprintf(os.Stderr, "Failed to OpenFile(\"%s\", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666): %v\n", self.basePath, err) | |
os.Exit(1) | |
} | |
} | |
func (self *rotateFile) scanToNextIdx() { | |
datePath := fmt.Sprintf("%s.%s.", self.basePath, self.dateStr()) | |
if existing, err := filepath.Glob(datePath + "*"); err == nil { | |
for _, rotatePath := range existing { | |
if idx, err := strconv.ParseUint(rotatePath[len(datePath):], 10, 32); err == nil { | |
if uint32(idx) >= self.fragidx { | |
self.fragidx = uint32(idx) + 1 | |
} | |
} | |
} | |
} | |
} | |
func (self *rotateFile) rotate(anotherday bool) { | |
if self.fd == nil { | |
// application initiallization, archive old file(if exists and have content), create new file | |
if info, err := os.Stat(self.basePath); err == nil && info.Size() > 0 { | |
self.date = info.ModTime() | |
self.scanToNextIdx() // to avoid conflict with that day's existing archives | |
self.archive() | |
self.date = time.Now() | |
self.fragidx = 0 | |
self.scanToNextIdx() // to avoid conflict with today's existing archives | |
} | |
} else { | |
// normal rotate, archive current file, create new file | |
self.fd.Close() | |
self.fd = nil | |
self.archive() | |
if anotherday { | |
self.date = time.Now() | |
self.fragidx = 0 | |
} else { | |
self.fragidx += 1 | |
} | |
} | |
self.createfile() | |
self.curFragSize = 0 | |
} | |
func (r *rotateFile) Close() error { | |
if fd := r.fd; fd != nil { | |
r.fd = nil | |
return fd.Close() | |
} | |
return nil | |
} | |
func (r *rotateFile) Write(b []byte) (n int, err error) { | |
if r.fd == nil { | |
return 0, ErrorNilFD | |
} | |
var now = time.Now() | |
var anotherday = now.YearDay() != r.date.YearDay() || now.Year() != r.date.Year() | |
if r.curFragSize > r.maxFragSize || anotherday { | |
r.rotate(anotherday) | |
} | |
if r.fd == nil { | |
return 0, ErrorNilFD | |
} | |
n, err = r.fd.Write(b) | |
r.curFragSize += uint64(n) | |
return | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment