最近参加春招,确实挺受打击,平常做项目遇到的问题,学到的知识点没有及时总结,导致在面试的时候无法清晰的描述出来,因此本专栏后续日常更新,总结编程之路的点滴。下面进入正题。Beego Logs 使用先大致了解怎么使用,再进行剖析。 // Test console without color func TestConsoleNoColor(t *testing.T) { log := NewLogger(100) log.SetLogger(“console”, {"color":false}
) bl.Error(“error”) bl.Warning(“warning”) } // NewLogger returns a new BeeLogger. // channelLen means the number of messages in // chan(used where asynchronous is true). // if the buffering chan is full, logger adapters write to file or other way. func NewLogger(channelLens …int64) *BeeLogger { bl := new(BeeLogger) bl.level = LevelDebug bl.loggerFuncCallDepth = 2 bl.msgChanLen = append(channelLens, 0)[0] if bl.msgChanLen <= 0 { bl.msgChanLen = defaultAsyncMsgLen } bl.signalChan = make(chan string, 1) bl.setLogger(AdapterConsole) return bl }上面有一句代码:bl.msgChanLen = append(channelLens, 0)[0]往 channelLens 切片添加一个值为零的元素后再取头个元素,这个技巧有以下好处:Go 不支持可选参数,但 Go 支持可变参数,这样做变相达到了可选参数的效果。如果 chanelLens 原来为空的话也能拿出一个值为零的元素出来,不用再去判断参数是否为空数组。loggerFuncCallDepth 的值应设为多少这个变量表示函数调用的栈深度,用于记录日志时同时打印出当时执行语句的位置,包括文件名和行号。虽然 NewLogger 方法里面默认将 loggerFuncCallDepth 置为2,但是如果你单独使用logs包时应根据情况设置不同值。举个栗子:···bl.Error(“error”) // ———-a 语句···// Error Log ERROR level message.func (bl *BeeLogger) Error(format string, v …interface{}) { if LevelError > bl.level { return } bl.writeMsg(LevelError, format, v…) // ———-b 语句}func (bl *BeeLogger) writeMsg(logLevel int, msg string, v …interface{}) error { ··· if bl.enableFuncCallDepth { _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) // ———-c 语句 ··· } ···}func Caller(skip int) (pc uintptr, file string, line int, ok bool) { ···}关于 Caller 方法的 skip 参数:The argument skip is the number of stack frames to ascend, with 0 identifying the caller of Caller. (For historical reasons the meaning of skip differs between Caller and Callers.)即,skip 为零的时候,表示 Caller 方法本身,而我们需要的是 a 语句的所在的行号和文件名,所以这种情境下需要提升 2 个栈帧数。工厂方法模式自定义日志输出引擎以下是添加 console 输出引擎的用法,直接调用 SetLogger 方法即可。func TestConsole(t *testing.T) { ··· log.SetLogger(“console”, {"color":false}
) ···}type newLoggerFunc func() Loggervar adapters = make(map[string]newLoggerFunc)func (bl *BeeLogger) SetLogger(adapterName string, configs …string) error { ··· return bl.setLogger(adapterName, configs…)}func (bl *BeeLogger) setLogger(adapterName string, configs …string) error { ··· log, ok := adapters[adapterName] if !ok { return fmt.Errorf(“logs: unknown adaptername %q (forgotten Register?)”, adapterName) } lg := log() //——— c 语句 err := lg.Init(config) if err != nil { fmt.Fprintln(os.Stderr, “logs.BeeLogger.SetLogger: “+err.Error()) return err } bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg}) return nil}func Register(name string, log newLoggerFunc) { ··· adapters[name] = log}在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。上面 c 语句可以看到,具体需要用到什么输出引擎,BeeLogger 不负责它们的创建,而是由这些输出引擎自己去做。从 adapters 这个 map 结构里找到该输出引擎的构造方法, 并且执行这个构造方法。例如 file.go 里面定义了如何构造一个文件输出引擎,并通过 init 方法注册:func init() { Register(AdapterFile, newFileWriter)}// newFileWriter create a FileLogWriter returning as LoggerInterface.func newFileWriter() Logger { w := &fileLogWriter{ Daily: true, MaxDays: 7, Rotate: true, RotatePerm: “0440”, Level: LevelTrace, Perm: “0660”, } return w}为什么要用到互斥锁?直接找到以下四处代码段:func (bl *BeeLogger) Async(msgLen …int64) *BeeLogger { bl.lock.Lock() defer bl.lock.Unlock() ···}func (bl *BeeLogger) SetLogger(adapterName string, configs …string) error { bl.lock.Lock() defer bl.lock.Unlock() ···}func (bl *BeeLogger) DelLogger(adapterName string) error { bl.lock.Lock() defer bl.lock.Unlock() ···}func (bl *BeeLogger) writeMsg(logLevel int, msg string, v …interface{}) error { if !bl.init { bl.lock.Lock() bl.setLogger(AdapterConsole) bl.lock.Unlock() } ···}可以看出,在进行 SetLogger 、 DelLogger 这些操作时涉及到临界资源 bl *BeeLogger 相关配置字段的更改,必须操作前加锁保证并发安全。临界资源是指每次仅允许一个进程访问的资源。Asynchronous 选项为什么能提升性能func (bl *BeeLogger) writeMsg(logLevel int, msg string, v …interface{}) error { ··· if bl.asynchronous { lm := logMsgPool.Get().(*logMsg) lm.level = logLevel lm.msg = msg lm.when = when bl.msgChan <- lm } else { bl.writeToLoggers(when, msg, logLevel) } return nil}如果开启 asynchronous 选项,将日志信息写进 msgChan 就完事了,可以继续执行其他的逻辑代码,除非 msgChan 缓存满了,否则不会发生阻塞,同时,还开启一个 goroutine 监听 msgChan,一旦 msgChan 不为空,将日志信息输出:func (bl *BeeLogger) Async(msgLen …int64) *BeeLogger { ··· go bl.startLogger() ···}// start logger chan reading.// when chan is not empty, write logs.func (bl *BeeLogger) startLogger() { gameOver := false for { select { case bm := <-bl.msgChan: bl.writeToLoggers(bm.when, bm.msg, bm.level) logMsgPool.Put(bm) case sg := <-bl.signalChan: // Now should only send “flush” or “close” to bl.signalChan bl.flush() if sg == “close” { for _, l := range bl.outputs { l.Destroy() } bl.outputs = nil gameOver = true } bl.wg.Done() } if gameOver { break } }}从 logs package 外的 log.go 文件了解 beego 如何解耦在 logs 包(package)外面还有一个 beego package 下的 log.go 文件,截取一段代码: // github.com/astaxie/beego/log.go package beego import “github.com/astaxie/beego/logs” // BeeLogger references the used application logger. var BeeLogger = logs.GetBeeLogger() // SetLevel sets the global log level used by the simple logger. func SetLevel(l int) { logs.SetLevel(l) } // github.com/astaxie/beego/logs/log.go// beeLogger references the used application logger.var beeLogger = NewLogger()// GetBeeLogger returns the default BeeLoggerfunc GetBeeLogger() *BeeLogger { return beeLogger}// SetLevel sets the global log level used by the simple logger.func SetLevel(l int) { beeLogger.SetLevel(l)}beego 为什么还在外面包了一层调用 logs 包里面的方法呢?其实 beego 本身是一个 Web 框架,那么本质就是一个服务端程序,服务端程序需要一个日志记录器来记录服务器的运行状况,那么调用 logs 包的代码以及其他一些配置、初始化的逻辑,就在 log.go 中处理。这里其实也没有什么,就是一开始笔者在读源码的时候老是被这里疑惑,认为多此一举。其实要实现一个功能单一的 logs 包并与其他模块解耦,这么做的确不错。再如, beego 的 session 模块,为了不与 logs 模块耦合,所以 session 模块也造了一个仅供自己模块内使用的日志记录器 SessionLog 。代码如下:// Log implement the log.Loggertype Log struct { *log.Logger}// NewSessionLog set io.Writer to create a Logger for session.func NewSessionLog(out io.Writer) *Log { sl := new(Log) sl.Logger = log.New(out, “[SESSION]”, 1e9) return sl}不妨看看 Beego 官方的架构图:beego 是基于八大独立的模块构建的,是一个高度解耦的框架。用户即使不使用 beego 的 HTTP 逻辑,也依旧可以使用这些独立模块,例如:你可以使用 cache 模块来做你的缓存逻辑;使用日志模块来记录你的操作信息;使用 config 模块来解析你各种格式的文件。所以 beego 不仅可以用于 HTTP 类的应用开发,在你的 socket 游戏开发中也是很有用的模块,这也是 beego 为什么受欢迎的一个原因。大家如果玩过乐高的话,应该知道很多高级的东西都是一块一块的积木搭建出来的,而设计 beego 的时候,这些模块就是积木,高级机器人就是 beego。