simplelog/filehandler.go
rick.chan 9a07945f08 支持按时间日期记录和限制文件数量的时间日期记录。
Signed-off-by: rick.chan <cy@haoan119.com>
2025-04-21 21:33:09 +08:00

350 lines
7.0 KiB
Go

package simplelog
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"time"
)
func isFileExist(filename string) bool {
info, e := os.Stat(filename)
if e == nil {
return !info.IsDir()
} else {
return os.IsExist(e)
}
}
// FileHandler writes log to a file.
type FileHandler struct {
fd *os.File
}
func (h *FileHandler) InitFile(name string) (*FileHandler, error) {
dir := path.Dir(name)
os.Mkdir(dir, 0777)
f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return nil, err
}
h.fd = f
return h, nil
}
func (h *FileHandler) Write(b []byte) (n int, err error) {
return h.fd.Write(b)
}
func (h *FileHandler) Close() error {
return h.fd.Close()
}
// RotatingFileHandler writes log a file, if file size exceeds maxBytes,
// it will backup current file and open a new one.
//
// max backup file number is set by backupCount, it will delete oldest if backups too many.
type RotatingFileHandler struct {
fd *os.File
fileName string
maxBytes int64
curBytes int64
backupCount uint
}
func (h *RotatingFileHandler) InitRotating(name string, maxBytes int64, backupCount uint) (*RotatingFileHandler, error) {
h.fileName = name
h.maxBytes = maxBytes
h.backupCount = backupCount
if isFileExist(name) {
var err error
h.fd, err = os.OpenFile(name, os.O_CREATE|os.O_RDONLY, 0666)
if err != nil {
return nil, err
}
f, err := h.fd.Stat()
if err != nil {
h.fd.Close()
return nil, err
}
if f.Size() < h.maxBytes {
h.fd.Seek(0, io.SeekEnd)
} else {
h._doRollover()
}
return h, nil
}
dir := path.Dir(name)
os.MkdirAll(dir, 0777)
if maxBytes <= 0 {
return nil, fmt.Errorf("invalid max bytes")
}
var err error
h.fd, err = os.OpenFile(name, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return nil, err
}
f, err := h.fd.Stat()
if err != nil {
return nil, err
}
h.curBytes = f.Size()
return h, nil
}
func (h *RotatingFileHandler) Write(p []byte) (n int, err error) {
h.doRollover()
n, err = h.fd.Write(p)
h.curBytes += int64(n)
return
}
func (h *RotatingFileHandler) Close() error {
if h.fd != nil {
return h.fd.Close()
}
return nil
}
func (h *RotatingFileHandler) doRollover() {
if h.curBytes < h.maxBytes {
return
}
f, err := h.fd.Stat()
if err != nil {
return
}
if h.maxBytes <= 0 {
return
} else if f.Size() < int64(h.maxBytes) {
h.curBytes = f.Size()
return
}
h._doRollover()
}
func (h *RotatingFileHandler) _doRollover() {
if h.backupCount > 0 {
h.fd.Close()
for i := h.backupCount - 1; i > 0; i-- {
sfn := fmt.Sprintf("%s.%d", h.fileName, i)
dfn := fmt.Sprintf("%s.%d", h.fileName, i+1)
os.Rename(sfn, dfn)
}
dfn := fmt.Sprintf("%s.1", h.fileName)
os.Rename(h.fileName, dfn)
h.fd, _ = os.OpenFile(h.fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
h.curBytes = 0
} else {
h.fd.Close()
h.fd, _ = os.OpenFile(h.fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
h.curBytes = 0
}
}
// TimedFileHandler writes log to a file.
type TimedFileHandler struct {
fd *os.File
dir string
name string
interval time.Duration
suffix string
rolloverAt time.Time
}
type WhenInterval int
const (
WhenSecond WhenInterval = iota
WhenMinute
WhenHour
WhenDay
)
func (h *TimedFileHandler) InitTimed(name string, when WhenInterval, interval int64) (*TimedFileHandler, error) {
dir := path.Dir(name)
os.Mkdir(dir, 0777)
h.dir, h.name = filepath.Split(name)
switch when {
case WhenSecond:
h.interval = 1 * time.Second
h.suffix = "2006-01-02-15-04-05"
case WhenMinute:
h.interval = 1 * time.Minute
h.suffix = "2006-01-02-15-04"
case WhenHour:
h.interval = 1 * time.Hour
h.suffix = "2006-01-02-15"
case WhenDay:
h.interval = 1 * time.Hour * 24
h.suffix = "2006-01-02"
default:
return nil, fmt.Errorf("invalid when_rotate: %d", when)
}
h.interval = time.Duration(interval) * h.interval
h.rolloverAt = time.Now()
var err error
h.fd, err = os.OpenFile(filepath.Join(h.dir, h.rolloverAt.Format(h.suffix)+"_"+h.name), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
return h, nil
}
func (h *TimedFileHandler) doRollover() {
if time.Since(h.rolloverAt) > time.Duration(h.interval) {
h.fd.Close()
h.rolloverAt = time.Now()
h.fd, _ = os.OpenFile(filepath.Join(h.dir, h.rolloverAt.Format(h.suffix)+"_"+h.name), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
}
}
func (h *TimedFileHandler) Write(b []byte) (n int, err error) {
h.doRollover()
return h.fd.Write(b)
}
func (h *TimedFileHandler) Close() error {
return h.fd.Close()
}
type TimedRotatingFileHandler struct {
fd *os.File
dir string
name string
interval time.Duration
suffix string
rolloverAt time.Time
backupCount uint
}
func (h *TimedRotatingFileHandler) InitTimedRotating(name string, when WhenInterval, interval int64, backupCount uint) (*TimedRotatingFileHandler, error) {
h.backupCount = backupCount
dir := path.Dir(name)
os.Mkdir(dir, 0777)
h.dir, h.name = filepath.Split(name)
switch when {
case WhenSecond:
h.interval = 1 * time.Second
h.suffix = "2006-01-02-15-04-05"
case WhenMinute:
h.interval = 1 * time.Minute
h.suffix = "2006-01-02-15-04"
case WhenHour:
h.interval = 1 * time.Hour
h.suffix = "2006-01-02-15"
case WhenDay:
h.interval = 1 * time.Hour * 24
h.suffix = "2006-01-02"
default:
return nil, fmt.Errorf("invalid when_rotate: %d", when)
}
h.interval = time.Duration(interval) * h.interval
h.rolloverAt = time.Now()
var err error
h.fd, err = os.OpenFile(filepath.Join(h.dir, h.rolloverAt.Format(h.suffix)+"_"+h.name), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
h._doRollover()
return h, nil
}
func (h *TimedRotatingFileHandler) _doRollover() {
fs, err := h.ListDir(h.dir, h.suffix)
if err != nil {
return
}
if len(fs) > int(h.backupCount) {
for i := 0; i < len(fs)-int(h.backupCount); i++ {
os.Remove(fs[i])
}
}
}
func (h *TimedRotatingFileHandler) doRollover() {
if time.Since(h.rolloverAt) > time.Duration(h.interval) {
h.fd.Close()
h.rolloverAt = time.Now()
h.fd, _ = os.OpenFile(filepath.Join(h.dir, h.rolloverAt.Format(h.suffix)+"_"+h.name), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
}
h._doRollover()
}
func (h *TimedRotatingFileHandler) Write(b []byte) (n int, err error) {
h.doRollover()
return h.fd.Write(b)
}
func (h *TimedRotatingFileHandler) Close() error {
return h.fd.Close()
}
func (h *TimedRotatingFileHandler) ListDir(dir, suffix string) (files []string, err error) {
files = []string{}
_dir, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for _, file := range _dir {
if file.IsDir() {
// 不需要递归读取子目录下的文件
continue
}
names := strings.Split(file.Name(), "_")
if len(names) != 2 {
// 文件名格式不对
continue
}
_, err := time.Parse(h.suffix, names[0])
if err != nil {
// 文件名格式不对
continue
}
if h.name != names[1] {
// 文件名格式不对
continue
}
files = append(files, filepath.Join(dir, file.Name()))
}
return files, nil
}