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. ruby和linux shell共同编程的示例

    有了shell为毛还要ruby呢?话不能这么说,有些小功能用ruby还是很方便的,比如说字符串的反转再加1功能用shell来写就比较麻烦.str="123456",我们定义一个反转 ...

  2. IOS使用FMDB封装的数据库增删改查操作

    // //  DBHelper.h //  LessonStoryBoard // //  Created by 袁冬冬 on 15/10/29. //  Copyright (c) 2015年 袁冬 ...

  3. ORACLE 博客文章目录

    从接触ORACLE到深入学习,已有好几年了,虽然写的博客不多,质量也参差不齐,但是,它却是成长的历程的点点滴滴的一个见证,见证了我在这条路上的寻寻觅觅,朝圣的心路历程,现在将ORACLE方面的博客整理 ...

  4. Spring定时任务(一):SpringTask使用

    背景:在日常开发中,经常会用到任务调度这类程序.实现方法常用的有:A. 通过java.util.Timer.TimerTask实现. B.通过Spring自带的SpringTask. C. 通过Spr ...

  5. ROS:使用Qt Creator创建GUI程序(一)

    开发环境: Ubuntu14.04 ROS indigo version Qt Creator 3.0.1 based on Qt 5.2.1 步骤如下:(按照下面命令一步步来,亲测可行) (一)安装 ...

  6. Django 1.11 release note简明解读

    1.首先1.11这个版本是一个LTS版本 2.第一个支持python3.6的版本,最后一个支持python2.*的版本 3.Deprecating warnings 默认不再显示,同时建议第三方包开始 ...

  7. Django Channels简明实践

    1.安装,如果你已经安装django1.9+,那就不要用官方文档的安装指令了,把-U去掉,直接用: sudo pip install channels 2.自定义的普通Channel的名称只能包含a- ...

  8. JBOSSAS 5.x/6.x 反序列化命令执行漏洞(CVE-2017-12149)

    本文主要记录一下JBOSSAS 5.x/6.x 反序列化命令执行漏洞的测试过程 仅供学习 文中利用到漏洞环境由phith0n维护: JBoss 5.x/6.x 反序列化漏洞(CVE-2017-1214 ...

  9. JavaScript (一、ECMAScript )

    一.js简介和变量 1.JavaScript的概述组成和特点 a.JavaScript 是脚本语言,是世界上最流行的编程语言,这门语言可用于 HTML 和 web,更可广泛 用于服务器.PC.笔记本电 ...

  10. Python:怎样用线程将任务并行化?

    如果待处理任务满足: 可拆分,即任务可以被拆分为多个子任务,或任务是多个相同的任务的集合: 任务不是CPU密集型的,如任务涉及到较多IO操作(如文件读取和网络数据处理) 则使用多线程将任务并行运行,能 ...