229 lines
6.0 KiB
Go
229 lines
6.0 KiB
Go
|
|
package starlog
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
var ErrRotatingFileSinkClosed = errors.New("rotating file sink closed")
|
||
|
|
|
||
|
|
type RotatingFileSink struct {
|
||
|
|
mu sync.Mutex
|
||
|
|
path string
|
||
|
|
policy RotatePolicy
|
||
|
|
checkInterval time.Duration
|
||
|
|
options RotateManageOptions
|
||
|
|
appendMode bool
|
||
|
|
|
||
|
|
file *os.File
|
||
|
|
lastCheck time.Time
|
||
|
|
closed bool
|
||
|
|
}
|
||
|
|
|
||
|
|
func normalizeRotateCheckInterval(interval time.Duration) time.Duration {
|
||
|
|
if interval <= 0 {
|
||
|
|
return time.Second
|
||
|
|
}
|
||
|
|
return interval
|
||
|
|
}
|
||
|
|
|
||
|
|
func ensureLogDir(path string) error {
|
||
|
|
dir := filepath.Dir(path)
|
||
|
|
if dir == "" || dir == "." {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return os.MkdirAll(dir, 0755)
|
||
|
|
}
|
||
|
|
|
||
|
|
func newRotatePolicySink(path string, appendMode bool, policy RotatePolicy, checkInterval time.Duration, options RotateManageOptions) (*RotatingFileSink, error) {
|
||
|
|
if policy == nil {
|
||
|
|
return nil, errors.New("rotate policy is nil")
|
||
|
|
}
|
||
|
|
fullpath, err := filepath.Abs(path)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
sink := &RotatingFileSink{
|
||
|
|
path: fullpath,
|
||
|
|
policy: policy,
|
||
|
|
checkInterval: normalizeRotateCheckInterval(checkInterval),
|
||
|
|
options: options,
|
||
|
|
appendMode: appendMode,
|
||
|
|
}
|
||
|
|
if err = sink.openFileLocked(appendMode); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return sink, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewRotatePolicySink(path string, appendMode bool, policy RotatePolicy, checkInterval time.Duration) (*RotatingFileSink, error) {
|
||
|
|
return newRotatePolicySink(path, appendMode, policy, checkInterval, RotateManageOptions{})
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewManagedRotatePolicySink(path string, appendMode bool, policy RotatePolicy, checkInterval time.Duration, options RotateManageOptions) (*RotatingFileSink, error) {
|
||
|
|
return newRotatePolicySink(path, appendMode, policy, checkInterval, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewRotateByTimeSink(path string, appendMode bool, interval time.Duration, checkInterval time.Duration) (*RotatingFileSink, error) {
|
||
|
|
return NewRotatePolicySink(path, appendMode, NewRotateByTimePolicy(interval), checkInterval)
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewManagedRotateByTimeSink(path string, appendMode bool, interval time.Duration, checkInterval time.Duration, options RotateManageOptions) (*RotatingFileSink, error) {
|
||
|
|
return NewManagedRotatePolicySink(path, appendMode, NewRotateByTimePolicy(interval), checkInterval, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewRotateBySizeSink(path string, appendMode bool, maxSizeBytes int64, checkInterval time.Duration) (*RotatingFileSink, error) {
|
||
|
|
return NewRotatePolicySink(path, appendMode, NewRotateBySizePolicy(maxSizeBytes), checkInterval)
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewManagedRotateBySizeSink(path string, appendMode bool, maxSizeBytes int64, checkInterval time.Duration, options RotateManageOptions) (*RotatingFileSink, error) {
|
||
|
|
return NewManagedRotatePolicySink(path, appendMode, NewRotateBySizePolicy(maxSizeBytes), checkInterval, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewRotateByTimeSizeSink(path string, appendMode bool, interval time.Duration, maxSizeBytes int64, checkInterval time.Duration) (*RotatingFileSink, error) {
|
||
|
|
return NewRotatePolicySink(path, appendMode, NewRotateByTimeSizePolicy(interval, maxSizeBytes), checkInterval)
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewManagedRotateByTimeSizeSink(path string, appendMode bool, interval time.Duration, maxSizeBytes int64, checkInterval time.Duration, options RotateManageOptions) (*RotatingFileSink, error) {
|
||
|
|
return NewManagedRotatePolicySink(path, appendMode, NewRotateByTimeSizePolicy(interval, maxSizeBytes), checkInterval, options)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (sink *RotatingFileSink) openFileLocked(appendMode bool) error {
|
||
|
|
if sink == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
if err := ensureLogDir(sink.path); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
flags := os.O_CREATE | os.O_WRONLY
|
||
|
|
if appendMode {
|
||
|
|
flags |= os.O_APPEND
|
||
|
|
} else {
|
||
|
|
flags |= os.O_TRUNC
|
||
|
|
}
|
||
|
|
fp, err := os.OpenFile(sink.path, flags, 0644)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
sink.file = fp
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (sink *RotatingFileSink) shouldCheckRotateLocked(now time.Time) bool {
|
||
|
|
if sink == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
if sink.lastCheck.IsZero() || now.Sub(sink.lastCheck) >= sink.checkInterval {
|
||
|
|
sink.lastCheck = now
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
func (sink *RotatingFileSink) rotateIfNeededLocked(now time.Time) error {
|
||
|
|
if sink == nil || sink.file == nil || sink.policy == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
info, err := sink.file.Stat()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
entry := &Entry{Time: now}
|
||
|
|
if !sink.policy.ShouldRotate(info, entry) {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
archivePath := strings.TrimSpace(resolveRotateArchivePath(sink.policy, sink.path, now))
|
||
|
|
if archivePath == "" || archivePath == sink.path {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
if err = ensureLogDir(archivePath); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err = sink.file.Close(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
sink.file = nil
|
||
|
|
if err = os.Rename(sink.path, archivePath); err != nil {
|
||
|
|
reopenErr := sink.openFileLocked(true)
|
||
|
|
if reopenErr != nil {
|
||
|
|
return fmt.Errorf("rotate rename failed: %v; reopen failed: %w", err, reopenErr)
|
||
|
|
}
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err = sink.openFileLocked(false); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err = ApplyRotateManageOptions(archivePath, sink.path, sink.options); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (sink *RotatingFileSink) Write(data []byte) error {
|
||
|
|
if sink == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
sink.mu.Lock()
|
||
|
|
defer sink.mu.Unlock()
|
||
|
|
if sink.closed {
|
||
|
|
return ErrRotatingFileSinkClosed
|
||
|
|
}
|
||
|
|
if sink.file == nil {
|
||
|
|
if err := sink.openFileLocked(true); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
now := time.Now()
|
||
|
|
if sink.shouldCheckRotateLocked(now) {
|
||
|
|
if err := sink.rotateIfNeededLocked(now); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_, err := sink.file.Write(data)
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (sink *RotatingFileSink) Sync() error {
|
||
|
|
if sink == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
sink.mu.Lock()
|
||
|
|
defer sink.mu.Unlock()
|
||
|
|
if sink.file == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return sink.file.Sync()
|
||
|
|
}
|
||
|
|
|
||
|
|
func (sink *RotatingFileSink) Close() error {
|
||
|
|
if sink == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
sink.mu.Lock()
|
||
|
|
defer sink.mu.Unlock()
|
||
|
|
if sink.closed {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
sink.closed = true
|
||
|
|
if sink.file == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
err := sink.file.Close()
|
||
|
|
sink.file = nil
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (sink *RotatingFileSink) Path() string {
|
||
|
|
if sink == nil {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
sink.mu.Lock()
|
||
|
|
defer sink.mu.Unlock()
|
||
|
|
return sink.path
|
||
|
|
}
|