zap源码浏览(2)

上篇文章次要是介绍了zap库的构造和性能劣势,然而其提供的只是根底的日志写入性能,理论工作须要日志具备大小切割,日志压缩,保留指定天数等性能,此时就须要应用另外一个开源库lumberjack(https://github.com/natefinch/...

1. lumberjack 库介绍

Logger构造体

type Logger struct {    Filename string `json:"filename" yaml:"filename"` //文件名    MaxSize int `json:"maxsize" yaml:"maxsize"` // 最大保留文件大小,默认100M    MaxAge int `json:"maxage" yaml:"maxage"` // 最大保留天数    MaxBackups int `json:"maxbackups" yaml:"maxbackups"` // 最大保留个数    LocalTime bool `json:"localtime" yaml:"localtime"` //文件以工夫命名的格局    Compress bool `json:"compress" yaml:"compress"` // 是否压缩    size int64     // 以后文件大小    file *os.File     mu   sync.Mutex    millCh    chan bool    startMill sync.Once}

该库对外提供的接口比较简单,次要是Write函数。

func (l *Logger) Write(p []byte) (n int, err error) {    l.mu.Lock()    defer l.mu.Unlock()    writeLen := int64(len(p))    if writeLen > l.max() { // 字节长度超过单个文件最大值        return 0, fmt.Errorf(            "write length %d exceeds maximum file size %d", writeLen, l.max(),        )    }    if l.file == nil { // 没有指定保留文件名, 创立一个        if err = l.openExistingOrNew(len(p)); err != nil {            return 0, err        }    }    if l.size+writeLen > l.max() { // 单个文件大于指定的最大值,须要拆分        if err := l.rotate(); err != nil {            return 0, err        }    }    n, err = l.file.Write(p) // 失常的文件写入内容    l.size += int64(n)    return n, err}

openExistingOrNew 函数

func (l *Logger) openExistingOrNew(writeLen int) error {    l.mill()    filename := l.filename()//写入日志的文件,带有门路    info, err := osStat(filename) //查看日志文件状态    if os.IsNotExist(err) { // 依据谬误后果,看看是否须要新建文件。        return l.openNew()    }    if err != nil {        return fmt.Errorf("error getting log file info: %s", err)    }    if info.Size()+int64(writeLen) >= l.max() { // 原有的日志文件加上要写的字节大于要求的最大值就拆分        return l.rotate()    }    file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644) //以追加和只写的模式关上已有的文件    if err != nil {        // if we fail to open the old log file for some reason, just ignore        // it and open a new log file.        return l.openNew()    }    l.file = file    l.size = info.Size()    return nil}

openNew函数,创立新的文件

func (l *Logger) openNew() error {    err := os.MkdirAll(l.dir(), 0755)    if err != nil {        return fmt.Errorf("can't make directories for new logfile: %s", err)    }    name := l.filename()    mode := os.FileMode(0600)    info, err := osStat(name)    if err == nil {        // Copy the mode off the old logfile.        mode = info.Mode()        // move the existing file        newname := backupName(name, l.LocalTime) // 以原始名称创立一个带工夫戳的文件名        if err := os.Rename(name, newname); err != nil { //更名原来的文件            return fmt.Errorf("can't rename log file: %s", err)        }        // this is a no-op anywhere but linux        if err := chown(name, info); err != nil {  //以清空文件内容来创立新文件            return err        }    }    // we use truncate here because this should only get called when we've moved    // the file ourselves. if someone else creates the file in the meantime,    // 从新关上文件    f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode)    if err != nil {        return fmt.Errorf("can't open new logfile: %s", err)    }    l.file = f    l.size = 0    return nil}var osChown = os.Chownfunc chown(name string, info os.FileInfo) error {    f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode())    if err != nil {        return err    }    f.Close()// 敞开文件    stat := info.Sys().(*syscall.Stat_t)    return osChown(name, int(stat.Uid), int(stat.Gid)) //调配组}

rotate 函数:

func (l *Logger) rotate() error { // 该函数并不是每个文件必须写满MaxSize, 当要写的日志大于MaxSize, 之前的日志文件不会再写,之后的文件以最新的文件开始写.    if err := l.close(); err != nil { // 先关掉之前的文件        return err    }    if err := l.openNew(); err != nil { // 创立新的文件        return err    }    l.mill()      return nil}

mill 函数:

func (l *Logger) mill() {    l.startMill.Do(func() {        l.millCh = make(chan bool, 1)        go l.millRun() // 开启协程    })    select {    case l.millCh <- true: // 每次Write都会告诉协程做事件。    default:    }}func (l *Logger) millRun() {    for range l.millCh { // 读millCh channel事件        _ = l.millRunOnce()    }}

日志切割外围函数millRunOnce:

func (l *Logger) millRunOnce() error {    if l.MaxBackups == 0 && l.MaxAge == 0 && !l.Compress {        return nil    }    files, err := l.oldLogFiles() //获取该目录下的所有符合条件的文件    if err != nil {        return err    }    var compress, remove []logInfo    if l.MaxBackups > 0 && l.MaxBackups < len(files) {// 超过须要保留的最大文件数        preserved := make(map[string]bool)        var remaining []logInfo        for _, f := range files {            // Only count the uncompressed log file or the            // compressed log file, not both.            fn := f.Name()            if strings.HasSuffix(fn, compressSuffix) {                fn = fn[:len(fn)-len(compressSuffix)]            }            preserved[fn] = true            if len(preserved) > l.MaxBackups {                 remove = append(remove, f)            } else {                remaining = append(remaining, f) //保留最近工夫的指定数量文件            }        }        files = remaining    }    if l.MaxAge > 0 {        diff := time.Duration(int64(24*time.Hour) * int64(l.MaxAge))        cutoff := currentTime().Add(-1 * diff)        var remaining []logInfo        for _, f := range files {             if f.timestamp.Before(cutoff) {                remove = append(remove, f)            } else {                remaining = append(remaining, f) //保留最近工夫的日志            }        }        files = remaining    }    if l.Compress {        for _, f := range files {            if !strings.HasSuffix(f.Name(), compressSuffix) {                compress = append(compress, f) // 须要压缩的文件            }        }    }    for _, f := range remove { //删除不满足条件的文件        errRemove := os.Remove(filepath.Join(l.dir(), f.Name()))        if err == nil && errRemove != nil {            err = errRemove        }    }    for _, f := range compress { // 对须要压缩的文件进行压缩解决        fn := filepath.Join(l.dir(), f.Name())        errCompress := compressLogFile(fn, fn+compressSuffix)        if err == nil && errCompress != nil {            err = errCompress        }    }    return err}func (l *Logger) oldLogFiles() ([]logInfo, error) {    files, err := ioutil.ReadDir(l.dir()) //目录下的所有文件    if err != nil {        return nil, fmt.Errorf("can't read log file directory: %s", err)    }    logFiles := []logInfo{}    prefix, ext := l.prefixAndExt()    for _, f := range files {        if f.IsDir() {            continue        }        if t, err := l.timeFromName(f.Name(), prefix, ext); err == nil {            logFiles = append(logFiles, logInfo{t, f})  //符合条件的文件            continue        }        if t, err := l.timeFromName(f.Name(), prefix, ext+compressSuffix); err == nil {            logFiles = append(logFiles, logInfo{t, f}) //符合条件的压缩文件            continue        }        // error parsing means that the suffix at the end was not generated        // by lumberjack, and therefore it's not a backup file.    }    sort.Sort(byFormatTime(logFiles)) //以最近工夫的日志排在后面    return logFiles, nil}

压缩办法compressLogFile:

func compressLogFile(src, dst string) (err error) {    f, err := os.Open(src)    if err != nil {        return fmt.Errorf("failed to open log file: %v", err)    }    defer f.Close()    fi, err := osStat(src)    if err != nil {        return fmt.Errorf("failed to stat log file: %v", err)    }    if err := chown(dst, fi); err != nil {        return fmt.Errorf("failed to chown compressed log file: %v", err)    }    // If this file already exists, we presume it was created by    // a previous attempt to compress the log file.    gzf, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())    if err != nil {        return fmt.Errorf("failed to open compressed log file: %v", err)    }    defer gzf.Close()    gz := gzip.NewWriter(gzf)    defer func() {        if err != nil {            os.Remove(dst)            err = fmt.Errorf("failed to compress log file: %v", err)        }    }()    if _, err := io.Copy(gz, f); err != nil {        return err    }    if err := gz.Close(); err != nil {        return err    }    if err := gzf.Close(); err != nil {        return err    }    if err := f.Close(); err != nil {        return err    }    if err := os.Remove(src); err != nil {        return err    }    return nil}