diff --git a/app/log/RotatingFile.go b/app/log/RotatingFile.go new file mode 100644 index 0000000..75fb813 --- /dev/null +++ b/app/log/RotatingFile.go @@ -0,0 +1,114 @@ +package log + +import ( + "fmt" + "io" + "os" + "path" + "time" +) + +type RotatingFile struct { + fd *os.File + size int64 + maxSize int64 + ts time.Time + maxAge time.Duration + format string +} + +func open(filename string) (*os.File, error) { + return os.OpenFile(filename, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0o666) +} + +func NewRotatingFile(filename string, maxSize int64, maxAge time.Duration) (*RotatingFile, error) { + if err := os.MkdirAll(path.Dir(filename), 0o766); err != nil && !os.IsExist(err) { + return nil, err + } + + fd, err := open(filename) + if err != nil { + return nil, err + } + + stat, err := fd.Stat() + if err != nil { + return nil, err + } + + return &RotatingFile{ + fd: fd, + size: stat.Size(), + maxSize: maxSize, + ts: time.Now(), + maxAge: maxAge, + format: "2006-01-02_150405", + }, nil +} + +func NewRotatingFileFromConfig(config Config) (*RotatingFile, error) { + return NewRotatingFile(config.GetFilePath(), int64(config.MaxFileSize), config.MaxTime) +} + +func (w *RotatingFile) newFilename(name string) string { + ext := path.Ext(name) + if len(ext) > 0 { + name = name[:len(name)-len(ext)] + } + return fmt.Sprintf("%s-%s%s", name, time.Now().Format(w.format), ext) +} + +// Rotate the file. +func (w *RotatingFile) Rotate() error { + dst, err := os.OpenFile(w.newFilename(w.fd.Name()), os.O_CREATE|os.O_WRONLY, 0o666) + if err != nil { + return err + } + defer dst.Close() + + // Seek to the beginning of file + if _, err = w.fd.Seek(0, io.SeekStart); err != nil { + return err + } + + // And copy the contents to the new file. + if _, err = io.Copy(dst, w.fd); err != nil { + return err + } + + // Then truncate the log. + if err = w.fd.Truncate(0); err != nil { + return err + } + + w.size = 0 + w.ts = time.Now() + + return nil +} + +// Implement io.Writer interface +func (w *RotatingFile) Write(p []byte) (int, error) { + n, err := w.fd.Write(p) + if err != nil { + return n, err + } + + w.size += int64(n) + + // Check if we should rotate + if w.size >= w.maxSize || time.Since(w.ts) >= w.maxAge { + if err := w.Rotate(); err != nil { + return n, err + } + } + + return n, nil +} + +// Implement io.Closer interface +func (w *RotatingFile) Close() error { + err := w.fd.Close() + w.fd = nil + return err +}