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. SSH框架项目开发命名规范

    SSH 框架项目开发命名规范   一.各层包及类命名规范   总体原则:包名所有字母小写,类名采用 "驼峰标识",具体如下:   1. Action 类      包命名规范:co ...

  2. 学习ASP.NET Core Razor 编程系列八——并发处理

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  3. 《JUnit实战(第2版)》读书笔记

    第1章 JUnit起步 主要了解JUnit,如何安装.运行JUnit 要点 JUnit4不需要像JUnit3那样extends TestCase类 Junit4基本都是用注解(该书都翻译为注释,但我喜 ...

  4. 6.4 Schema 设计对系统的性能影响

    前面两节中,我们已经分析了在一个数据库应用系统的软环境中应用系统的架构实现和系统中与数据库交互的SQL 语句对系统性能的影响.在这一节我们再分析一下系统的数据模型设计实现对系统的性能影响,更通俗一点就 ...

  5. SQL 逻辑优化 case when 转为 union all

    通常数据库的优化从硬件层面去考虑可分为4个方面: CPU:即降低计算复杂度,如减少sql各类聚合函数,窗口函数,case when等. IO :(较少查询结果集过程中对数据的访问量.数据优化很大程度从 ...

  6. C#WebService 出现No 'Access-Control-Allow-Origin' header is present on the requested resource

    C#WebService 出现No 'Access-Control-Allow-Origin' header is present on the requested resource 解决办法: 在c ...

  7. CSS的display:table

    好久都没有写博客了,似乎总是觉得少了些什么-- 刚好最近在工作中遇到了一个新的东西display:table,这个也是css的布局的一种,而且又是display的,之前已经写过了display的fle ...

  8. java线程之创建线程类

    1.extends Thread方法 class Person extends Thread { int sum1 = 50; // 含参构造器 public Person(String name) ...

  9. JSON数据Key排序

    /// <summary> /// JSON格式化重新排序 /// </summary> /// <param name="jobj">原始JS ...

  10. 程序中编写log日志

    public string logFile; ; private Stream s = null; StreamWriter sw = null; /// <summary> /// 用l ...