Skip to content

Instantly share code, notes, and snippets.

@tsingakbar
Created October 14, 2021 02:02
Show Gist options
  • Save tsingakbar/a69a5ea577966488d348213e407c619c to your computer and use it in GitHub Desktop.
Save tsingakbar/a69a5ea577966488d348213e407c619c to your computer and use it in GitHub Desktop.
simple golang log rotator
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