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...)
}
}
有疑问加站长微信联系(非本文作者)