分享
  1. 首页
  2. 文章

log4go 源码剖析

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

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

关注微信
3179 次点击
1 回复 | 直到 2025年04月28日 18:44:04
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏