-
Notifications
You must be signed in to change notification settings - Fork 24
/
config.go
290 lines (244 loc) · 7.12 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
package rotatefile
import (
"fmt"
"os"
"time"
"github.com/gookit/goutil/stdio"
"github.com/gookit/goutil/timex"
)
type rotateLevel uint8
const (
levelDay rotateLevel = iota
levelHour
levelMin
levelSec
)
// RotateTime for rotate file. unit is seconds.
//
// EveryDay:
// - "error.log.20201223"
//
// EveryHour, Every30Min, EveryMinute:
// - "error.log.20201223_1500"
// - "error.log.20201223_1530"
// - "error.log.20201223_1523"
type RotateTime int
// built in rotate time constants
const (
EveryDay RotateTime = timex.OneDaySec
EveryHour RotateTime = timex.OneHourSec
Every30Min RotateTime = 30 * timex.OneMinSec
Every15Min RotateTime = 15 * timex.OneMinSec
EveryMinute RotateTime = timex.OneMinSec
EverySecond RotateTime = 1 // only use for tests
)
// Interval get check interval time. unit is seconds.
func (rt RotateTime) Interval() int64 {
return int64(rt)
}
// FirstCheckTime for rotate file.
// - will automatically align the time from the start of each hour.
func (rt RotateTime) FirstCheckTime(now time.Time) time.Time {
interval := rt.Interval()
switch rt.level() {
case levelDay:
return timex.DayEnd(now)
case levelHour:
// should check on H:59:59.500
return timex.HourStart(now).Add(timex.OneHour - 500*time.Millisecond)
case levelMin:
// eg: minutes=5
minutes := int(interval / 60)
nextMin := now.Minute() + minutes
// will rotate at next hour start. eg: now.Minute()=57, nextMin=62.
if nextMin >= 60 {
return timex.HourStart(now).Add(timex.OneHour)
}
// eg: now.Minute()=37, nextMin=42, will get nextDur=40
nextDur := time.Duration(nextMin).Round(time.Duration(minutes))
return timex.HourStart(now).Add(nextDur)
default: // levelSec
return now.Add(time.Duration(interval) * time.Second)
}
}
// level for rotate time
func (rt RotateTime) level() rotateLevel {
switch {
case rt >= timex.OneDaySec:
return levelDay
case rt >= timex.OneHourSec:
return levelHour
case rt >= EveryMinute:
return levelMin
default:
return levelSec
}
}
// TimeFormat get log file suffix format
//
// EveryDay:
// - "error.log.20201223"
//
// EveryHour, Every30Min, EveryMinute:
// - "error.log.20201223_1500"
// - "error.log.20201223_1530"
// - "error.log.20201223_1523"
func (rt RotateTime) TimeFormat() (suffixFormat string) {
suffixFormat = "20060102_1500" // default is levelHour
switch rt.level() {
case levelDay:
suffixFormat = "20060102"
case levelHour:
suffixFormat = "20060102_1500"
case levelMin:
suffixFormat = "20060102_1504"
case levelSec:
suffixFormat = "20060102_150405"
}
return
}
// String rotate type to string
func (rt RotateTime) String() string {
switch rt.level() {
case levelDay:
return fmt.Sprintf("Every %d Day", rt.Interval()/timex.OneDaySec)
case levelHour:
return fmt.Sprintf("Every %d Hours", rt.Interval()/timex.OneHourSec)
case levelMin:
return fmt.Sprintf("Every %d Minutes", rt.Interval()/timex.OneMinSec)
default: // levelSec
return fmt.Sprintf("Every %d Seconds", rt.Interval())
}
}
// Clocker is the interface used for determine the current time
type Clocker interface {
Now() time.Time
}
// ClockFn func
type ClockFn func() time.Time
// Now implements the Clocker
func (fn ClockFn) Now() time.Time {
return fn()
}
// ConfigFn for setting config
type ConfigFn func(c *Config)
// Config struct for rotate dispatcher
type Config struct {
// Filepath the log file path, will be rotating. eg: "logs/error.log"
Filepath string `json:"filepath" yaml:"filepath"`
// FilePerm for create log file. default DefaultFilePerm
FilePerm os.FileMode `json:"file_perm" yaml:"file_perm"`
// RotateMode for rotate file. default ModeRename
RotateMode RotateMode `json:"rotate_mode" yaml:"rotate_mode"`
// MaxSize file contents max size, unit is bytes.
// If is equals zero, disable rotate file by size
//
// default see DefaultMaxSize
MaxSize uint64 `json:"max_size" yaml:"max_size"`
// RotateTime the file rotate interval time, unit is seconds.
// If is equals zero, disable rotate file by time
//
// default: EveryHour
RotateTime RotateTime `json:"rotate_time" yaml:"rotate_time"`
// CloseLock use sync lock on write contents, rotating file.
//
// default: false
CloseLock bool `json:"close_lock" yaml:"close_lock"`
// BackupNum max number for keep old files.
//
// 0 is not limit, default is DefaultBackNum
BackupNum uint `json:"backup_num" yaml:"backup_num"`
// BackupTime max time for keep old files, unit is hours.
//
// 0 is not limit, default is DefaultBackTime
BackupTime uint `json:"backup_time" yaml:"backup_time"`
// Compress determines if the rotated log files should be compressed using gzip.
// The default is not to perform compression.
Compress bool `json:"compress" yaml:"compress"`
// RenameFunc you can custom-build filename for rotate file by size.
//
// default see DefaultFilenameFn
RenameFunc func(filePath string, rotateNum uint) string
// TimeClock for rotate file by time.
TimeClock Clocker
// DebugMode for debug on development.
DebugMode bool
}
func (c *Config) backupDuration() time.Duration {
if c.BackupTime < 1 {
return 0
}
return time.Duration(c.BackupTime) * time.Hour
}
// With more config setting func
func (c *Config) With(fns ...ConfigFn) *Config {
for _, fn := range fns {
fn(c)
}
return c
}
// Create new Writer by config
func (c *Config) Create() (*Writer, error) { return NewWriter(c) }
// IsMode check rotate mode
func (c *Config) IsMode(m RotateMode) bool { return c.RotateMode == m }
// Debug print debug message on development
func (c *Config) Debug(vs ...any) {
if c.DebugMode {
stdio.WriteString("[rotatefile.DEBUG] " + fmt.Sprintln(vs...))
}
}
var (
// DefaultFilePerm perm and flags for create log file
DefaultFilePerm os.FileMode = 0664
// DefaultFileFlags for open log file
DefaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
// DefaultFilenameFn default new filename func
DefaultFilenameFn = func(filepath string, rotateNum uint) string {
suffix := time.Now().Format("010215")
// eg: /tmp/error.log => /tmp/error.log.163021_0001
return filepath + fmt.Sprintf(".%s_%03d", suffix, rotateNum)
}
// DefaultTimeClockFn for create time
DefaultTimeClockFn = ClockFn(func() time.Time {
return time.Now()
})
)
// NewDefaultConfig instance
func NewDefaultConfig() *Config {
return &Config{
MaxSize: DefaultMaxSize,
RotateTime: EveryHour,
BackupNum: DefaultBackNum,
BackupTime: DefaultBackTime,
RenameFunc: DefaultFilenameFn,
TimeClock: DefaultTimeClockFn,
FilePerm: DefaultFilePerm,
}
}
// NewConfig by file path
func NewConfig(filePath string) *Config {
return NewConfigWith(WithFilepath(filePath))
}
// NewConfigWith custom func
func NewConfigWith(fns ...ConfigFn) *Config {
return NewDefaultConfig().With(fns...)
}
// EmptyConfigWith new empty config with custom func
func EmptyConfigWith(fns ...ConfigFn) *Config {
c := &Config{
RenameFunc: DefaultFilenameFn,
TimeClock: DefaultTimeClockFn,
FilePerm: DefaultFilePerm,
}
return c.With(fns...)
}
// WithFilepath setting
func WithFilepath(logfile string) ConfigFn {
return func(c *Config) {
c.Filepath = logfile
}
}
// WithDebugMode setting for debug mode
func WithDebugMode(c *Config) {
c.DebugMode = true
}