这个包主要的结构就是这个,RotateLogs日志的分割主要是实现了io.Writer ,配合一些其它包使用
type RotateLogs struct {
clock Clock // 时区
curFn string // 当前文件路径
curBaseFn string
globPattern string // 用来匹配文件的
generation int // 用来做日志分割,比如按文件大小,那么就生成test.1 test.2这种
linkName string // 生成软链,指向最新日志文件
maxAge time.Duration // 文件最大保存时间
mutex sync.RWMutex
eventHandler Handler
outFh *os.File // 操作文件对象,最终执行write的对象
pattern *strftime.Strftime
rotationTime time.Duration // 日志切割时间间隔
rotationSize int64 // 日志分割大小
rotationCount uint // 切割数量
forceNewFile bool // 是否强制新建日志文件
}
日志分割主要是实现了io的write接口,如果这个不明白,可以看golang接口相关的知识
// Write satisfies the io.Writer interface. It writes to the
// appropriate file handle that is currently being used.
// If we have reached rotation time, the target file gets
// automatically rotated, and also purged if necessary.
func (rl *RotateLogs) Write(p []byte) (n int, err error) {
// Guard against concurrent writes
rl.mutex.Lock()
defer rl.mutex.Unlock()
out, err := rl.getWriter_nolock(false, false)
if err != nil {
return 0, errors.Wrap(err, `failed to acquite target io.Writer`)
}
return out.Write(p)
}
write方法主要是下面这个方法
// must be locked during this operation
func (rl *RotateLogs) getWriter_nolock(bailOnRotateFail, useGenerationalNames bool) (io.Writer, error) {
generation := rl.generation
previousFn := rl.curFn
// This filename contains the name of the "NEW" filename
// to log to, which may be newer than rl.currentFilename
// 会根据输入的path的规则去生成当前的文件的名称,用于比较是不是需要新建,还是用老的文件
baseFn := rl.genFilename()
filename := baseFn
var forceNewFile bool
// 获取文件的操作对象
fi, err := os.Stat(rl.curFn)
sizeRotation := false
// 判断是否需要按文件大小来分割
if err == nil && rl.rotationSize > 0 && rl.rotationSize <= fi.Size() {
forceNewFile = true
sizeRotation = true
}
// 比较新生成的文件和当前操作的是不是相同,相等用当前对象的outFh就行,不相等的话,
if baseFn != rl.curBaseFn {
generation = 0
// even though this is the first write after calling New(),
// check if a new file needs to be created
if rl.forceNewFile {
forceNewFile = true
}
} else {
if !useGenerationalNames && !sizeRotation {
// nothing to do
return rl.outFh, nil
}
forceNewFile = true
generation++
}
if forceNewFile {
// A new file has been requested. Instead of just using the
// regular strftime pattern, we create a new file name using
// generational names such as "foo.1", "foo.2", "foo.3", etc
var name string
for {
if generation == 0 {
name = filename
} else {
name = fmt.Sprintf("%s.%d", filename, generation)
}
if _, err := os.Stat(name); err != nil {
filename = name
break
}
generation++
}
}
// make sure the dir is existed, eg:
// ./foo/bar/baz/hello.log must make sure ./foo/bar/baz is existed
dirname := filepath.Dir(filename)
if err := os.MkdirAll(dirname, 0755); err != nil {
return nil, errors.Wrapf(err, "failed to create directory %s", dirname)
}
// if we got here, then we need to create a file
fh, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return nil, errors.Errorf("failed to open file %s: %s", rl.pattern, err)
}
if err := rl.rotate_nolock(filename); err != nil {
err = errors.Wrap(err, "failed to rotate")
if bailOnRotateFail {
// Failure to rotate is a problem, but it's really not a great
// idea to stop your application just because you couldn't rename
// your log.
//
// We only return this error when explicitly needed (as specified by bailOnRotateFail)
//
// However, we *NEED* to close `fh` here
if fh != nil { // probably can't happen, but being paranoid
fh.Close()
}
return nil, err
}
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
}
rl.outFh.Close()
rl.outFh = fh
rl.curBaseFn = baseFn
rl.curFn = filename
rl.generation = generation
if h := rl.eventHandler; h != nil {
go h.Handle(&FileRotatedEvent{
prev: previousFn,
current: filename,
})
}
return fh, nil
}
网友评论