NSQD中的log 设计

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{})

通过接口、自定义日志类型、底层还是调用核心日志模块的函数的模式,实现了日志功能的解耦。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamCatcher

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值