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...)
}

baseFileWriter.go的更多相关文章

随机推荐

  1. 计算机网络-TCP/IP HTTP Conclusion

    1.1OSI 与 TCP/IP 各层的结构 1.2 三次握手和四次挥手,TCP为什么三次握手,四次挥手 在第一次消息发送中,A随机选取一个序列号作为自己的初始序号发送给B:第二次消息B使用ack对A的 ...

  2. datagrid 新增,并行内编辑,提交保存

    <a class="mini-button" iconCls="icon-add" onclick="addRow()" plain= ...

  3. 爬虫Scrapy框架运用----房天下二手房数据采集

    在许多电商和互联网金融的公司为了更好地服务用户,他们需要爬虫工程师对用户的行为数据进行搜集.分析和整合,为人们的行为选择提供更多的参考依据,去服务于人们的行为方式,甚至影响人们的生活方式.我们的scr ...

  4. 什么才是java的基础知识?

    近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是java的基础知识?又怎么样才算掌握了java的基础知识呢?这个问题还真值得仔细思考. ...

  5. VS2010断点调试技巧

    设置断点:在如下图中的红色圆点处设置断点,红色圆点表示已经在这行设置断点.快捷键F9.   启动调试:按F5或者点击左边红框中的按钮.右边框是开始执行(不调试)Ctrl+F5. 调试工具栏:下面是工具 ...

  6. 修改was数据源

    本机的RAD运行的工程可以通过修改jpa中的persistence中的jni修改数据源: 对于通过was控制台部署的ear需要在was控制台:资源--jdbc 修改数据源

  7. Spring3.x企业应用开发实战-Spring+Hibernat架构分析

    1: 持久层设计 采用Spring注解方式省略了大量Hibernate ORM配置文件: BaseDAO减少DAO层代码量,只需要编写非通用型的持久层方法: 持久层提供分页支持: Hibernate ...

  8. WebRTC MCU( Multipoint Conferencing Unit)服务器调研

    接触过的有licode.kurento. licode的缺陷:文档支持有限,licode的app client库只有js的 kurento的优势:文档齐全,Demo俱备,封装API比较齐全.它的主要特 ...

  9. vi 常用命令使用說明

    vi是一種文字模式全螢幕文字編輯軟體(Text Editor).對初學者來說,vi是個很難用的工具,一般需要2個星期的時間才能得心應手.之所以介紹vi,其理由如下: vi是Unix上的標準文字編輯軟體 ...

  10. jsoup 使用总结4--高级用法之 script js 脚本

    jsoup 使用总结4--高级用法之 script js 脚本 大部分时候,我们使用jsoup解析网页的时候,都是直接找到某一类元素,或者按某种selector查询:具体使用方法可以参考jsoup官网 ...