log4go 源码剖析

cchd0001 · · 2719 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

log4go 源码

下载

https://github.com/alecthomas/log4go.git

源码目录

.
..
config.go
examples 
filelog.go
.git
.gitignore
LICENSE
log4go.go
log4go_test.go
pattlog.go
README
socklog.go
termlog.go
wrapper.go

源码剖析

对问题的抽象和解决方案

这里写图片描述

从源码中一一找到对应代码

写什么 ?

log4- X 系列发迹于log4j , 然后被拓展到各种语言,提供便捷的基于等级制度的日志记录库。
那么写的当然是日志。 在log4go中对日志的描述统一为结构体 LogRecord

log4go.go

type LogRecord struct {
    Level   Level     // The log level // 日志等级
    Created time.Time // The time at which the log message was created (nanoseconds) // 纳秒级别的日期。日志发生的时间
    Source  string    // The message source // 日志的基本信息 ( 行号 , 文件名 ) 
    Message string    // The log message // 日志携带的消息
}

写到哪里 ?

这里首先规定了些日志的基本接口 LogWriter

log4go.go

// This is an interface for anything that should be able to write logs
type LogWriter interface {
    // This will be called to log a LogRecord message.
    LogWrite(rec *LogRecord)

    // This should clean up anything lingering about the LogWriter, as it is called before
    // the LogWriter is removed.  LogWrite should not be called after Close.
    Close()
}

即, 有一个能够接收LogRecord指针来记录对应的日志的 LogWrite接口。 有一个收尾的Close接口即可。

终端

termlog.go

// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>.  All rights reserved.

package log4go

import (
    "fmt"
    "io"
    "os"
    "time"
)

var stdout io.Writer = os.Stdout

// This is the standard writer that prints to standard output.
type ConsoleLogWriter struct {
    format string
    w      chan *LogRecord
}

// This creates a new ConsoleLogWriter
func NewConsoleLogWriter() *ConsoleLogWriter {
    consoleWriter := &ConsoleLogWriter{
        format: "[%T %D] [%L] (%S) %M",
        w:      make(chan *LogRecord, LogBufferLength),
    }
    go consoleWriter.run(stdout)
    return consoleWriter
}
// 自定义格式
func (c *ConsoleLogWriter) SetFormat(format string) {
    c.format = format
}
func (c *ConsoleLogWriter) run(out io.Writer) {
// 持续监听channel , 将数据格式化后打印到终端
    for rec := range c.w {
        fmt.Fprint(out, FormatLogRecord(c.format, rec))
    }
}

// This is the ConsoleLogWriter's output method.  This will block if the output
// buffer is full.
func (c *ConsoleLogWriter) LogWrite(rec *LogRecord) {
    c.w <- rec
}

// Close stops the logger from sending messages to standard output.  Attempts to
// send log messages to this logger after a Close have undefined behavior.
func (c *ConsoleLogWriter) Close() {
    close(c.w)
    time.Sleep(50 * time.Millisecond) // Try to give console I/O time to complete
}

文件

写文件的时候比写终端要复杂写, 主要是设计到一些日志文件的属性, 比如最大文件大小,最大文件行数,定期更换并备份日志文件 。。。。

下面是我将这些属性相关的代码剔除后的代码

filelog.go

// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>.  All rights reserved.

package log4go

import (
    "fmt"
    "os"
    "time"
)

// This log writer sends output to a file
type FileLogWriter struct {
    rec chan *LogRecord
    // The opened file
    filename string
    file     *os.File
    // The logging format
    format string
}

// This is the FileLogWriter's output method
func (w *FileLogWriter) LogWrite(rec *LogRecord) {
    w.rec <- rec
}

func (w *FileLogWriter) Close() {
    close(w.rec)
    w.file.Sync()
}

// NewFileLogWriter creates a new LogWriter which writes to the given file and
// The standard log-line format is:
//   [%D %T] [%L] (%S) %M
func NewFileLogWriter(fname string, rotate bool) *FileLogWriter {
    w := &FileLogWriter{
        rec:       make(chan *LogRecord, LogBufferLength),
        filename:  fname,
        format:    "[%D %T] [%L] (%S) %M",
    }

    // open the file for the first time
    if err := w.intRotate(); err != nil {
        fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
        return nil
    }

    go func() {
        defer func() {
            if w.file != nil {
                fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
                w.file.Close()
            }
        }()

        for {
            select {
            // 这里使用select 是因为完整的代码中还有别的channel . 属性相关。
            case rec, ok := <-w.rec:
                if !ok {
                    return
                }
                fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
                    return
                }

                // Perform the write
                n, err := fmt.Fprint(w.file, FormatLogRecord(w.format, rec))
                if err != nil {
                    fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
                    return
                }
            }
        }
    }()

    return w
}



// Set the logging format (chainable).  Must be called before the first log
// message is written.
func (w *FileLogWriter) SetFormat(format string) *FileLogWriter {
    w.format = format
    return w
}

// If this is called in a threaded context, it MUST be synchronized
func (w *FileLogWriter) intRotate() error {
    // Close any log file that may be open
    if w.file != nil {
        fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
        w.file.Close()
    }
    // Open the log file
    fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
    if err != nil {
        return err
    }
    w.file = fd
    return nil
}

// 预定义出一种XML格式
// NewXMLLogWriter is a utility method for creating a FileLogWriter set up to
// output XML record log messages instead of line-based ones.
func NewXMLLogWriter(fname string, rotate bool) *FileLogWriter {
    return NewFileLogWriter(fname, rotate).SetFormat(
        `   <record level="%L">
        <timestamp>%D %T</timestamp>
        <source>%S</source>
        <message>%M</message>
    </record>`).SetHeadFoot("<log created=\"%D %T\">", "</log>")
}

Socket

socklog.go

// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>.  All rights reserved.

package log4go

import (
    "encoding/json"
    "fmt"
    "net"
    "os"
)

// This log writer sends output to a socket
type SocketLogWriter chan *LogRecord

// This is the SocketLogWriter's output method
func (w SocketLogWriter) LogWrite(rec *LogRecord) {
    w <- rec
}

func (w SocketLogWriter) Close() {
    close(w)
}

func NewSocketLogWriter(proto, hostport string) SocketLogWriter {
    sock, err := net.Dial(proto, hostport)
    if err != nil {
        fmt.Fprintf(os.Stderr, "NewSocketLogWriter(%q): %s\n", hostport, err)
        return nil
    }

    w := SocketLogWriter(make(chan *LogRecord, LogBufferLength))

    go func() {
        defer func() {
            if sock != nil && proto == "tcp" {
                sock.Close()
            }
        }()

        for rec := range w {
            // Marshall into JSON
            js, err := json.Marshal(rec)
            if err != nil {
                fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err)
                return
            }

            _, err = sock.Write(js)
            if err != nil {
                fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err)
                return
            }
        }
    }()

    return w
}

什么格式写 ?

日志的最终形式必然是字符串。这边需要从LogRecord结构到对应字符串的序列化工具

JSON

使用encoding/json库的支持, 直接对LogRecord结构体进行序列化。在写入socket的时候使用的是这种序列化方式。

自定义格式

pattlog.go

// 目前支持的格式
// Known format codes:
// %T - Time (15:04:05 MST) 
// %t - Time (15:04) 
// %D - Date (2006/01/02)
// %d - Date (01/02/06)
// %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)
// %S - Source
// %M - Message  // 目前包含文件名和行号
// Ignores unknown formats
// Recommended: "[%D %T] [%L] (%S) %M"
// 下面的接口按照规定的格式, 将LogRecord结构体序列化为字符串。
func FormatLogRecord(format string, rec *LogRecord) string {
    if rec == nil {
        return "<nil>"
    }
    if len(format) == 0 {
        return ""
    }

    out := bytes.NewBuffer(make([]byte, 0, 64))
    secs := rec.Created.UnixNano() / 1e9

    cache := *formatCache // 这里有个本地的时间缓存。秒级别刷新时间和对应的字符串
    if cache.LastUpdateSeconds != secs {
        month, day, year := rec.Created.Month(), rec.Created.Day(), rec.Created.Year()
        hour, minute, second := rec.Created.Hour(), rec.Created.Minute(), rec.Created.Second()
        zone, _ := rec.Created.Zone()
        updated := &formatCacheType{
            LastUpdateSeconds: secs,
            shortTime:         fmt.Sprintf("%02d:%02d", hour, minute),
            shortDate:         fmt.Sprintf("%02d/%02d/%02d", day, month, year%100),
            longTime:          fmt.Sprintf("%02d:%02d:%02d %s", hour, minute, second, zone),
            longDate:          fmt.Sprintf("%04d/%02d/%02d", year, month, day),
        }
        cache = *updated
        formatCache = updated
    }

    // Split the string into pieces by % signs
    pieces := bytes.Split([]byte(format), []byte{'%'})

    // Iterate over the pieces, replacing known formats
    // 下面按照指定格式拼接最终的字符串
    for i, piece := range pieces {
        if i > 0 && len(piece) > 0 {
            switch piece[0] {
            case 'T':
                out.WriteString(cache.longTime)
            case 't':
                out.WriteString(cache.shortTime)
            case 'D':
                out.WriteString(cache.longDate)
            case 'd':
                out.WriteString(cache.shortDate)
            case 'L':
                out.WriteString(levelStrings[rec.Level])
            case 'S':
                out.WriteString(rec.Source)
            case 's':
                slice := strings.Split(rec.Source, "/")
                out.WriteString(slice[len(slice)-1])
            case 'M':
                out.WriteString(rec.Message)
            }
            if len(piece) > 1 {
                out.Write(piece[1:])
            }
        } else if len(piece) > 0 {
            out.Write(piece)
        }
    }
    out.WriteByte('\n')

    return out.String()
}

XML

基于自定义格式,定义出符合XML规范的输出

filelog.go 这里定义了一种XML的输出格式, 用于写入文件。

    <record level="%L">
        <timestamp>%D %T</timestamp>
        <source>%S</source>
        <message>%M</message>
    </record>`

日志的等级

log4go.go

const (
    FINEST Level = iota
    FINE
    DEBUG
    TRACE
    INFO
    WARNING
    ERROR
    CRITICAL
)

仅仅接收自己关心的日志

// log4go.go

// A Filter represents the log level below which no log records are written to
// the associated LogWriter.
type Filter struct {
    Level Level //关心的等级
    LogWriter   // 对应的Writer
}

// A Logger represents a collection of Filters through which log messages are
// written.
type Logger map[string]*Filter  

// 下面提取关键代码 Log 接口
// Send a log message with manual level, source, and message.
func (log Logger) Log(lvl Level, source, message string) {
    skip := true

    //查看是否有接收的着。 只要有一个即可
    for _, filt := range log {
        if lvl >= filt.Level {
            skip = false
            break
        }
    }
    if skip {
        return
    }
    // 利用上下文组装LogRecord
    // Make the log record
    rec := &LogRecord{
        Level:   lvl,
        Created: time.Now(),
        Source:  source,
        Message: message,
    }
    // 发送给所有希望接收这个日志的Filter
    // Dispatch the logs
    for _, filt := range log {
        if lvl < filt.Level {
            continue
        }
        filt.LogWrite(rec)
    }
}

使用配置文件配置一个Logger

config.go 核心代码解析

package log4go
// xml 对应的结构体
type xmlProperty struct {
    Name  string `xml:"name,attr"`
    Value string `xml:",chardata"`
}
// xml 对应的结构体
type xmlFilter struct {
    Enabled  string        `xml:"enabled,attr"`
    Tag      string        `xml:"tag"`
    Level    string        `xml:"level"`
    Type     string        `xml:"type"`
    Property []xmlProperty `xml:"property"`
}

type xmlLoggerConfig struct {
    Filter []xmlFilter `xml:"filter"`
}
// Load XML configuration; see examples/example.xml for documentation
func (log Logger) LoadConfiguration(filename string) {
    log.Close()
    // 打开,解析文件
    // Open the configuration file
    fd, err := os.Open(filename)
    if err != nil {
        fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not open %q for reading: %s\n", filename, err)
        os.Exit(1)
    }

    contents, err := ioutil.ReadAll(fd)
    if err != nil {
        fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not read %q: %s\n", filename, err)
        os.Exit(1)
    }

    xc := new(xmlLoggerConfig)
    if err := xml.Unmarshal(contents, xc); err != nil {
        fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not parse XML configuration in %q: %s\n", filename, err)
        os.Exit(1)
    }
    // 依次解析每个Filter
    for _, xmlfilt := range xc.Filter {
        var filt LogWriter
        var lvl Level
        bad, good, enabled := false, true, false
        // 检查基本属性是否完整
        // Check required children
        if len(xmlfilt.Enabled) == 0 {
            fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required attribute %s for filter missing in %s\n", "enabled", filename)
            bad = true
        } else {
            enabled = xmlfilt.Enabled != "false"
        }
        if len(xmlfilt.Tag) == 0 {
            fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "tag", filename)
            bad = true
        }
        if len(xmlfilt.Type) == 0 {
            fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "type", filename)
            bad = true
        }
        if len(xmlfilt.Level) == 0 {
            fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "level", filename)
            bad = true
        }
        // 确定关心的最低等级
        switch xmlfilt.Level {
        case "FINEST":
            lvl = FINEST
        case "FINE":
            lvl = FINE
        case "DEBUG":
            lvl = DEBUG
        case "TRACE":
            lvl = TRACE
        case "INFO":
            lvl = INFO
        case "WARNING":
            lvl = WARNING
        case "ERROR":
            lvl = ERROR
        case "CRITICAL":
            lvl = CRITICAL
        default:
            fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter has unknown value in %s: %s\n", "level", filename, xmlfilt.Level)
            bad = true
        }

        // Just so all of the required attributes are errored at the same time if missing
        if bad {
            os.Exit(1)
        }
        // 利用额外属性创建并初始化对应的Filter
        switch xmlfilt.Type {
        case "console":
            filt, good = xmlToConsoleLogWriter(filename, xmlfilt.Property, enabled)
        case "file":
            filt, good = xmlToFileLogWriter(filename, xmlfilt.Property, enabled)
        case "xml":
            filt, good = xmlToXMLLogWriter(filename, xmlfilt.Property, enabled)
        case "socket":
            filt, good = xmlToSocketLogWriter(filename, xmlfilt.Property, enabled)
        default:
            fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not load XML configuration in %s: unknown filter type \"%s\"\n", filename, xmlfilt.Type)
            os.Exit(1)
        }

        // Just so all of the required params are errored at the same time if wrong
        if !good {
            os.Exit(1)
        }

        // If we're disabled (syntax and correctness checks only), don't add to logger
        if !enabled {
            continue
        }
        // 保存Filter
        log[xmlfilt.Tag] = &Filter{lvl, filt}
    }
}

辅助接口

以上的接口已经足够配置出强大灵活的日志系统了。不过很多情景下我其实并不需要过度的定制化。为了能够简化代码,log4go 提供了一系列的傻瓜接口。

wrapper.go

import (
    "errors"
    "fmt"
    "os"
    "strings"
)

var (
    Global Logger //提供一个默认的logger , 所有的操作都是对这个logger操作的 
)

func init() {
    Global = NewDefaultLogger(DEBUG)
}

// Wrapper for (*Logger).LoadConfiguration
func LoadConfiguration(filename string) {
    Global.LoadConfiguration(filename)
}

// Wrapper for (*Logger).AddFilter
func AddFilter(name string, lvl Level, writer LogWriter) {
    Global.AddFilter(name, lvl, writer)
}

// Wrapper for (*Logger).Close (closes and removes all logwriters)
func Close() {
    Global.Close()
}
// 比如 : 
func Crash(args ...interface{}) {
    if len(args) > 0 {
        Global.intLogf(CRITICAL, strings.Repeat(" %v", len(args))[1:], args...)
    }
    panic(args)
}
// 比如 : 
// Compatibility with `log`
func Exit(args ...interface{}) {
    if len(args) > 0 {
        Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...)
    }
    Global.Close() // so that hopefully the messages get logged
    os.Exit(0)
}

// 比如 : 
// Utility for finest log messages (see Debug() for parameter explanation)
// Wrapper for (*Logger).Finest
func Finest(arg0 interface{}, args ...interface{}) {
    const (
        lvl = FINEST
    )
    switch first := arg0.(type) {
    case string:
        // Use the string as a format string
        Global.intLogf(lvl, first, args...)
    case func() string:
        // Log the closure (no other arguments used)
        Global.intLogc(lvl, first)
    default:
        // Build a format string so that it will be similar to Sprint
        Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
    }
}


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:cchd0001

查看原文:log4go 源码剖析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2719 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传