log4go 源碼剖析

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 <[email protected]>.  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 <[email protected]>.  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 <[email protected]>.  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...)
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章