1package appenders
2
3import (
4	"fmt"
5	"github.com/ian-kent/go-log/layout"
6	"github.com/ian-kent/go-log/levels"
7	"os"
8	"strconv"
9	"strings"
10	"sync"
11)
12
13type rollingFileAppender struct {
14	Appender
15	layout         layout.Layout
16	MaxFileSize    int64
17	MaxBackupIndex int
18
19	filename   string
20	file       *os.File
21	append     bool
22	writeMutex sync.Mutex
23
24	bytesWritten int64
25}
26
27func RollingFile(filename string, append bool) *rollingFileAppender {
28	a := &rollingFileAppender{
29		layout:         layout.Default(),
30		MaxFileSize:    104857600,
31		MaxBackupIndex: 1,
32		append:         append,
33		bytesWritten:   0,
34	}
35	err := a.SetFilename(filename)
36	if err != nil {
37		fmt.Printf("Error opening file: %s\n", err)
38		return nil
39	}
40	return a
41}
42
43func (a *rollingFileAppender) Close() {
44	if a.file != nil {
45		a.file.Close()
46		a.file = nil
47	}
48}
49
50func (a *rollingFileAppender) Write(level levels.LogLevel, message string, args ...interface{}) {
51	m := a.Layout().Format(level, message, args...)
52	if !strings.HasSuffix(m, "\n") {
53		m += "\n"
54	}
55
56	a.writeMutex.Lock()
57	a.file.Write([]byte(m))
58
59	a.bytesWritten += int64(len(m))
60	if a.bytesWritten >= a.MaxFileSize {
61		a.bytesWritten = 0
62		a.rotateFile()
63	}
64
65	a.writeMutex.Unlock()
66}
67
68func (a *rollingFileAppender) Layout() layout.Layout {
69	return a.layout
70}
71
72func (a *rollingFileAppender) SetLayout(layout layout.Layout) {
73	a.layout = layout
74}
75
76func (a *rollingFileAppender) Filename() string {
77	return a.filename
78}
79
80func (a *rollingFileAppender) SetFilename(filename string) error {
81	if a.filename != filename || a.file == nil {
82		a.closeFile()
83		a.filename = filename
84		err := a.openFile()
85		return err
86	}
87	return nil
88}
89
90func (a *rollingFileAppender) rotateFile() {
91	a.closeFile()
92
93	lastFile := a.filename + "." + strconv.Itoa(a.MaxBackupIndex)
94	if _, err := os.Stat(lastFile); err == nil {
95		os.Remove(lastFile)
96	}
97
98	for n := a.MaxBackupIndex; n > 0; n-- {
99		f1 := a.filename + "." + strconv.Itoa(n)
100		f2 := a.filename + "." + strconv.Itoa(n+1)
101		os.Rename(f1, f2)
102	}
103
104	os.Rename(a.filename, a.filename+".1")
105
106	a.openFile()
107}
108func (a *rollingFileAppender) closeFile() {
109	if a.file != nil {
110		a.file.Close()
111		a.file = nil
112	}
113}
114func (a *rollingFileAppender) openFile() error {
115	mode := os.O_WRONLY | os.O_APPEND | os.O_CREATE
116	if !a.append {
117		mode = os.O_WRONLY | os.O_CREATE
118	}
119	f, err := os.OpenFile(a.filename, mode, 0666)
120	a.file = f
121	return err
122}
123