1、标准包的中log
结构体:
// A Logger represents an active logging object that generates lines of
// output to an io.Writer. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {
mu sync.Mutex // ensures atomic writes; protects the following fields
prefix string // prefix on each line to identify the logger (but see Lmsgprefix)
flag int // properties
out io.Writer // destination for output
buf []byte // for accumulating text to write
}
(1) mu:互斥锁,用于确保原子的写入
(2) prefix:每行需写入的日志前缀内容
(3) flag:设置日志辅助信息(时间、文件名、行号)的写入。可选如下标识位:
// These flags define which text to prefix to each log entry generated by the Logger.
// Bits are or'ed together to control what's printed.
// With the exception of the Lmsgprefix flag, there is no
// control over the order they appear (the order listed here)
// or the format they present (as described in the comments).
// The prefix is followed by a colon only when Llongfile or Lshortfile
// is specified.
// For example, flags Ldate | Ltime (or LstdFlags) produce,
// 2009/01/23 01:23:23 message
// while flags Ldate | Ltime | Lmicroseconds | Llongfile produce,
// 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
const (
Ldate = 1 << iota // the date in the local time zone: 2009/01/23
Ltime // the time in the local time zone: 01:23:23
Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.
Llongfile // full file name and line number: /a/b/c/d.go:23
Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone
Lmsgprefix // move the "prefix" from the beginning of the line to before the message
LstdFlags = Ldate | Ltime // initial values for the standard logger
)
- Ldate:本地时区的日期:2009/01/23
- Ltime:本地时区的时间:01:23:23
- Lmicroseconds:在 Ltime 的基础上,增加微秒的时间数值显示
- Llongfile:完整的文件名和行号:/a/b/c/d.go:23
- Lshortfile:最终的文件名和行号:d.go:23,会覆盖 Llongfile 标识
- LUTC:如果设置 Ldate 或 Ltime,且设置 LUTC,则优先使用 UTC 时区而不是本地时区
- Lmsgprefix :将“prefix”从行首移动到消息之前
- LstdFlags:标准Logger 的默认初始值(Ldate 和 Ltime)
创建:New
// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line, or
// after the log header if the Lmsgprefix flag is provided.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
var std = New(os.Stderr, "", LstdFlags)
New 方法用于初始化 Logger,接受三个初始参数,且可以定制化。
在 log 包内默认会初始一个 std,它指向标准错误输出流
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
// standard output, and standard error file descriptors.
//
// Note that the Go runtime writes to standard error for panics and crashes;
// closing Stderr may cause those messages to go elsewhere, perhaps
// to a file opened later.
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
核心方法:
// Output writes the output for a logging event. The string s contains
// the text to print after the prefix specified by the flags of the
// Logger. A newline is appended if the last character of s is not
// already a newline. Calldepth is used to recover the PC and is
// provided for generality, although at the moment on all pre-defined
// paths it will be 2.
func (l *Logger) Output(calldepth int, s string) error {
now := time.Now() // get this early.
var file string
var line int
l.mu.Lock()
defer l.mu.Unlock()
if l.flag&(Lshortfile|Llongfile) != 0 {
// Release lock while getting caller info - it's expensive.
l.mu.Unlock()
var ok bool
_, file, line, ok = runtime.Caller(calldepth)
if !ok {
file = "???"
line = 0
}
l.mu.Lock()
}
l.buf = l.buf[:0]
l.formatHeader(&l.buf, now, file, line)
l.buf = append(l.buf, s...)
if len(s) == 0 || s[len(s)-1] != '\n' {
l.buf = append(l.buf, '\n')
}
_, err := l.out.Write(l.buf)
return err
}
主要是为一个日志事件进行信息组装并输出,最后一个字符不是换行符,则追加一个换行符。
并通过方法
_, file, line, ok = runtime.Caller(calldepth)
去获取当前 goroutine 所执行的函数文件、行号等调用栈信息(log 标准库中默认深度为 2)
2、NSQD中的log设计
(1)对标准库log的使用
代码:apps/nsqd/main.go
// 省略其他代码...
options.Resolve(opts, flagSet, cfg)
//根据配置选项创建nsqd实例
nsqd, err := nsqd.New(opts)
if err != nil {
logFatal("failed to instantiate nsqd - %s", err)
}
p.nsqd = nsqd
// 省略其他代码...
代码:nsqd/nsqd.go
func New(opts *Options) (*NSQD, error) {
// 省略其他代码...
if opts.Logger == nil {
//创建一个标准库的日志实例,并赋值给nsqd中的Logger
opts.Logger = log.New(os.Stderr, opts.LogPrefix, log.Ldate|log.Ltime|log.Lmicroseconds)
}
那如何做到,将标准库中的日志实例 自由地赋值给 自定义的日志类型??
答案是利用接口。
标准库中的Logger是一个结构体,并且该结构体实现了核心方法:
func (l *Logger) Output(calldepth int, s string) error
那么,可以自定义一个Logger接口,该接口只提供一个方法:
Output(maxdepth int, s string) error
这样,标准库中的Logger就自然而然的实现了自定义的日志接口了。
(2)NSQD的log设计
NSQD中的log就是通过上述方法设计的。
在内部核心源码:
代码:internal/lg/lg.go
type Logger interface {
Output(maxdepth int, s string) error
}
type NilLogger struct{}
func (l NilLogger) Output(maxdepth int, s string) error {
return nil
}
在internal/lg/lg.go中:
- 新建了一个Logger 接口,实现了Output(maxdepth int, s string) error方法,
- 提供了类型为Nil的日志结构体:NilLogger 并实现Logger接口
- 提供了LogLevel,实现了标准库中(flag/flag.go)接口:Getter、Value,这样可以作为命令行Flag使用。
//自定义日志级别
type LogLevel int
//实现了Getter接口
func (l *LogLevel) Get() interface{} { return *l }
//实现了Value接口
func (l *LogLevel) Set(s string) error {
lvl, err := ParseLogLevel(s)
if err != nil {
return err
}
*l = lvl
return nil
}
//实现了Value接口
func (l *LogLevel) String() string {
switch *l {
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARN:
return "WARNING"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
}
return "invalid"
}
func ParseLogLevel(levelstr string) (LogLevel, error) {
switch strings.ToLower(levelstr) {
case "debug":
return DEBUG, nil
case "info":
return INFO, nil
case "warn":
return WARN, nil
case "error":
return ERROR, nil
case "fatal":
return FATAL, nil
}
return 0, fmt.Errorf("invalid log level '%s' (debug, info, warn, error, fatal)", levelstr)
}
- 定义了5个日志级别:
const (
DEBUG = LogLevel(1)
INFO = LogLevel(2)
WARN = LogLevel(3)
ERROR = LogLevel(4)
FATAL = LogLevel(5)
)
- 提供了应用日志函数类型
type AppLogFunc func(lvl LogLevel, f string, args ...interface{})
- 提供了根据消息日志级别(msgLevel)打印日志的函数
func Logf(logger Logger, cfgLevel LogLevel, msgLevel LogLevel, f string, args ...interface{}) {
if cfgLevel > msgLevel {
return
}
logger.Output(3, fmt.Sprintf(msgLevel.String()+": "+f, args...))
}
func LogFatal(prefix string, f string, args ...interface{}) {
logger := log.New(os.Stderr, prefix, log.Ldate|log.Ltime|log.Lmicroseconds)
Logf(logger, FATAL, FATAL, f, args...)
os.Exit(1)
}
在各个模块中,也可以自定义日志类型,
比如在nsqd模块中:
源码:nsqd/logger.go
package nsqd
import (
"github.com/nsqio/nsq/internal/lg"
)
type Logger lg.Logger
const (
LOG_DEBUG = lg.DEBUG
LOG_INFO = lg.INFO
LOG_WARN = lg.WARN
LOG_ERROR = lg.ERROR
LOG_FATAL = lg.FATAL
)
func (n *NSQD) logf(level lg.LogLevel, f string, args ...interface{}) {
opts := n.getOpts()
lg.Logf(opts.Logger, opts.LogLevel, level, f, args...)
}
在包nsqd中,自定义了模块nsqd的日志类型Logger和日志级别,并且实现了NSQD的日志方法:
func (n *NSQD) logf(level lg.LogLevel, f string, args ...interface{})
同样,在nsqadmin模块中:
源码:nsqadmin/logger.go
package nsqadmin
import (
"github.com/nsqio/nsq/internal/lg"
)
type Logger lg.Logger
const (
LOG_DEBUG = lg.DEBUG
LOG_INFO = lg.INFO
LOG_WARN = lg.WARN
LOG_ERROR = lg.ERROR
LOG_FATAL = lg.FATAL
)
func (n *NSQAdmin) logf(level lg.LogLevel, f string, args ...interface{}) {
opts := n.getOpts()
lg.Logf(opts.Logger, opts.LogLevel, level, f, args...)
}
也自定义了模块nsqadmin的日志类型Logger和日志级别,并且实现了NSQAdmin的日志方法:
func (n *NSQAdmin) logf(level lg.LogLevel, f string, args ...interface{})
通过接口、自定义日志类型、底层还是调用核心日志模块的函数的模式,实现了日志功能的解耦。
991

被折叠的 条评论
为什么被折叠?



