logfrog-go/handlers/rotatingFile.go

269 lines
7.3 KiB
Go

package handlers
import (
"bufio"
"compress/gzip"
"fmt"
"os"
"path/filepath"
"sync"
"time"
"codeark.it/Bithero-Agency-Go/logfrog-go"
)
type RotationInterval int
const (
RotateMinutely RotationInterval = iota
RotateHourly
RotateDaily
RotateWeekly
)
const day = time.Hour * 24
const week = day * 7
const DefaultDailyTimeLayout = "2006-01-02"
// Returns the next timeframe for an interval
func (r RotationInterval) GetNextTimeFrame() time.Time {
switch r {
case RotateMinutely:
return time.Now().Add(time.Minute).Truncate(time.Minute);
case RotateHourly:
return time.Now().Add(time.Hour).Truncate(time.Hour);
case RotateDaily:
return time.Now().Add(day).Truncate(day);
case RotateWeekly:
return time.Now().Add(week).Truncate(week);
default:
panic(fmt.Errorf("invalid rotation interval"))
}
}
type RotatingFileHandler struct {
*logfrog.BaseLogHandler
basepath string
file *os.File
lognameFormat string // format for the current logfile
oldLognameFormat string // format for the old logfiles
rotateInterval RotationInterval
nextRotation time.Time
compress bool
compressionLevel int
mu sync.Mutex
}
// Creates a new RotatingFileHandler, that rotates daily, keeps at maximum 7 rotated files,
// compresses rotated logs with an level of gzip.DefaultCompression.
//
// The current logfile is named
//
// logname + ".log"
//
// and rotated logs will be named with the date, i.e.:
//
// logname + "_2006-01-02.log"
//
func NewDailyRotatingFileHandler(basepath, logname string) (*RotatingFileHandler, error) {
return NewRotatingFileHandler(
basepath,
logname + ".log",
logname + "_" + DefaultDailyTimeLayout + ".log",
RotateDaily,
)
}
// Creates an new RotatingFileHandler with the given attributes. Compresses per default with
// a level of gzip.DefaultCompression.
//
// The both format argumens (lognameFormat and oldLognameFormat) will be put through time.Format,
// so the same rules apply. I.e. the string "2006" will be replaced with the current year.
func NewRotatingFileHandler(
basepath, lognameFormat, oldLognameFormat string, rotateInterval RotationInterval,
) (*RotatingFileHandler, error) {
b := logfrog.NewBaseLogHandler()
r := &RotatingFileHandler{
BaseLogHandler: b,
basepath: basepath,
file: nil,
lognameFormat: lognameFormat,
oldLognameFormat: oldLognameFormat,
rotateInterval: rotateInterval,
nextRotation: (rotateInterval).GetNextTimeFrame(),
compress: true,
compressionLevel: gzip.DefaultCompression,
}
r.LogHandler = r
logfilename := r.getLogFilename()
file, err := openOrCreateFile(logfilename)
if err != nil {
return nil, err
}
r.file = file
return r, nil
}
// --------------------------------------------------------------------------------
// Enables compression of rotated logs with the specified compression level
func (r *RotatingFileHandler) EnableCompression(level int) *RotatingFileHandler {
r.compress = true
r.compressionLevel = level
return r
}
// Disables compression of rotated logs
func (r *RotatingFileHandler) DisableCompression() *RotatingFileHandler {
r.compress = false
return r
}
// Sets the format of the filename for the current rotation
func (r *RotatingFileHandler) SetLognameFormat(lognameFormat string) *RotatingFileHandler {
r.lognameFormat = lognameFormat
return r
}
// Sets the format of the filename of rotated logfiles
func (r *RotatingFileHandler) SetOldLognameFormat(oldLognameFormat string) *RotatingFileHandler {
r.oldLognameFormat = oldLognameFormat
return r
}
// Returns the timestamp of the next rotation
func (r *RotatingFileHandler) GetNextRotation() time.Time {
return r.nextRotation
}
// Returns the duration until the next rotation
func (r *RotatingFileHandler) GetUntilNextRotation() time.Duration {
return time.Until(r.nextRotation)
}
// Returns the interval in which the log is rotated
func (r *RotatingFileHandler) GetRotationInterval() RotationInterval {
return r.rotateInterval
}
// --------------------------------------------------------------------------------
func (r *RotatingFileHandler) OnClose() error {
if r.file == nil {
return nil
}
return r.file.Close()
}
// --------------------------------------------------------------------------------
func (r *RotatingFileHandler) getLogFilename() string {
return filepath.Join(r.basepath, r.nextRotation.Format(r.lognameFormat))
}
func (r *RotatingFileHandler) getRotatedLogFilename() string {
return filepath.Join(r.basepath, r.nextRotation.Format(r.oldLognameFormat))
}
func (rf *RotatingFileHandler) compressOldLog(rotatedfilename string) error {
fmt.Printf("begin compressing of %s\n", rotatedfilename);
fr, err := os.Open(rotatedfilename)
if err != nil {
fmt.Printf("[compressing %s] failed to open file: %v\n", rotatedfilename, err);
return err
}
defer fr.Close()
//fw, err := os.OpenFile(rotatedfilename + ".gz", os.O_WRONLY | os.O_CREATE, 0o666)
fw, err := os.Create(rotatedfilename + ".gz")
if err != nil {
fmt.Printf("[compressing %s] failed to open output file: %v\n", rotatedfilename, err);
return err
}
defer fw.Close()
br := bufio.NewReader(fr)
gzw, err := gzip.NewWriterLevel(fw, gzip.DefaultCompression)
if err != nil {
fmt.Printf("[compressing %s] failed to create gzip writer: %v\n", rotatedfilename, err);
return err
}
defer gzw.Close()
_, err = br.WriteTo(gzw)
if err != nil {
fmt.Printf("[compressing %s] failed WriteTo: %v\n", rotatedfilename, err);
gzw.Close()
fw.Close()
os.Remove(rotatedfilename + ".gz")
return err
}
fr.Close()
err = os.Remove(rotatedfilename)
fmt.Printf("[compressing %s] done with compressing: %v\n", rotatedfilename, err);
return err
}
func (r *RotatingFileHandler) doRotation() error {
// first create the filename of the logfile that will be rotated
oldFilename := r.getLogFilename()
// then create the filename for the old logfile with the current nextRotation
rotatedfilename := r.getRotatedLogFilename()
// update nextRotation to the next rotation
r.nextRotation = r.rotateInterval.GetNextTimeFrame()
// create the filename for the current logfile with the current nextRotation
newFilename := r.getLogFilename()
// close the old one and set the field additionally to nil
r.file.Close()
r.file = nil
defer func(){
// open the new logfile for writing
file, err := openOrCreateFile(newFilename)
if err != nil {
panic(err)
}
r.file = file
}()
// rotate the file if necessary
if oldFilename != rotatedfilename {
os.Rename(oldFilename, rotatedfilename)
}
// if compression is enabled, compress the old logfile
if r.compress {
go r.compressOldLog(rotatedfilename)
}
return nil
}
func (r *RotatingFileHandler) Write(buf []byte) error {
r.mu.Lock()
defer r.mu.Unlock()
if time.Now().After(r.nextRotation) {
r.doRotation()
}
_, werr := r.file.Write(buf)
return werr
}