logger.go的源码学习
Dapr Logger package中的logger.go文件的源码学习,定义logger相关`的日志类型、schema、日志级别、接口以及保存全局logger列表。
logger的相关定义
log type
log类型分为 普通 log 和 request 两种:
const (
// LogTypeLog is normal log type
LogTypeLog = "log"
// LogTypeRequest is Request log type
LogTypeRequest = "request"
......
}
log schema
const (
......
// Field names that defines Dapr log schema
logFieldTimeStamp = "time"
logFieldLevel = "level"
logFieldType = "type"
logFieldScope = "scope"
logFieldMessage = "msg"
logFieldInstance = "instance"
logFieldDaprVer = "ver"
logFieldAppID = "app_id"
)
log level
log level 没啥特别,很传统的定义:
const (
// DebugLevel has verbose message
DebugLevel LogLevel = "debug"
// InfoLevel is default log level
InfoLevel LogLevel = "info"
// WarnLevel is for logging messages about possible issues
WarnLevel LogLevel = "warn"
// ErrorLevel is for logging errors
ErrorLevel LogLevel = "error"
// FatalLevel is for logging fatal messages. The system shuts down after logging the message.
FatalLevel LogLevel = "fatal"
// UndefinedLevel is for undefined log level
UndefinedLevel LogLevel = "undefined"
)
注意: FatalLevel 有特别的意义,”The system shuts down after logging the message“. 所以这个不能随便用。
toLogLevel() 方法将字符串转为 LogLevel,大小写不敏感:
// toLogLevel converts to LogLevel
func toLogLevel(level string) LogLevel {
switch strings.ToLower(level) {
case "debug":
return DebugLevel
case "info":
return InfoLevel
case "warn":
return WarnLevel
case "error":
return ErrorLevel
case "fatal":
return FatalLevel
}
// unsupported log level by Dapr
return UndefinedLevel
}
Logger 接口定义
// Logger includes the logging api sets
type Logger interface {
// EnableJSONOutput enables JSON formatted output log
EnableJSONOutput(enabled bool)
// SetAppID sets dapr_id field in log. Default value is empty string
SetAppID(id string)
// SetOutputLevel sets log output level
SetOutputLevel(outputLevel LogLevel)
// WithLogType specify the log_type field in log. Default value is LogTypeLog
WithLogType(logType string) Logger
// Info logs a message at level Info.
Info(args ...interface{})
// Infof logs a message at level Info.
Infof(format string, args ...interface{})
// Debug logs a message at level Debug.
Debug(args ...interface{})
// Debugf logs a message at level Debug.
Debugf(format string, args ...interface{})
// Warn logs a message at level Warn.
Warn(args ...interface{})
// Warnf logs a message at level Warn.
Warnf(format string, args ...interface{})
// Error logs a message at level Error.
Error(args ...interface{})
// Errorf logs a message at level Error.
Errorf(format string, args ...interface{})
// Fatal logs a message at level Fatal then the process will exit with status set to 1.
Fatal(args ...interface{})
// Fatalf logs a message at level Fatal then the process will exit with status set to 1.
Fatalf(format string, args ...interface{})
}
logger的创建和获取
全局 logger 列表
// globalLoggers is the collection of Dapr Logger that is shared globally.
// TODO: User will disable or enable logger on demand.
var globalLoggers = map[string]Logger{} // map保存所有的logger实例
var globalLoggersLock = sync.RWMutex{} // 用读写锁对map进行保护
创建新logger或获取已经保存的logger
logger创建之后会保存在 global loggers 中,这意味着每个 name 的logger只会创建一个实例。
// NewLogger creates new Logger instance.
func NewLogger(name string) Logger {
globalLoggersLock.Lock() // 加写锁
defer globalLoggersLock.Unlock()
logger, ok := globalLoggers[name]
if !ok {
logger = newDaprLogger(name)
globalLoggers[name] = logger
}
return logger
}
newDaprLogger() 方法的细节见 dapr_logger.go。
获取所有已经创建的logger列表
func getLoggers() map[string]Logger {
globalLoggersLock.RLock() // 加读锁
defer globalLoggersLock.RUnlock()
l := map[string]Logger{}
for k, v := range globalLoggers {
l[k] = v
}
return l
}
dapr_logger.go的源码学习
Dapr logger package中的dapr_logger.go文件的源码分析,daprLogger 是实际的日志实现。
daprLogger 结构体定义
daprLogger 结构体,底层实现是 logrus :
// daprLogger is the implemention for logrus
type daprLogger struct {
// name is the name of logger that is published to log as a scope
name string
// loger is the instance of logrus logger
logger *logrus.Entry
}
创建Dapr logger
创建Dapr logger的逻辑:
func newDaprLogger(name string) *daprLogger {
// 底层是 logrus
newLogger := logrus.New()
// 输出到 stdout
newLogger.SetOutput(os.Stdout)
dl := &daprLogger{
name: name,
logger: newLogger.WithFields(logrus.Fields{
logFieldScope: name,
// 默认是普通log类型
logFieldType: LogTypeLog,
}),
}
// 设置是否启用json输出,defaultJSONOutput默认是false
dl.EnableJSONOutput(defaultJSONOutput)
return dl
}
启用json输出
函数名有点小问题,实际是初始化logger,是否enables JSON只是部分逻辑:
// EnableJSONOutput enables JSON formatted output log
func (l *daprLogger) EnableJSONOutput(enabled bool) {
var formatter logrus.Formatter
fieldMap := logrus.FieldMap{
// If time field name is conflicted, logrus adds "fields." prefix.
// So rename to unused field @time to avoid the confliction.
logrus.FieldKeyTime: logFieldTimeStamp,
logrus.FieldKeyLevel: logFieldLevel,
logrus.FieldKeyMsg: logFieldMessage,
}
hostname, _ := os.Hostname()
l.logger.Data = logrus.Fields{
logFieldScope: l.logger.Data[logFieldScope],
logFieldType: LogTypeLog,
logFieldInstance: hostname,
logFieldDaprVer: DaprVersion,
}
if enabled {
formatter = &logrus.JSONFormatter{
TimestampFormat: time.RFC3339Nano,
FieldMap: fieldMap,
}
} else {
formatter = &logrus.TextFormatter{
TimestampFormat: time.RFC3339Nano,
FieldMap: fieldMap,
}
}
l.logger.Logger.SetFormatter(formatter)
}
logger的设置
设置DaprVersion
var DaprVersion string = "unknown"
func (l *daprLogger) EnableJSONOutput(enabled bool) {
l.logger.Data = logrus.Fields{
......
logFieldDaprVer: DaprVersion,
}
}
DaprVersion的值来自于 makefile (dapr/Makefile
):
LOGGER_PACKAGE_NAME := github.com/dapr/kit/logger
DEFAULT_LDFLAGS:=-X $(BASE_PACKAGE_NAME)/pkg/version.gitcommit=$(GIT_COMMIT) \
-X $(BASE_PACKAGE_NAME)/pkg/version.gitversion=$(GIT_VERSION) \
-X $(BASE_PACKAGE_NAME)/pkg/version.version=$(DAPR_VERSION) \
-X $(LOGGER_PACKAGE_NAME).DaprVersion=$(DAPR_VERSION)
设置appid
设置日志的 app_id 字段,默认为空。
// SetAppID sets app_id field in log. Default value is empty string
func (l *daprLogger) SetAppID(id string) {
l.logger = l.logger.WithField(logFieldAppID, id)
}
这个方法在logger被初始化时调用进行设置,见 options.go 方法:
func ApplyOptionsToLoggers(options *Options) error {
......
if options.appID != undefinedAppID {
v.SetAppID(options.appID)
}
}
设置日志级别
// SetOutputLevel sets log output level
func (l *daprLogger) SetOutputLevel(outputLevel LogLevel) {
l.logger.Logger.SetLevel(toLogrusLevel(outputLevel))
}
func toLogrusLevel(lvl LogLevel) logrus.Level {
// ignore error because it will never happens
l, _ := logrus.ParseLevel(string(lvl))
return l
}
这个是在原有的 daprLogger 实例上进行设置,没啥特殊。
设置日志类型
默认是普通 log 类型,如果要设置log类型:
// WithLogType specify the log_type field in log. Default value is LogTypeLog
func (l *daprLogger) WithLogType(logType string) Logger {
// 这里重新构造了一个新的 daprLogger 结构体,然后返回
return &daprLogger{
name: l.name,
logger: l.logger.WithField(logFieldType, logType),
}
}
疑问和TODO:
- 为什么不是直接设置 l.logger,而是构造一个新的结构体,然后返回还是 Logger ?
- 会不会有隐患?前面logger创建之后是存放在global logger map中的,key是简单的 name 而不是 name + logtype,这岂不是无法保存一个 name 两个类型的两个 logger 对象?
logger的实现
所有的写log的方法都简单代理给了 l.logger (*logrus.Entry):
// Info logs a message at level Info.
func (l *daprLogger) Info(args ...interface{}) {
l.logger.Log(logrus.InfoLevel, args...)
}
// Infof logs a message at level Info.
func (l *daprLogger) Infof(format string, args ...interface{}) {
l.logger.Logf(logrus.InfoLevel, format, args...)
}
// Debug logs a message at level Debug.
func (l *daprLogger) Debug(args ...interface{}) {
l.logger.Log(logrus.DebugLevel, args...)
}
// Debugf logs a message at level Debug.
func (l *daprLogger) Debugf(format string, args ...interface{}) {
l.logger.Logf(logrus.DebugLevel, format, args...)
}
// Warn logs a message at level Warn.
func (l *daprLogger) Warn(args ...interface{}) {
l.logger.Log(logrus.WarnLevel, args...)
}
// Warnf logs a message at level Warn.
func (l *daprLogger) Warnf(format string, args ...interface{}) {
l.logger.Logf(logrus.WarnLevel, format, args...)
}
// Error logs a message at level Error.
func (l *daprLogger) Error(args ...interface{}) {
l.logger.Log(logrus.ErrorLevel, args...)
}
// Errorf logs a message at level Error.
func (l *daprLogger) Errorf(format string, args ...interface{}) {
l.logger.Logf(logrus.ErrorLevel, format, args...)
}
// Fatal logs a message at level Fatal then the process will exit with status set to 1.
func (l *daprLogger) Fatal(args ...interface{}) {
l.logger.Fatal(args...)
}
// Fatalf logs a message at level Fatal then the process will exit with status set to 1.
func (l *daprLogger) Fatalf(format string, args ...interface{}) {
l.logger.Fatalf(format, args...)
}
注意 logrus 的 Fatalf() 方法的实现,在输出日志之后会调用ExitFunc(如果没设置则默认是 os.Exit
)
func (entry *Entry) Fatalf(format string, args ...interface{}) {
entry.Logf(FatalLevel, format, args...)
entry.Logger.Exit(1)
}
func (logger *Logger) Exit(code int) {
runHandlers()
if logger.ExitFunc == nil {
logger.ExitFunc = os.Exit
}
logger.ExitFunc(code)
}
这会导致进程退出。因此要慎用。
options.go的源码学习
Dapr logger package中的 options.go 文件的源码学习,设置logger相关的属性,包括从命令行参数中解析标记。
默认属性
const (
defaultJSONOutput = false
defaultOutputLevel = "info"
undefinedAppID = ""
)
Options 结构体定义
Options 结构体,就三个字段:
// Options defines the sets of options for Dapr logging
type Options struct {
// appID is the unique id of Dapr Application
// 默认为空
appID string
// JSONFormatEnabled is the flag to enable JSON formatted log
// 默认为fasle
JSONFormatEnabled bool
// OutputLevel is the level of logging
// 默认为 info
OutputLevel string
}
设值方法
// SetOutputLevel sets the log output level
func (o *Options) SetOutputLevel(outputLevel string) error {
// 疑问:这里检查和赋值存在不一致:如果 outputLevel 中有大写字母
// TODO:改进一下
if toLogLevel(outputLevel) == UndefinedLevel {
return errors.Errorf("undefined Log Output Level: %s", outputLevel)
}
o.OutputLevel = outputLevel
return nil
}
// SetAppID sets Dapr ID
func (o *Options) SetAppID(id string) {
o.appID = id
}
疑问 :为什么字段和设置方法不统一?
- JSONFormatEnabled 是 public 字段,没有Set方法
- OutputLevel 是 public 字段,有 Set 方法,Set 方法做了输入值的检测。
- 问题来了:既然是 public 字段,那么绕开 Set 方法直接赋值岂不是就绕开了输入值检测的逻辑?
- appID 是 private 字段,有 Set 方法,而 Set 方法什么都没有做,只是简单赋值,那么为什么不直接用 public 字段呢?
检查发现:
- SetOutputLevel 在dapr/dapr 项目中没有任何人调用
默认构造
返回每个字段的默认值,没啥特殊:
// DefaultOptions returns default values of Options
func DefaultOptions() Options {
return Options{
JSONFormatEnabled: defaultJSONOutput,
appID: undefinedAppID,
OutputLevel: defaultOutputLevel,
}
}
备注:go 不像 java 可以在字段定义时直接赋值一个默认值,有时还真不方便。
从命令行标记中读取日志属性
在命令行参数中读取 log-level
和 log-as-json
两个标记并设置 OutputLevel 和 JSONFormatEnabled:
// AttachCmdFlags attaches log options to command flags
func (o *Options) AttachCmdFlags(
stringVar func(p *string, name string, value string, usage string),
boolVar func(p *bool, name string, value bool, usage string)) {
if stringVar != nil {
stringVar(
&o.OutputLevel,
"log-level",
defaultOutputLevel,
"Options are debug, info, warn, error, or fatal (default info)")
}
if boolVar != nil {
boolVar(
&o.JSONFormatEnabled,
"log-as-json",
defaultJSONOutput,
"print log as JSON (default false)")
}
}
备注:这大概就是 OutputLevel 和 JSONFormatEnabled 两个字段是 public 的原因?
这个方法会在每个二进制文件(runtime(也就是daprd) / injector / operator / placement / sentry) 的初始化代码中调用:
loggerOptions := logger.DefaultOptions()
loggerOptions.AttachCmdFlags(flag.StringVar, flag.BoolVar)
注意:这个时候 OutputLevel 的值是没有经过检查而直接设值的,绕开了 SetOutputLevel 方法的检查。
将属性应用到所有的logger
// ApplyOptionsToLoggers applys options to all registered loggers
func ApplyOptionsToLoggers(options *Options) error {
// 所有的 logger 指的是保存在全局 logger map 中所有 logger
internalLoggers := getLoggers()
// Apply formatting options first
for _, v := range internalLoggers {
v.EnableJSONOutput(options.JSONFormatEnabled)
if options.appID != undefinedAppID {
v.SetAppID(options.appID)
}
}
daprLogLevel := toLogLevel(options.OutputLevel)
if daprLogLevel == UndefinedLevel {
// 在这里做了 OutputLevel 值的有效性检查
return errors.Errorf("invalid value for --log-level: %s", options.OutputLevel)
}
for _, v := range internalLoggers {
v.SetOutputLevel(daprLogLevel)
}
return nil
}
TODO:OutputLevel 赋值有效性检查的地方现在发现有两个,其中一个还没有被使用。准备PR修订。
查了一下这个方法的确是在每个二进制文件(runtime(也就是daprd) / injector / operator / placement / sentry) 的初始化代码中调用:
loggerOptions := logger.DefaultOptions()
loggerOptions.AttachCmdFlags(flag.StringVar, flag.BoolVar)
......
// Apply options to all loggers
loggerOptions.SetAppID(*appID)
if err := logger.ApplyOptionsToLoggers(&loggerOptions); err != nil {
return nil, err
}
TODO: ApplyOptionsToLoggers这个方法名最好修改增加“来自命令行的options”语义,否则报错 “invalid value for –log-level“ 就会很奇怪。