zoukankan      html  css  js  c++  java
  • baseFileWriter.go

    package blog4go

    import (
    "fmt"
    "os"
    "sync"
    "time"
    )

    const (
    // unit of sizes

    _ = iota // ignore first value by assigning to blank identifier
    // KB unit of kilobyte
    KB int64 = 1 << (10 * iota)
    // MB unit of megabyte
    MB
    // GB unit of gigabyte
    GB

    // default logrotate condition

    // DefaultRotateSize is default size when size base logrotate needed
    DefaultRotateSize = 500 * MB
    // DefaultRotateLines is default lines when lines base logrotate needed
    DefaultRotateLines = 2000000 // 2 million

    // DefaultLogRetentionCount is the default days of logs to be keeped
    DefaultLogRetentionCount = 7
    )

    // baseFileWriter defines a writer for single file.
    // It suppurts partially write while formatting message, logging level filtering,
    // logrotate, user defined hook for every logging action, change configuration
    // on the fly and logging with colors.
    type baseFileWriter struct {
    // configuration about file
    // full path of the file, the same as configuration
    fileName string
    // current file name of the writer, may be changed with logrotate
    currentFileName string
    // the file object
    file *os.File

    // the BLog
    blog *BLog

    // close sign, default false
    // set this tag true if writer is closed
    closed bool

    // configuration about user defined logging hook
    // actual hook instance
    hook Hook
    // hook is called when message level exceed level of logging action
    hookLevel LevelType
    // it determines whether hook is called async, default true
    hookAsync bool

    // configuration about logrotate
    // exclusive lock use in logrotate
    lock *sync.RWMutex

    // configuration about time base logrotate
    // sign of time base logrotate, default false
    // set this tag true if logrotate in time base mode
    timeRotated bool
    // signal send when time base rotate needed
    timeRotateSig chan bool

    // configuration about size && line base logrotate
    // sign of line base logrotate, default false
    // set this tag true if logrotate in line base mode
    lineRotated bool
    // line base logrotate threshold
    rotateLines int
    // total lines written from last size && line base logrotate
    currentLines int
    // sign of size base logrotate, default false
    // set this tag true if logrotate in size base mode
    sizeRotated bool
    // size rotate按行数、大小rotate, 后缀 xxx.1, xxx.2
    // signal send when size && line base logrotate
    sizeRotateSig chan bool
    // size base logrotate threshold
    rotateSize int64
    // total size written after last size && line logrotate
    currentSize int64
    // channel used to sum up sizes written from last logrotate
    logSizeChan chan int

    // number of logs retention when time base logrotate or size base logrotate
    retentions int64

    // sign decided logging with colors or not, default false
    colored bool
    }

    // NewBaseFileWriter initialize a base file writer
    func NewBaseFileWriter(fileName string, timeRotated bool) (err error) {
    singltonLock.Lock()
    defer singltonLock.Unlock()

    if nil != blog {
    return ErrAlreadyInit
    }

    baseFileWriter, err := newBaseFileWriter(fileName, timeRotated)
    if nil != err {
    return err
    }

    blog = baseFileWriter
    return err
    }

    // newbaseFileWriter create a single file writer instance and return the poionter
    // of it. When any errors happened during creation, a null writer and appropriate
    // will be returned.
    // fileName must be an absolute path to the destination log file
    // rotate determine if it will logrotate
    func newBaseFileWriter(fileName string, timeRotated bool) (fileWriter *baseFileWriter, err error) {
    fileWriter = new(baseFileWriter)
    fileWriter.fileName = fileName
    // open file target file
    if timeRotated {
    fileName = fmt.Sprintf("%s.%s", fileName, timeCache.Date())
    }
    file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0644))
    fileWriter.file = file
    fileWriter.currentFileName = fileName
    if nil != err {
    return nil, err
    }
    fileWriter.blog = NewBLog(file)

    fileWriter.closed = false

    // about logrotate
    fileWriter.lock = new(sync.RWMutex)
    fileWriter.timeRotated = timeRotated
    fileWriter.timeRotateSig = make(chan bool)
    fileWriter.sizeRotateSig = make(chan bool)
    fileWriter.logSizeChan = make(chan int, 8192)

    fileWriter.lineRotated = false
    fileWriter.rotateSize = DefaultRotateSize
    fileWriter.currentSize = 0

    fileWriter.sizeRotated = false
    fileWriter.rotateLines = DefaultRotateLines
    fileWriter.currentLines = 0
    fileWriter.retentions = DefaultLogRetentionCount

    fileWriter.colored = false

    // log hook
    fileWriter.hook = nil
    fileWriter.hookLevel = DEBUG
    fileWriter.hookAsync = true

    go fileWriter.daemon()

    return fileWriter, nil
    }

    // daemon run in background as NewbaseFileWriter called.
    // It flushes writer buffer every 1 second.
    // It decides whether a time base when logrotate is needed.
    // It sums up lines && sizes already written. Alse it does the lines &&
    // size base logrotate
    func (writer *baseFileWriter) daemon() {
    // tick every seconds
    // time base logrotate
    t := time.Tick(1 * time.Second)
    // tick every second
    // auto flush writer buffer
    f := time.Tick(1 * time.Second)

    DaemonLoop:
    for {
    select {
    case <-f:
    if writer.Closed() {
    break DaemonLoop
    }

    writer.blog.flush()
    case <-t:
    if writer.Closed() {
    break DaemonLoop
    }

    if writer.timeRotated {
    // if fileName not equal to currentFileName, it needs a time base logrotate
    if fileName := fmt.Sprintf("%s.%s", writer.fileName, timeCache.Date()); writer.currentFileName != fileName {
    writer.resetFile()
    writer.currentFileName = fileName

    // when it needs to expire logs
    if writer.retentions > 0 {
    // format the expired log file name
    date := timeCache.Now().Add(time.Duration(-24*(writer.retentions+1)) * time.Hour).Format(DateFormat)
    expiredFileName := fmt.Sprintf("%s.%s", writer.fileName, date)
    // check if expired log exists
    if _, err := os.Stat(expiredFileName); nil == err {
    os.Remove(expiredFileName)
    }
    }
    }
    }

    // analyse lines && size written
    // do lines && size base logrotate
    case size := <-writer.logSizeChan:
    if writer.Closed() {
    break DaemonLoop
    }

    if !writer.sizeRotated && !writer.lineRotated {
    continue
    }

    // TODO have any better solution?
    // use func to ensure writer.lock will be released
    writer.lock.Lock()
    writer.currentSize += int64(size)
    writer.currentLines++
    writer.lock.Unlock()

    if (writer.sizeRotated && writer.currentSize >= writer.rotateSize) || (writer.lineRotated && writer.currentLines >= writer.rotateLines) {
    // need lines && size base logrotate
    var oldName, newName string
    oldName = fmt.Sprintf("%s.%d", writer.currentFileName, writer.retentions)
    // check if expired log exists
    if _, err := os.Stat(oldName); os.IsNotExist(err) {
    os.Remove(oldName)
    }
    if writer.retentions > 0 {

    for i := writer.retentions - 1; i > 0; i-- {
    oldName = fmt.Sprintf("%s.%d", writer.currentFileName, i)
    newName = fmt.Sprintf("%s.%d", writer.currentFileName, i+1)
    os.Rename(oldName, newName)
    }
    os.Rename(writer.currentFileName, oldName)

    writer.resetFile()
    }
    }
    }
    }
    }

    // resetFile reset current writing file
    func (writer *baseFileWriter) resetFile() {
    writer.lock.Lock()
    defer writer.lock.Unlock()

    fileName := writer.fileName
    if writer.timeRotated {
    fileName = fmt.Sprintf("%s.%s", fileName, timeCache.Date())
    }
    file, _ := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0644))
    writer.blog.resetFile(file)
    writer.file.Close()
    writer.file = file

    writer.currentSize = 0
    writer.currentLines = 0
    }

    // write writes pure message with specific level
    func (writer *baseFileWriter) write(level LevelType, args ...interface{}) {
    var size = 0

    if writer.closed {
    return
    }

    defer func() {
    // 异步调用log hook
    if nil != writer.hook && !(level < writer.hookLevel) {
    if writer.hookAsync {
    go func(level LevelType, args ...interface{}) {
    writer.hook.Fire(level, args...)
    }(level, args...)

    } else {
    writer.hook.Fire(level, args...)
    }
    }

    // logrotate
    if writer.sizeRotated || writer.lineRotated {
    writer.logSizeChan <- size
    }
    }()

    size = writer.blog.write(level, args...)
    }

    // write formats message with specific level and write it
    func (writer *baseFileWriter) writef(level LevelType, format string, args ...interface{}) {
    // 格式化构造message
    // 边解析边输出
    // 使用 % 作占位符

    // 统计日志size
    var size = 0

    if writer.closed {
    return
    }

    defer func() {
    // 异步调用log hook
    if nil != writer.hook && !(level < writer.hookLevel) {
    if writer.hookAsync {
    go func(level LevelType, format string, args ...interface{}) {
    writer.hook.Fire(level, fmt.Sprintf(format, args...))
    }(level, format, args...)

    } else {
    writer.hook.Fire(level, fmt.Sprintf(format, args...))
    }
    }

    // logrotate
    if writer.sizeRotated || writer.lineRotated {
    writer.logSizeChan <- size
    }
    }()

    size = writer.blog.writef(level, format, args...)
    }

    // Closed get writer status
    func (writer *baseFileWriter) Closed() bool {
    writer.lock.RLock()
    defer writer.lock.RUnlock()
    return writer.closed
    }

    // Close close file writer
    func (writer *baseFileWriter) Close() {
    if writer.Closed() {
    return
    }

    writer.lock.Lock()
    defer writer.lock.Unlock()

    writer.closed = true
    writer.blog.flush()
    writer.blog.Close()
    writer.blog = nil
    writer.file.Close()
    close(writer.logSizeChan)
    close(writer.timeRotateSig)
    close(writer.sizeRotateSig)
    }

    // TimeRotated get timeRotated
    func (writer *baseFileWriter) TimeRotated() bool {
    writer.lock.RLock()
    defer writer.lock.RUnlock()
    return writer.timeRotated
    }

    // SetTimeRotated toggle time base logrotate on the fly
    func (writer *baseFileWriter) SetTimeRotated(timeRotated bool) {
    writer.lock.Lock()
    defer writer.lock.Unlock()
    writer.timeRotated = timeRotated
    }

    // Retentions get log retention days
    func (writer *baseFileWriter) Retentions() int64 {
    writer.lock.RLock()
    defer writer.lock.RUnlock()
    return writer.retentions
    }

    // SetExpiredDays set how many days of logs will keep
    func (writer *baseFileWriter) SetRetentions(retentions int64) {
    writer.lock.Lock()
    defer writer.lock.Unlock()
    if retentions < 1 {
    return
    }
    writer.retentions = retentions
    }

    // RotateSize get log rotate size
    func (writer *baseFileWriter) RotateSize() int64 {
    writer.lock.RLock()
    defer writer.lock.RUnlock()
    return writer.rotateSize
    }

    // SetRotateSize set size when logroatate
    func (writer *baseFileWriter) SetRotateSize(rotateSize int64) {
    writer.lock.Lock()
    defer writer.lock.Unlock()
    if rotateSize > 0 {
    writer.sizeRotated = true
    writer.rotateSize = rotateSize
    } else {
    writer.sizeRotated = false
    }
    }

    // RotateLines get log rotate lines
    func (writer *baseFileWriter) RotateLines() int {
    writer.lock.RLock()
    defer writer.lock.RUnlock()
    return writer.rotateLines
    }

    // SetRotateLines set line number when logrotate
    func (writer *baseFileWriter) SetRotateLines(rotateLines int) {
    writer.lock.Lock()
    defer writer.lock.Unlock()
    if rotateLines > 0 {
    writer.lineRotated = true
    writer.rotateLines = rotateLines
    } else {
    writer.lineRotated = false
    }
    }

    // Colored get whether it is log with colored
    func (writer *baseFileWriter) Colored() bool {
    writer.lock.RLock()
    defer writer.lock.RUnlock()
    return writer.colored
    }

    // SetColored set logging color
    func (writer *baseFileWriter) SetColored(colored bool) {
    writer.lock.Lock()
    defer writer.lock.Unlock()
    if colored == writer.colored {
    return
    }

    writer.colored = colored
    initPrefix(colored)
    }

    // Level get log level
    func (writer *baseFileWriter) Level() LevelType {
    writer.lock.RLock()
    defer writer.lock.RUnlock()
    return writer.blog.Level()
    }

    // SetLevel set logging level threshold
    func (writer *baseFileWriter) SetLevel(level LevelType) {
    writer.lock.Lock()
    defer writer.lock.Unlock()
    writer.blog.SetLevel(level)
    }

    // SetHook set hook for the base file writer
    func (writer *baseFileWriter) SetHook(hook Hook) {
    writer.lock.Lock()
    defer writer.lock.Unlock()
    writer.hook = hook
    }

    // SetHookAsync set hook async for base file writer
    func (writer *baseFileWriter) SetHookAsync(async bool) {
    writer.lock.Lock()
    defer writer.lock.Unlock()
    writer.hookAsync = async
    }

    // SetHookLevel set when hook will be called
    func (writer *baseFileWriter) SetHookLevel(level LevelType) {
    writer.lock.Lock()
    defer writer.lock.Unlock()
    writer.hookLevel = level
    }

    // flush flush logs to disk
    func (writer *baseFileWriter) flush() {
    writer.blog.flush()
    }

    // Trace trace
    func (writer *baseFileWriter) Trace(args ...interface{}) {
    if nil == writer.blog || TRACE < writer.blog.Level() {
    return
    }

    writer.write(TRACE, args...)
    }

    // Tracef tracef
    func (writer *baseFileWriter) Tracef(format string, args ...interface{}) {
    if nil == writer.blog || TRACE < writer.blog.Level() {
    return
    }

    writer.writef(TRACE, format, args...)
    }

    // Debug debug
    func (writer *baseFileWriter) Debug(args ...interface{}) {
    if nil == writer.blog || DEBUG < writer.blog.Level() {
    return
    }

    writer.write(DEBUG, args...)
    }

    // Debugf debugf
    func (writer *baseFileWriter) Debugf(format string, args ...interface{}) {
    if nil == writer.blog || DEBUG < writer.blog.Level() {
    return
    }

    writer.writef(DEBUG, format, args...)
    }

    // Info info
    func (writer *baseFileWriter) Info(args ...interface{}) {
    if nil == writer.blog || INFO < writer.blog.Level() {
    return
    }

    writer.write(INFO, args...)
    }

    // Infof infof
    func (writer *baseFileWriter) Infof(format string, args ...interface{}) {
    if nil == writer.blog || INFO < writer.blog.Level() {
    return
    }

    writer.writef(INFO, format, args...)
    }

    // Warn warn
    func (writer *baseFileWriter) Warn(args ...interface{}) {
    if nil == writer.blog || WARNING < writer.blog.Level() {
    return
    }

    writer.write(WARNING, args...)
    }

    // Warnf warn
    func (writer *baseFileWriter) Warnf(format string, args ...interface{}) {
    if nil == writer.blog || WARNING < writer.blog.Level() {
    return
    }

    writer.writef(WARNING, format, args...)
    }

    // Error error
    func (writer *baseFileWriter) Error(args ...interface{}) {
    if nil == writer.blog || ERROR < writer.blog.Level() {
    return
    }

    writer.write(ERROR, args...)
    }

    // Errorf errorf
    func (writer *baseFileWriter) Errorf(format string, args ...interface{}) {
    if nil == writer.blog || ERROR < writer.blog.Level() {
    return
    }

    writer.writef(ERROR, format, args...)
    }

    // Critical critical
    func (writer *baseFileWriter) Critical(args ...interface{}) {
    if nil == writer.blog || CRITICAL < writer.blog.Level() {
    return
    }

    writer.write(CRITICAL, args...)
    }

    // Criticalf criticalf
    func (writer *baseFileWriter) Criticalf(format string, args ...interface{}) {
    if nil == writer.blog || CRITICAL < writer.blog.Level() {
    return
    }

    writer.writef(CRITICAL, format, args...)
    }

  • 相关阅读:
    SQL中的count()函数内加条件
    SpringBoot+Vue实现第三方微博登录(二)
    SpringBoot+Vue实现第三方百度登录(一)
    Javascript unescape 的 Sql Server 实现
    DataGridView 行内编辑探索
    JQuery Offset实验与应用
    垃圾代码是这样练成的......
    关于前天发现的可能是VS2005服务器资源管理器的bug ...
    调试
    可怕的误导,来自(iHeavy Inc.公司的高级顾问Sean Hull的谬论... )
  • 原文地址:https://www.cnblogs.com/zhangboyu/p/7449633.html
Copyright © 2011-2022 走看看