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. obj-c编程15[Cocoa实例02]:KVC和KVO的实际运用

    我们在第16和第17篇中分别介绍了obj-c的KVC与KVO特性,当时举的例子比较fun,太抽象,貌似和实际不沾边哦.那么下面我们就用一个实际中的例子来看看KVC与KVO是如何运用的吧. 该例中用到了 ...

  2. http 状态表

    整理一下xmlHttp.status的值(http 状态表)   状态码 状态码 意义 释义 100 1xx (临时响应)表示临时响应并需要请求者继续执行操作的状态代码.  继续 客户端应当继续发送请 ...

  3. Copy List with Random Pointer(复杂链表复制)

    输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用,否则判题程序 ...

  4. java面试笔试题大汇总

    第一,谈谈final, finally, finalize的区别.  最常被问到.   第二,Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以 ...

  5. 推荐eclipse插件Properties Editor(转)

    Properties Editor 是一款properties文件编辑器. 需求:一般我们在做“国际化”功能时,我们需要properties中文表示方式用unicode表示.eclipse默认prop ...

  6. memocache 分布式搭建

    memcached+magent实现memcached集群   首先说明下memcached存在如下问题 本身没有内置分布式功能,无法实现使用多台Memcache服务器来存储不同的数据,最大程度的使用 ...

  7. codechef Killing Monsters

    题目大意:大厨正在玩一个打怪兽的小游戏.游戏中初始时有 n 只怪兽排成一排,从左到右编号为 0 ∼ n − 1.第 i 只怪兽的初始血量为 hi,当怪兽的血量小于等于 0 时,这只怪兽就挂了. 大厨要 ...

  8. leetcode-判断回文数,非字符串算法(java实现)

    link: https://leetcode-cn.com/problems/palindrome-number/description/ 问题: 判断一个整数是否是回文数.回文数是指正序(从左向右) ...

  9. linux系统下安装tomcat服务器

    一.首先需要关闭linux防火墙(重启后生效) chkconfig iptables off 二.从官网上下载Linux合适版本的tomcat,我现在下来的文件为apache-tomcat-8.5.3 ...

  10. thinkphp实现文件上传

    文件上传详细讲解 http://www.thinkphp.cn/info/194.html 上传根目录不存在问题解决方法 http://www.thinkphp.cn/topic/10779.html