mirror of
https://github.com/langhuihui/monibuca.git
synced 2026-05-09 22:01:20 +08:00
221 lines
4.7 KiB
Go
221 lines
4.7 KiB
Go
package filerotate
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
DidClose func(path string, didRotate bool)
|
|
PathIfShouldRotate func(creationTime time.Time, now time.Time) string
|
|
}
|
|
|
|
type File struct {
|
|
sync.Mutex
|
|
|
|
// Path is the path of the current file
|
|
Path string
|
|
|
|
creationTime time.Time
|
|
|
|
//Location *time.Location
|
|
|
|
config Config
|
|
file *os.File
|
|
|
|
// position in the file of last Write or Write2, exposed for tests
|
|
lastWritePos int64
|
|
}
|
|
|
|
func IsSameDay(t1, t2 time.Time) bool {
|
|
return t1.YearDay() == t2.YearDay()
|
|
}
|
|
|
|
func IsSameHour(t1, t2 time.Time) bool {
|
|
return t1.YearDay() == t2.YearDay() && t1.Hour() == t2.Hour()
|
|
}
|
|
|
|
func New(config *Config) (*File, error) {
|
|
if nil == config {
|
|
return nil, fmt.Errorf("must provide config")
|
|
}
|
|
if config.PathIfShouldRotate == nil {
|
|
return nil, fmt.Errorf("must provide config.ShouldRotate")
|
|
}
|
|
file := &File{
|
|
config: *config,
|
|
}
|
|
err := file.reopenIfNeeded()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return file, nil
|
|
}
|
|
|
|
func MakeDailyRotateInDir(dir string, fileNameSuffix string) func(time.Time, time.Time) string {
|
|
return func(creationTime time.Time, now time.Time) string {
|
|
if IsSameDay(creationTime, now) {
|
|
return ""
|
|
}
|
|
name := now.Format("20060102")
|
|
if fileNameSuffix != "" {
|
|
name = strings.Replace(fileNameSuffix, "$T", name, 1)
|
|
//name = name + "-" + fileNameSuffix
|
|
} else {
|
|
name += ".txt"
|
|
}
|
|
return filepath.Join(dir, name)
|
|
}
|
|
}
|
|
|
|
func MakeHourlyRotateInDir(dir string, fileNameSuffix string) func(time.Time, time.Time) string {
|
|
return func(creationTime time.Time, now time.Time) string {
|
|
if IsSameHour(creationTime, now) {
|
|
return ""
|
|
}
|
|
name := now.Format("20060102_15")
|
|
if fileNameSuffix != "" {
|
|
name = strings.Replace(fileNameSuffix, "$T", name, 1)
|
|
//name = name + "-" + fileNameSuffix
|
|
} else {
|
|
name += ".txt"
|
|
}
|
|
return filepath.Join(dir, name)
|
|
}
|
|
}
|
|
|
|
// NewDaily creates a new file, rotating daily in a given directory
|
|
func NewDaily(dir string, fileUniqueName string, didClose func(path string, didRotate bool)) (*File, error) {
|
|
daily := MakeDailyRotateInDir(dir, fileUniqueName)
|
|
config := Config{
|
|
DidClose: didClose,
|
|
PathIfShouldRotate: daily,
|
|
}
|
|
return New(&config)
|
|
}
|
|
|
|
// NewHourly creates a new file, rotating hourly in a given directory
|
|
func NewHourly(dir string, logUniqueName string, didClose func(path string, didRotate bool)) (*File, error) {
|
|
hourly := MakeHourlyRotateInDir(dir, logUniqueName)
|
|
config := Config{
|
|
DidClose: didClose,
|
|
PathIfShouldRotate: hourly,
|
|
}
|
|
return New(&config)
|
|
}
|
|
|
|
func (f *File) close(didRotate bool) error {
|
|
if f.file == nil {
|
|
return nil
|
|
}
|
|
err := f.file.Close()
|
|
f.file = nil
|
|
if err == nil && f.config.DidClose != nil {
|
|
f.config.DidClose(f.Path, didRotate)
|
|
}
|
|
return err
|
|
}
|
|
|
|
/*
|
|
func nowInMaybeLocation(loc *time.Location) time.Time {
|
|
now := time.Now()
|
|
if loc != nil {
|
|
now = now.In(loc)
|
|
}
|
|
return now
|
|
}
|
|
*/
|
|
|
|
func (f *File) open(path string) error {
|
|
f.Path = path
|
|
f.creationTime = time.Now()
|
|
// we can't assume that the dir for the file already exists
|
|
dir := filepath.Dir(f.Path)
|
|
err := os.MkdirAll(dir, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// would be easier to open with os.O_APPEND but Seek() doesn't work in that case
|
|
flag := os.O_CREATE | os.O_WRONLY
|
|
f.file, err = os.OpenFile(f.Path, flag, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = f.file.Seek(0, io.SeekEnd)
|
|
return err
|
|
}
|
|
|
|
func (f *File) reopenIfNeeded() error {
|
|
now := time.Now()
|
|
newPath := f.config.PathIfShouldRotate(f.creationTime, now)
|
|
if newPath == "" {
|
|
return nil
|
|
}
|
|
err := f.close(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return f.open(newPath)
|
|
}
|
|
|
|
func (f *File) write(d []byte, sync bool) (int64, int, error) {
|
|
err := f.reopenIfNeeded()
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
f.lastWritePos, err = f.file.Seek(0, io.SeekCurrent)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
n, err := f.file.Write(d)
|
|
if err != nil {
|
|
return 0, n, err
|
|
}
|
|
if sync {
|
|
err = f.file.Sync()
|
|
}
|
|
return f.lastWritePos, n, err
|
|
}
|
|
|
|
// Write writes data to a file
|
|
func (f *File) Write(d []byte) (int, error) {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
|
|
_, n, err := f.write(d, false)
|
|
return n, err
|
|
}
|
|
|
|
// Write2 writes data to a file, optionally syncs to disk. To enable users to later
|
|
// seek to where the data was written, it returns offset at which the data was
|
|
// written, number of bytes and error.
|
|
// You can get path of the file from f.Path
|
|
func (f *File) Write2(d []byte, sync bool) (int64, int, error) {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
|
|
writtenAtPos, n, err := f.write(d, sync)
|
|
return writtenAtPos, n, err
|
|
}
|
|
|
|
func (f *File) Close() error {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
|
|
return f.close(false)
|
|
}
|
|
|
|
// Sync commits the current contents of the file to stable storage.
|
|
func (f *File) Sync() error {
|
|
f.Lock()
|
|
defer f.Unlock()
|
|
|
|
return f.file.Sync()
|
|
}
|