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. Python 3 中的json模块使用

    1. 概述 JSON (JavaScript Object Notation)是一种使用广泛的轻量数据格式. Python标准库中的json模块提供了JSON数据的处理功能. Python中一种非常常 ...

  2. climbing stairs(爬楼梯)(动态规划)

    You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb ...

  3. ssh三大框架集成后,jsp中采用forword标签提交时会报错的解决方案

    最近这两天心烦,所以没事就做做三大框架,对于今天遇到了一个烦心的事!或许有很多开发人员对于web.xml拦截器的认识不清,出现了这样的情况 <filter> <filter-name ...

  4. require './ex25' can't load such file

    require './ex25' can't load such file 在练习learn ruby the hard way时候,第25题,发生了一下错误 LoadError: cannot lo ...

  5. 学习Timer定时器

    原文地址:http://www.cppblog.com/ivenher/articles/19969.html setTimer函数用于创建一个计时器,KillTimer函数用于销毁一个计时器.计时器 ...

  6. 上传本地代码及更新代码到GitHub教程

    上传本地代码及更新代码到GitHub教程 上传本地代码 第一步:去github上创建自己的Repository,创建页面如下图所示: 红框为新建的仓库的https地址 第二步: echo " ...

  7. 使用TortoiseGit操作分支的创建与合并

    第一步:创建本地分支 点击右键选择TortoiseGit,选择Create Branch…,在Branch框中填写新分支的名称(若选中”switch to new branch”则直接转到新分支上,省 ...

  8. 基于.net的爬虫应用-DotnetSpider

    最近应朋友的邀请,帮忙做了个简单的爬虫程序,要求不高,主要是方便对不同网站的爬取进行扩展,获取到想要的数据信息即可.当然,基于数据的后期分析功能是后话,以后的随笔我会逐步的介绍. 开源的爬虫框架比较多 ...

  9. Django中数据库表的关联与创建(语言:python)

    首先选择选用的数据库,(本人选用django(1.11.8版本)) 在主项目settings中操作如下: DATABASES = { 'default': { 'ENGINE': 'django.db ...

  10. Maven Scope 依赖范围

    Maven依赖范围就是用来控制依赖与这三种classpath(编译classpath.测试classpath.运行classpath)的关系,Maven有以下几种依赖范围: ·compile:编译依赖 ...