老高在最近的一个项目(GOLANG)中加入了koding/multiconfig。
这个包可以用默认值(struct tag)<文件<环境变量<命令行参数的顺序设置参数,很好很强大,极大地方便了测试和开发。
但是同时他也带来了一个问题,和glog一起工作的时候会报错:
flag provided but not defined: -alsologtostderr
因为项目同时使用了glog包,而命令行参数 -alsologtostderr
的意思是同时将日志打印到标准输出。但是自从使用了multiconfig后,这个命令直接会导致程序停止。
在对multiconfig源码分析之后,老高找到了问题的所在,这还要从FLAG包讲起。
FLAG
Package flag implements command-line flag parsing.
flag包实现了命令行的参数解析,说白了,就是把命令后面跟的参数选项映射为程序里的变量,好让程序判断处理逻辑。当给一个程序传入了未定义的参数或选项时,就会得到类似flag provided but not defined
的错误。
文档中列出了flag包的各种方法,其中比较重要的就是Parse()方法,在绑定好了各种参数后,此方法必须被调用,否则无法捕获参数。
再看看两个核心的结构体
// A FlagSet represents a set of defined flags. The zero value of a FlagSet
// has no name and has ContinueOnError error handling.
type FlagSet struct {
// Usage is the function called when an error occurs while parsing flags.
// The field is a function (not a method) that may be changed to point to
// a custom error handler.
Usage func()
name string
parsed bool
actual map[string]*Flag
formal map[string]*Flag
args []string // arguments after flags
errorHandling ErrorHandling
output io.Writer // nil means stderr; use out() accessor
}
// A Flag represents the state of a flag.
type Flag struct {
Name string // name as it appears on command line
Usage string // help message
Value Value // value as set
DefValue string // default value (as text); for usage message
}
Flag是一个单独的解析实例,而FlagSet是一个flag的集合。
系统默认会创建一个FlagSet,当执行Int(),Bool等方法时,会被保存在这个默认的CommandLine中。
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
分析
了解到这个基本知识后,定位到multiconfig的flag.go的源代码中,发现FlagLoader在接口方法Load中,新建了一个NewFlagSet,并将结合配置填充进去数据,随后方法调用了flagSet.Parse(args)
,就会有找不到配置的报错。
glog在init方法中新建了6个flag
flag.BoolVar(&logging.toStderr, "logtostderr", false, "log to standard error instead of files")
flag.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files")
flag.Var(&logging.verbosity, "v", "log level for V logs")
flag.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr")
flag.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging")
flag.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace")
他们被默认添加进了CommandLine中,但是在multiconfig中只是在自己定义的flagSet中进行了校验,所以导致了这个BUG,修复的方法就是在Parse前将glog设置的flag导入到当前的set中,所有老高在return之前,写下如下代码:
// 其实flag.VisitAll在源码里直接调用了CommandLine的VisitAll方法
flag.VisitAll(func(ff *flag.Flag) {
f.flagSet.Var(ff.Value, ff.Name, ff.Usage)
})
之后还要保证glog在multiconfig之前被载入即可!
还有一个方法
其实你应该想到一个方法,可以在不修改源码的情况下消除此bug,就是在自己的配置中加入glog的六个配置结构即可!不过这种做法移植性不强,不过也算是一个hack啦~