package blog4go

import (
"bufio"
"errors"
"fmt"
"io"
"os"
"strings"
"sync"
)

const (
// EOL end of a line
EOL = '\n'
// ESCAPE escape character
ESCAPE = '\\'
// PLACEHOLDER placeholder
PLACEHOLDER = '%'
)

var (
// blog is the singleton instance use for blog.write/writef
blog Writer

// global mutex log used for singlton
singltonLock *sync.Mutex

// DefaultBufferSize bufio buffer size
DefaultBufferSize = 4096 // default memory page size
// ErrInvalidFormat invalid format error
ErrInvalidFormat = errors.New("Invalid format type")
// ErrAlreadyInit show that blog is already initialized once
ErrAlreadyInit = errors.New("blog4go has been already initialized")
)

// Writer interface is a common definition of any writers in this package.
// Any struct implements Writer interface must implement functions below.
// Close is used for close the writer and free any elements if needed.
// write is an internal function that write pure message with specific
// logging level.
// writef is an internal function that formatting message with specific
// logging level. Placeholders in the format string will be replaced with
// args given.
// Both write and writef may have an asynchronous call of user defined
// function before write and writef function end..
type Writer interface {
// Close do anything end before program end
Close()

// SetLevel set logging level threshold
SetLevel(level LevelType)
// Level get log level
Level() LevelType

// write/writef functions with different levels
write(level LevelType, args ...interface{})
writef(level LevelType, format string, args ...interface{})
Debug(args ...interface{})
Debugf(format string, args ...interface{})
Trace(args ...interface{})
Tracef(format string, args ...interface{})
Info(args ...interface{})
Infof(format string, args ...interface{})
Warn(args ...interface{})
Warnf(format string, args ...interface{})
Error(args ...interface{})
Errorf(format string, args ...interface{})
Critical(args ...interface{})
Criticalf(format string, args ...interface{})

// flush log to disk
flush()

// hook
SetHook(hook Hook)
SetHookLevel(level LevelType)
SetHookAsync(async bool)

// logrotate
SetTimeRotated(timeRotated bool)
TimeRotated() bool
SetRotateSize(rotateSize int64)
RotateSize() int64
SetRotateLines(rotateLines int)
RotateLines() int
SetRetentions(retentions int64)
Retentions() int64
SetColored(colored bool)
Colored() bool
}

func init() {
singltonLock = new(sync.Mutex)
DefaultBufferSize = os.Getpagesize()
}

// NewWriterFromConfigAsFile initialize a writer according to given config file
// configFile must be the path to the config file
func NewWriterFromConfigAsFile(configFile string) (err error) {
singltonLock.Lock()
defer singltonLock.Unlock()
if nil != blog {
return ErrAlreadyInit
}

// read config from file
config, err := readConfig(configFile)
if nil != err {
return
}

if err = config.valid(); nil != err {
return
}

multiWriter := new(MultiWriter)

multiWriter.level = DEBUG
if level := LevelFromString(config.MinLevel); level.valid() {
multiWriter.level = level
}

multiWriter.closed = false
multiWriter.writers = make(map[LevelType]Writer)

for _, filter := range config.Filters {
var rotate = false
var timeRotate = false
var isSocket = false
var isConsole = false

var f *os.File
var blog *BLog
var fileLock *sync.RWMutex

// get file path
var filePath string
if (file{}) != filter.File {
// file do not need logrotate
filePath = filter.File.Path
rotate = false

f, err = os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0644))
if nil != err {
return err
}
blog = NewBLog(f)
fileLock = new(sync.RWMutex)
} else if (rotateFile{}) != filter.RotateFile {
// file need logrotate
filePath = filter.RotateFile.Path
rotate = true
timeRotate = TypeTimeBaseRotate == filter.RotateFile.Type
fileName := filePath
if timeRotate {
fileName = fmt.Sprintf("%s.%s", fileName, timeCache.Date())
}
f, err = os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0644))
if nil != err {
return err
}
blog = NewBLog(f)
fileLock = new(sync.RWMutex)
} else if (socket{}) != filter.Socket {
isSocket = true
} else {
// use console writer as default
isConsole = true
}

levels := strings.Split(filter.Levels, ",")
for _, levelStr := range levels {
var level LevelType
if level = LevelFromString(levelStr); !level.valid() {
return ErrInvalidLevel
}

if isConsole {
// console writer
writer, err := newConsoleWriter(filter.Console.Redirect)
if nil != err {
return err
}

multiWriter.writers[level] = writer
continue
}

if isSocket {
// socket writer
writer, err := newSocketWriter(filter.Socket.Network, filter.Socket.Address)
if nil != err {
return err
}

multiWriter.writers[level] = writer
continue
}

// init a base file writer
writer, err := newBaseFileWriter(filePath, timeRotate)
if nil != err {
return err
}

if rotate {
// set logrotate strategy
if TypeTimeBaseRotate == filter.RotateFile.Type {
writer.SetTimeRotated(true)
writer.SetRetentions(filter.RotateFile.Retentions)
} else if TypeSizeBaseRotate == filter.RotateFile.Type {
writer.SetRotateSize(filter.RotateFile.RotateSize)
writer.SetRotateLines(filter.RotateFile.RotateLines)
writer.SetRetentions(filter.RotateFile.Retentions)
} else {
return ErrInvalidRotateType
}
}

writer.file = f
writer.blog = blog
writer.lock = fileLock

// set color
multiWriter.SetColored(filter.Colored)
multiWriter.writers[level] = writer
}
}

blog = multiWriter
return
}

// BLog struct is a threadsafe log writer inherit bufio.Writer
type BLog struct {
// logging level
// every message level exceed this level will be written
level LevelType

// input io
in io.Writer

// bufio.Writer object of the input io
writer *bufio.Writer

// exclusive lock while calling write function of bufio.Writer
lock *sync.Mutex

// closed tag
closed bool
}

// NewBLog create a BLog instance and return the pointer of it.
// fileName must be an absolute path to the destination log file
func NewBLog(in io.Writer) (blog *BLog) {
blog = new(BLog)
blog.in = in
blog.level = TRACE
blog.lock = new(sync.Mutex)
blog.closed = false

blog.writer = bufio.NewWriterSize(in, DefaultBufferSize)
return
}

// write writes pure message with specific level
func (blog *BLog) write(level LevelType, args ...interface{}) int {
blog.lock.Lock()
defer blog.lock.Unlock()

// 统计日志size
var size = 0
format := fmt.Sprint(args...)

blog.writer.Write(timeCache.Format())
blog.writer.WriteString(level.prefix())
blog.writer.WriteString(format)
blog.writer.WriteByte(EOL)

size = len(timeCache.Format()) + len(level.prefix()) + len(format) + 1
return size
}

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

// 统计日志size
var size = 0

// 识别占位符标记
var tag = false
var tagPos int
// 转义字符标记
var escape = false
// 在处理的args 下标
var n int
// 未输出的,第一个普通字符位置
var last int
var s int

blog.writer.Write(timeCache.Format())
blog.writer.WriteString(level.prefix())

size += len(timeCache.Format()) + len(level.prefix())

for i, v := range format {
if tag {
switch v {
case 'd', 'f', 'v', 'b', 'o', 'x', 'X', 'c', 'p', 't', 's', 'T', 'q', 'U', 'e', 'E', 'g', 'G':
if escape {
escape = false
}

s, _ = blog.writer.WriteString(fmt.Sprintf(format[tagPos:i+1], args[n]))
size += s
n++
last = i + 1
tag = false
//转义符
case ESCAPE:
if escape {
blog.writer.WriteByte(ESCAPE)
size++
}
escape = !escape
//默认
default:

}

} else {
// 占位符,百分号
if PLACEHOLDER == format[i] && !escape {
tag = true
tagPos = i
s, _ = blog.writer.WriteString(format[last:i])
size += s
escape = false
}
}
}
blog.writer.WriteString(format[last:])
blog.writer.WriteByte(EOL)

size += len(format[last:]) + 1
return size
}

// Flush flush buffer to disk
func (blog *BLog) flush() {
blog.lock.Lock()
defer blog.lock.Unlock()

if blog.closed {
return
}

blog.writer.Flush()
}

// Close close file writer
func (blog *BLog) Close() {
blog.lock.Lock()
defer blog.lock.Unlock()

if nil == blog || blog.closed {
return
}

blog.closed = true
blog.writer.Flush()
blog.writer = nil
}

// In return the input io.Writer
func (blog *BLog) In() io.Writer {
return blog.in
}

// Level return logging level threshold
func (blog *BLog) Level() LevelType {
return blog.level
}

// SetLevel set logging level threshold
func (blog *BLog) SetLevel(level LevelType) *BLog {
blog.level = level
return blog
}

// resetFile resets file descriptor of the writer with specific file name
func (blog *BLog) resetFile(in io.Writer) (err error) {
blog.lock.Lock()
defer blog.lock.Unlock()

blog.writer.Flush()

blog.in = in
blog.writer.Reset(in)

return
}

// SetBufferSize set bufio buffer size in bytes
func SetBufferSize(size int) {
DefaultBufferSize = size
}

// Level get log level
func Level() LevelType {
return blog.Level()
}

// SetLevel set level for logging action
func SetLevel(level LevelType) {
blog.SetLevel(level)
}

// SetHook set hook for logging action
func SetHook(hook Hook) {
blog.SetHook(hook)
}

// SetHookLevel set when hook will be called
func SetHookLevel(level LevelType) {
blog.SetHookLevel(level)
}

// SetHookAsync set whether hook is called async
func SetHookAsync(async bool) {
blog.SetHookAsync(async)
}

// Colored get whether it is log with colored
func Colored() bool {
return blog.Colored()
}

// SetColored set logging color
func SetColored(colored bool) {
blog.SetColored(colored)
}

// TimeRotated get timeRotated
func TimeRotated() bool {
return blog.TimeRotated()
}

// SetTimeRotated toggle time base logrotate on the fly
func SetTimeRotated(timeRotated bool) {
blog.SetTimeRotated(timeRotated)
}

// Retentions get retentions
func Retentions() int64 {
return blog.Retentions()
}

// SetRetentions set how many logs will keep after logrotate
func SetRetentions(retentions int64) {
blog.SetRetentions(retentions)
}

// RotateSize get rotateSize
func RotateSize() int64 {
return blog.RotateSize()
}

// SetRotateSize set size when logroatate
func SetRotateSize(rotateSize int64) {
blog.SetRotateSize(rotateSize)
}

// RotateLines get rotateLines
func RotateLines() int {
return blog.RotateLines()
}

// SetRotateLines set line number when logrotate
func SetRotateLines(rotateLines int) {
blog.SetRotateLines(rotateLines)
}

// Flush flush logs to disk
func Flush() {
blog.flush()
}

// Trace static function for Trace
func Trace(args ...interface{}) {
blog.Trace(args...)
}

// Tracef static function for Tracef
func Tracef(format string, args ...interface{}) {
blog.Tracef(format, args...)
}

// Debug static function for Debug
func Debug(args ...interface{}) {
blog.Debug(args...)
}

// Debugf static function for Debugf
func Debugf(format string, args ...interface{}) {
blog.Debugf(format, args...)
}

// Info static function for Info
func Info(args ...interface{}) {
blog.Info(args...)
}

// Infof static function for Infof
func Infof(format string, args ...interface{}) {
blog.Infof(format, args...)
}

// Warn static function for Warn
func Warn(args ...interface{}) {
blog.Warn(args...)
}

// Warnf static function for Warnf
func Warnf(format string, args ...interface{}) {
blog.Warnf(format, args...)
}

// Error static function for Error
func Error(args ...interface{}) {
blog.Error(args...)
}

// Errorf static function for Errorf
func Errorf(format string, args ...interface{}) {
blog.Errorf(format, args...)
}

// Critical static function for Critical
func Critical(args ...interface{}) {
blog.Critical(args...)
}

// Criticalf static function for Criticalf
func Criticalf(format string, args ...interface{}) {
blog.Criticalf(format, args...)
}

// Close close the logger
func Close() {
singltonLock.Lock()
defer singltonLock.Unlock()

if nil == blog {
return
}

blog.Close()
blog = nil
}

blog4go.go的更多相关文章

  1. 改进log4go的一些设想

    log4go 的 4.0.2 版本(https://github.com/ccpaging/log4go/tree/4.0.2)发布以后, 看了看别的 go 语言日志文件设计.发现了一篇好文: log ...

  2. baseFileWriter.go

    package blog4go import ( "fmt" "os" "sync" "time" ) const ( ...

  3. config.go

    package blog4go import ( "encoding/xml" "errors" "io/ioutil" "os& ...

  4. fileWriter.go

    package blog4go import ( "fmt" "path" "strings" ) // NewFileWriter ini ...

  5. consoleWriter.go

    package blog4go import ( "fmt" "os" "time" ) // ConsoleWriter is a con ...

  6. level.go

    package blog4go import ( "fmt" "strings" ) // LevelType type defined for logging ...

  7. socketWriter.go

    package blog4go import ( "bytes" "fmt" "net" "sync" ) // Soc ...

  8. multiWriter.go

    package blog4go import ( "errors" "fmt" ) var ( // ErrFilePathNotFound 文件路径找不到 E ...

  9. timeCache.go

    package blog4go import ( "sync" "time" ) const ( // PrefixTimeFormat  时间格式前缀 Pre ...

随机推荐

  1. 排序算法入门之插入排序(java实现)

    插入排序思想:相当于插入元素,对于第i个元素,i之前的元素已经是有序的了,这时候将第i个元素依次与前面元素比较,插入合适的位置.

  2. Java的运行原理

    在Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器.这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口.编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由 ...

  3. 修改flume源码,使其HTTPSource具备访问路径功能

    目前有一个需求,就是Flume可以作为一个类似于tomcat的服务器,可以通过post请求进行访问,并且路径需要:ip:port/contextPath格式. 经过一些资料获悉,httpSource只 ...

  4. 使用nginx sticky实现基于cookie的负载均衡

    在多台后台服务器的环境下,我们为了确保一个客户只和一台服务器通信,我们势必使用长连接.使用什么方式来实现这种连接呢,常见的有使用nginx自带的ip_hash来做,我想这绝对不是一个好的办法,如果前端 ...

  5. ansible常见模块

    模块的使用 查看模块帮助 ansible-doc -l 查看所有模块 ansible-doc -s MODULE_NAME 查看指定模块的详细帮助 ansible命令应用基础 语法: ansible ...

  6. windows10上pip install channels

    之前一直在MBP上做开发,在windows偶尔改一次代码,最近在windows上Pipi nstall了一次Django Channels,其中到twisted那步出现数坑 1. Microsoft  ...

  7. DjangoUeditor项目的集成

    DjangoUeditor这个项目,出品人已经不再提供维护支持. 最近在一个使用到aliyun oss的项目里集成了一次这个东西,当然我之前在普通文件上传的北京下已经集成过很多次了. 主要修改的东西就 ...

  8. DUIR Framework 相关技术介绍

    开发者在搭建界面自动化测试框架时,又或者在开发界面自动化控制的机器人时,往往需要对界面进行自动化的程序控制.而现在公司内部使用的杜尔自动化框架,就是一个封装了界面自动化控制逻辑的程序框架.基于该框架, ...

  9. SSM框架下声明式事务管理(注解配置方式)

    一.spring-mybatis.xml文件中加入事务管理配置 <?xml version="1.0" encoding="UTF-8"?> < ...

  10. Java多线程:线程间通信之volatile与sychronized

    由前文Java内存模型我们熟悉了Java的内存工作模式和线程间的交互规范,本篇从应用层面讲解Java线程间通信. Java为线程间通信提供了三个相关的关键字volatile, synchronized ...