diff --git a/mgr/runner.go b/mgr/runner.go index 2cb40849f..8fbf023be 100644 --- a/mgr/runner.go +++ b/mgr/runner.go @@ -20,6 +20,7 @@ import ( "github.com/qiniu/logkit/conf" "github.com/qiniu/logkit/parser" _ "github.com/qiniu/logkit/parser/builtin" + "github.com/qiniu/logkit/parser/qiniu" "github.com/qiniu/logkit/reader" _ "github.com/qiniu/logkit/reader/builtin" "github.com/qiniu/logkit/reader/cloudtrail" @@ -89,7 +90,6 @@ type LogExportRunner struct { } const defaultSendIntervalSeconds = 60 -const qiniulogHeadPatthern = "[1-9]\\d{3}/[0-1]\\d/[0-3]\\d [0-2]\\d:[0-6]\\d:[0-6]\\d(\\.\\d{6})?" // NewRunner 创建Runner func NewRunner(rc RunnerConfig, cleanChan chan<- cleaner.CleanSignal) (runner Runner, err error) { @@ -1043,13 +1043,13 @@ func Compatible(rc RunnerConfig) RunnerConfig { } pattern, _ := rc.ReaderConfig.GetStringOr(reader.KeyHeadPattern, "") if parserType == parser.TypeLogv1 && pattern == "" { - prefix, _ := rc.ParserConf.GetStringOr(parser.KeyQiniulogPrefix, "") + prefix, _ := rc.ParserConf.GetStringOr(qiniu.KeyPrefix, "") prefix = strings.TrimSpace(prefix) var readpattern string if len(prefix) > 0 { - readpattern = "^" + prefix + " " + qiniulogHeadPatthern + readpattern = "^" + prefix + " " + qiniu.HeadPatthern } else { - readpattern = "^" + qiniulogHeadPatthern + readpattern = "^" + qiniu.HeadPatthern } rc.ReaderConfig[reader.KeyHeadPattern] = readpattern } diff --git a/mgr/runner_test.go b/mgr/runner_test.go index 92f93199e..b616fbba8 100644 --- a/mgr/runner_test.go +++ b/mgr/runner_test.go @@ -19,6 +19,7 @@ import ( "github.com/qiniu/logkit/cleaner" "github.com/qiniu/logkit/conf" "github.com/qiniu/logkit/parser" + "github.com/qiniu/logkit/parser/qiniu" "github.com/qiniu/logkit/reader" "github.com/qiniu/logkit/router" "github.com/qiniu/logkit/sender" @@ -287,7 +288,7 @@ func Test_RunForEnvTag(t *testing.T) { t.Error(err) } senderConfigs := []conf.MapConf{ - conf.MapConf{ + { "name": "mock_sender", "sender_type": "mock", }, @@ -517,7 +518,7 @@ func Test_Compatible(t *testing.T) { "mode": "dir", "read_from": "oldest", "datasource_tag": "testtag", - "head_pattern": "^" + qiniulogHeadPatthern, + "head_pattern": "^" + qiniu.HeadPatthern, }, ParserConf: conf.MapConf{ "type": "qiniulog", @@ -545,7 +546,7 @@ func Test_Compatible(t *testing.T) { "mode": "dir", "read_from": "oldest", "datasource_tag": "testtag", - "head_pattern": "^PREX " + qiniulogHeadPatthern, + "head_pattern": "^PREX " + qiniu.HeadPatthern, }, ParserConf: conf.MapConf{ "type": "qiniulog", @@ -593,9 +594,11 @@ func Test_QiniulogRun(t *testing.T) { 2016/10/20 17:20:30.642662 [123][WARN] disk.go:241: github.com/qiniu/logkit/queue/disk.go 1 ` log3 := `2016/10/20 17:20:30.642662 [124][WARN] disk.go:456: xxxxxx` - expfiles := []string{`[REQ_END] 200 0.010k 3.792ms \t\t[WARN][SLdoIrCDZj7pmZsU] disk.go pop() failed: not found`, + expfiles := []string{`[REQ_END] 200 0.010k 3.792ms + [WARN][SLdoIrCDZj7pmZsU] disk.go pop() failed: not found`, `Service: POST 10.200.20.25:9100/user/info, Code: 200, Xlog: AC, Time: 1ms`, - `github.com/qiniu/logkit/queue/disk.go:241 \t1234 3243xsaxs`, `github.com/qiniu/logkit/queue/disk.go 1`, + `github.com/qiniu/logkit/queue/disk.go:241 + 1234 3243xsaxs`, `github.com/qiniu/logkit/queue/disk.go 1`, `xxxxxx`} expreqid := []string{"X-ZsU", "2pyKMukqvwSd-ZsU", "", "123", "124"} if err := ioutil.WriteFile(filepath.Join(logpath, "log1"), []byte(log1), 0666); err != nil { @@ -697,7 +700,11 @@ func Test_QiniulogRun(t *testing.T) { } for idx, dt := range dts { assert.Equal(t, expfiles[idx], dt["log"], "equl log test") - assert.Equal(t, expreqid[idx], dt["reqid"], "equal reqid test") + if expreqid[idx] == "" { + assert.Nil(t, dt["reqid"]) + } else { + assert.Equal(t, expreqid[idx], dt["reqid"], "equal reqid test") + } } ls, err := runner.LagStats() assert.NoError(t, err) diff --git a/parser/mysql/mysql.go b/parser/mysql/mysql.go index b401b4eef..da7afbcfb 100644 --- a/parser/mysql/mysql.go +++ b/parser/mysql/mysql.go @@ -1,24 +1,16 @@ package mysql import ( - "regexp" "strings" "github.com/Preetam/mysqllog" "github.com/qiniu/logkit/conf" "github.com/qiniu/logkit/parser" - "github.com/qiniu/logkit/parser/qiniu" . "github.com/qiniu/logkit/utils/models" ) func init() { - qiniu.CompliedPatterns = make(map[string]*regexp.Regexp) - for k, v := range qiniu.HeaderPattern { - c, _ := regexp.Compile(v) - qiniu.CompliedPatterns[k] = c - } - parser.RegisterConstructor(parser.TypeMySQL, NewParser) } diff --git a/parser/qiniu/qiniu.go b/parser/qiniu/qiniu.go index 6b8466312..93e2334db 100644 --- a/parser/qiniu/qiniu.go +++ b/parser/qiniu/qiniu.go @@ -11,70 +11,62 @@ import ( . "github.com/qiniu/logkit/utils/models" ) +/* +理论上这个parser可以解析 七牛的 xlog,log,teapot这三类日志 +*/ + const ( LogHeadPrefix = "prefix" LogHeadDate = "date" LogHeadTime = "time" - LogHeadLevel = "level" LogHeadReqid string = "reqid" + LogHeadLevel = "level" + LogHeadModule = "module" LogHeadFile = "file" LogHeadLog = "log" //默认在最后,不能改变顺序 + + LogCombinedReqidLevel = "combinedReqidLevel" //reqid和level组合,代表reqid可能有、可能没有,但是如果都有,前者被认定为一定reqid + LogCombinedModuleFile = "combinedModuleFile" //module和file的组合,代表可能有module,可能没有,如果存在中括号开头就认为是module +) + +const ( + LogFilePattern = ":\\d+:$" + + HeadPatthern = `[1-9]\d{3}\/[0-1]\d\/[0-3]\d [0-2]\d:[0-6]\d:[0-6]\d(\.\d{6})? \[` + KeyPrefix = "qiniulog_prefix" //qiniulog的日志前缀 ) var ( - defaultLogHeads = []string{LogHeadPrefix, LogHeadDate, LogHeadTime, LogHeadReqid, LogHeadLevel, LogHeadFile} - HeaderPattern = map[string]string{ - LogHeadDate: "^[1-9]\\d{3}/[0-1]\\d/[0-3]\\d$", - LogHeadTime: "^[0-2]\\d:[0-6]\\d:[0-6]\\d(\\.\\d{6})?$", - LogHeadReqid: "^\\[\\w+\\]\\[\\w+\\]$", - LogHeadLevel: "^\\[[A-Z]+\\]$", - LogHeadFile: ":\\d+:$", - } - CompliedPatterns map[string]*regexp.Regexp + defaultLogHeads = []string{LogHeadDate, LogHeadTime, LogCombinedReqidLevel, LogCombinedModuleFile} + logFilePattern = regexp.MustCompile(LogFilePattern) ) func init() { - CompliedPatterns = make(map[string]*regexp.Regexp) - for k, v := range HeaderPattern { - c, _ := regexp.Compile(v) - CompliedPatterns[k] = c - } - parser.RegisterConstructor(parser.TypeLogv1, NewParser) } type Parser struct { name string - prefix string headers []string labels []parser.Label disableRecordErrData bool } -func getAllLogv1Heads() map[string]bool { - return map[string]bool{ - LogHeadDate: true, - LogHeadTime: true, - LogHeadReqid: true, - LogHeadLevel: true, - LogHeadFile: true, - } -} - -func isExist(source []string, item string) bool { - for _, v := range source { - if v == string(item) { - return true - } - } - return false -} - func NewParser(c conf.MapConf) (parser.Parser, error) { name, _ := c.GetStringOr(parser.KeyParserName, "") - prefix, _ := c.GetStringOr(parser.KeyQiniulogPrefix, "") labelList, _ := c.GetStringListOr(parser.KeyLabels, []string{}) logHeaders, _ := c.GetStringListOr(parser.KeyLogHeaders, defaultLogHeads) + if len(logHeaders) < 1 { + return nil, fmt.Errorf("no log headers was configured to parse") + } + + //兼容老的配置,以前的配置必须要配 KeyPrefix 才能匹配 prefix + prefix, _ := c.GetStringOr(KeyPrefix, "") + if len(prefix) > 0 { + if logHeaders[0] != LogHeadPrefix { + logHeaders = append([]string{LogHeadPrefix}, logHeaders...) + } + } nameMap := make(map[string]struct{}) for k, _ := range logHeaders { @@ -87,7 +79,6 @@ func NewParser(c conf.MapConf) (parser.Parser, error) { return &Parser{ name: name, labels: labels, - prefix: prefix, headers: logHeaders, disableRecordErrData: disableRecordErrData, }, nil @@ -101,7 +92,7 @@ func (p *Parser) Type() string { return parser.TypeLogv1 } -func (p *Parser) GetParser(head string) (func(string) (string, string, error), error) { +func (p *Parser) GetParser(head string) (func(string) (string, map[string]string, error), error) { switch head { case LogHeadPrefix: return p.parsePrefix, nil @@ -113,8 +104,16 @@ func (p *Parser) GetParser(head string) (func(string) (string, string, error), e return p.parseReqid, nil case LogHeadLevel: return p.parseLogLevel, nil + case LogHeadModule: + return p.parseModule, nil case LogHeadFile: - return p.parseLogFile, nil + //以前的file,就是组合式的解析 + return p.parseCombinedModuleFile, nil + case LogCombinedReqidLevel: + return p.parseCombinedReqidLevel, nil + case LogCombinedModuleFile: + return p.parseCombinedModuleFile, nil + } return nil, fmt.Errorf("QiniulogParser Loghead <%v> not exist", head) } @@ -127,30 +126,32 @@ func getSplitByFirstSpace(line string) (firstPart, left string) { return line[0:space], line[space+1:] } -func (p *Parser) parsePrefix(line string) (leftline, prefix string, err error) { - if !strings.HasPrefix(line, p.prefix) { - err = fmt.Errorf("%v can not find prefix %v", line, p.prefix) - return - } - return strings.TrimPrefix(line, p.prefix), p.prefix, nil +func (p *Parser) parsePrefix(line string) (string, map[string]string, error) { + prefix, leftline := getSplitByFirstSpace(line) + result := map[string]string{LogHeadPrefix: prefix} + return leftline, result, nil } -func (p *Parser) parseDate(line string) (leftline, date string, err error) { - date, leftline = getSplitByFirstSpace(line) - return + +func (p *Parser) parseDate(line string) (string, map[string]string, error) { + date, leftline := getSplitByFirstSpace(line) + result := map[string]string{LogHeadDate: date} + return leftline, result, nil } -func (p *Parser) parseTime(line string) (leftline, time string, err error) { - time, leftline = getSplitByFirstSpace(line) - return + +func (p *Parser) parseTime(line string) (string, map[string]string, error) { + time, leftline := getSplitByFirstSpace(line) + result := map[string]string{LogHeadTime: time} + return leftline, result, nil } func parseFromBracket(line, leftBracket, rightBracket string) (leftline, thing string, err error) { if !strings.HasPrefix(line, leftBracket) { - err = fmt.Errorf("can not find bracket %v", leftBracket) + err = fmt.Errorf("can not find left bracket %v %s", leftBracket, line) return } index := strings.Index(line, rightBracket) if index < 0 { - err = fmt.Errorf("can not find bracket %v", leftBracket) + err = fmt.Errorf("can not find right bracket %v from log %s", leftBracket, line) return } thing = line[len(leftBracket):index] @@ -162,54 +163,112 @@ func parseFromBracket(line, leftBracket, rightBracket string) (leftline, thing s return } -func (p *Parser) parseReqid(line string) (leftline, reqid string, err error) { - req, _ := getSplitByFirstSpace(line) - if strings.Count(req, "[") < 2 || strings.Count(req, "]") < 2 { - return line, "", nil +func (p *Parser) parseReqid(line string) (string, map[string]string, error) { + + //reqid可以不存在 + if !strings.HasPrefix(line, "[") { + return line, nil, nil } - leftline, reqid, err = parseFromBracket(line, "[", "]") + leftline, reqid, err := parseFromBracket(line, "[", "]") if err != nil { err = errorCanNotParse(LogHeadReqid, line, err) - return + return line, nil, err } - return + //中括号里面可能不是reqid,可能是file,兼容teapot + if strings.Contains(reqid, `"`) { + return line, nil, nil + } + result := map[string]string{LogHeadReqid: reqid} + return leftline, result, nil } -func (p *Parser) parseLogLevel(line string) (leftline, loglevel string, err error) { - leftline, loglevel, err = parseFromBracket(line, "[", "]") +func (p *Parser) parseCombinedReqidLevel(line string) (string, map[string]string, error) { + leftline, firstRes, err := parseFromBracket(line, "[", "]") if err != nil { - err = errorCanNotParse(LogHeadLevel, line, err) - return + err = errorCanNotParse(LogCombinedReqidLevel, line, err) + return line, nil, err } - return -} -func (p *Parser) parseLogFile(line string) (leftline, logFile string, err error) { - logFile, leftline = getSplitByFirstSpace(line) - if strings.HasPrefix(logFile, "[") { - leftline, logFile, err = parseFromBracket(line, "[", "]") + result := make(map[string]string) + var secondRes string + if strings.HasPrefix(leftline, "[") { + leftline, secondRes, err = parseFromBracket(leftline, "[", "]") if err != nil { - err = errorCanNotParse(LogHeadFile, line, err) - return + err = errorCanNotParse(LogCombinedReqidLevel, leftline, err) + return line, nil, err } - return + result[LogHeadReqid] = firstRes + result[LogHeadLevel] = secondRes + } else { + result[LogHeadLevel] = firstRes } - match := isMatch(CompliedPatterns[LogHeadFile], logFile) + return leftline, result, nil +} + +func (p *Parser) parseLogLevel(line string) (string, map[string]string, error) { + leftline, loglevel, err := parseFromBracket(line, "[", "]") + if err != nil { + err = errorCanNotParse(LogHeadLevel, line, err) + return line, nil, err + } + result := map[string]string{LogHeadLevel: loglevel} + return leftline, result, nil +} + +func (p *Parser) parseModule(line string) (string, map[string]string, error) { + if !strings.HasPrefix(line, "[") { + return line, nil, nil + } + leftline, module, err := parseFromBracket(line, "[", "]") + if err != nil { + err = errorCanNotParse(LogHeadModule, line, err) + return line, nil, err + } + result := map[string]string{LogHeadModule: module} + return leftline, result, err +} + +func (p *Parser) parseLogFile(line string) (string, map[string]string, error) { + logFile, leftline := getSplitByFirstSpace(line) + match := isMatch(logFilePattern, logFile) if match { - return + result := map[string]string{LogHeadFile: logFile} + return leftline, result, nil } if len(leftline) < 1 { - err = errorCanNotParse(LogHeadFile, line, fmt.Errorf("no left to parse")) - return + err := errorCanNotParse(LogHeadFile, line, fmt.Errorf("no left to parse")) + return line, nil, err } leftline = strings.TrimSpace(leftline) nextfile, leftline := getSplitByFirstSpace(leftline) - match = isMatch(CompliedPatterns[LogHeadFile], nextfile) + match = isMatch(logFilePattern, nextfile) if !match { - err = errorCanNotParse(LogHeadFile, nextfile, fmt.Errorf("pattern %v not match %v", HeaderPattern[LogHeadFile], nextfile)) - return + err := errorCanNotParse(LogHeadFile, nextfile, fmt.Errorf("pattern <%v> not match %v", LogFilePattern, nextfile)) + return line, nil, err } logFile += " " + nextfile - return + result := map[string]string{LogHeadFile: logFile} + return leftline, result, nil +} + +func (p *Parser) parseCombinedModuleFile(line string) (string, map[string]string, error) { + result := make(map[string]string) + if strings.HasPrefix(line, "[") { + leftLine, moduleResult, err := p.parseModule(line) + if err != nil { + return leftLine, result, err + } + line = strings.TrimSpace(leftLine) + result[LogHeadModule] = moduleResult[LogHeadModule] + } + leftLine, logRes, err := p.parseLogFile(line) + if err != nil { + if len(result) > 0 { + return leftLine, result, nil + } + return leftLine, result, err + } + result[LogHeadFile] = logRes[LogHeadFile] + return leftLine, result, nil } func isMatch(pattern *regexp.Regexp, raw string) bool { @@ -219,20 +278,20 @@ func isMatch(pattern *regexp.Regexp, raw string) bool { func errorNothingParse(s string, d string) error { return fmt.Errorf("there is no left log to parse %v, have parsed %v", s, d) } + func errorCanNotParse(s string, line string, err error) error { return fmt.Errorf("can not parse %v from %v %v", s, line, err) } func (p *Parser) parse(line string) (d Data, err error) { d = make(Data, len(p.headers)+len(p.labels)) - line = strings.Replace(line, "\n", " ", -1) - line = strings.Replace(line, "\t", "\\t", -1) - var result, logdate string + // 不明白为什么之前要把换行和\t干掉,现在注释 + //line = strings.Replace(line, "\n", " ", -1) + //line = strings.Replace(line, "\t", "\\t", -1) + var logdate string + var result map[string]string for _, head := range p.headers { line = strings.TrimSpace(line) - if head == LogHeadPrefix && len(p.prefix) < 1 { - continue - } if len(line) < 1 { return nil, errorNothingParse(head, line) } @@ -249,16 +308,19 @@ func (p *Parser) parse(line string) (d Data, err error) { return nil, err } if head == LogHeadDate { - logdate = result + logdate = result[LogHeadDate] continue } if head == LogHeadTime { + logTime := result[LogHeadTime] if len(logdate) > 0 { _, zoneValue := times.GetTimeZone() - result = logdate + " " + result + zoneValue + result[LogHeadTime] = logdate + " " + logTime + zoneValue } } - d[string(head)] = result + for k, v := range result { + d[k] = v + } } line = strings.TrimSpace(line) d[LogHeadLog] = line @@ -267,6 +329,7 @@ func (p *Parser) parse(line string) (d Data, err error) { } return d, nil } + func (p *Parser) Parse(lines []string) ([]Data, error) { datas := []Data{} se := &StatsError{} diff --git a/parser/qiniu/qiniu_test.go b/parser/qiniu/qiniu_test.go index da20aa0f9..1485a58f3 100644 --- a/parser/qiniu/qiniu_test.go +++ b/parser/qiniu/qiniu_test.go @@ -2,7 +2,6 @@ package qiniu import ( "regexp" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -23,6 +22,10 @@ func Test_QiniuLogRegex(t *testing.T) { line: "2017/03/28 15:41:06 [Wm0AAPg-IUMW-68U][INFO] bdc.go:573: deleted: 67608", exp: true, }, + { + line: "2018/08/17 10:49:29 [INFO][github.com/qiniu/logkit/reader] seqfile.go:538: Runner[UndefinedRunnerName] ", + exp: true, + }, { line: "2016/10/20 17:30:21.433423 [GE2owHck-Y4IWJHS][WARN] github.com/qiniu/http/rpcutil.v1/rpc_util.go:203: ==> qiniu.com/streaming.v2/apiserver.go:1367: E18102: The specified repo does not exist under the provided appid ~", exp: true, @@ -52,7 +55,8 @@ func Test_QiniuLogRegex(t *testing.T) { exp: false, }, } - mp := "[1-9]\\d{3}/[0-1]\\d/[0-3]\\d [0-2]\\d:[0-6]\\d:[0-6]\\d(\\.\\d{6})?" + mp := `[1-9]\d{3}\/[0-1]\d\/[0-3]\d [0-2]\d:[0-6]\d:[0-6]\d(\.\d{6})? \[` + //mp := "[1-9]\\d{3}/[0-1]\\d/[0-3]\\d [0-2]\\d:[0-6]\\d:[0-6]\\d(\\.\\d{6})?" for _, ti := range tests { got, err := regexp.MatchString("^"+mp, ti.line) assert.NoError(t, err) @@ -138,8 +142,8 @@ func Test_QiniulogParser(t *testing.T) { if dts[1]["reqid"] != "GE2owHck-Y4IWJHS" { t.Errorf("parse reqid error exp GE2owHck-Y4IWJHS but %v", dts[1]["reqid"]) } - if dts[2]["reqid"] != "" { - t.Errorf("parse reqid error exp but %v", dts[2]["reqid"]) + if dts[2]["reqid"] != nil { + t.Errorf("parse reqid error exp nil but got %v", dts[2]["reqid"]) } if dts[3]["reqid"] != "GE2owHck-Y4IWJHS" { t.Errorf("parse reqid error exp GE2owHck-Y4IWJHS but %v", dts[4]["reqid"]) @@ -149,10 +153,7 @@ func Test_QiniulogParser(t *testing.T) { if dts[1]["time"] != exp { t.Errorf("parse time error exp %v but %v", exp, dts[1]["time"]) } - rawlog := dts[1]["log"].(string) - if strings.Contains(rawlog, "\n") || strings.Contains(rawlog, "\t") { - t.Error("log should not contain \\n or \\t ") - } + newlines := []string{ "2016/10/20 17:20:30.642666 [ERROR] disk.go github.com/qiniu/logkit/queue/disk.go:241: ", "2016/10/20 17:20:30.642662 [123][WARN] disk.go github.com/qiniu/logkit/queue/disk.go:241: 1", @@ -211,7 +212,7 @@ func Test_QiniulogParserForErrData(t *testing.T) { func Test_QiniulogParserForTeapot(t *testing.T) { c := conf.MapConf{} c[parser.KeyParserType] = "qiniulog" - c[parser.KeyLogHeaders] = "prefix,date,time,level,reqid,file" + c[parser.KeyLogHeaders] = "date,time,level,reqid,file" ps := parser.NewRegistry() p, err := ps.NewLogParser(c) if err != nil { @@ -241,8 +242,8 @@ func Test_QiniulogParserForTeapot(t *testing.T) { t.Errorf("parse reqid error exp 2pyKMgVp5EKg-ZsU but %v", dts[0]["reqid"]) } - if dts[1]["reqid"] != "" { - t.Errorf("parse reqid error exp but %v", dts[1]["reqid"]) + if dts[1]["reqid"] != nil { + t.Errorf("parse reqid error exp nil but got %v", dts[1]["reqid"]) } _, zoneValue := times.GetTimeZone() @@ -272,5 +273,5 @@ func Test_QiniulogParserForTeapot(t *testing.T) { if dts[1]["level"] != "WARN" { t.Errorf("parse level error exp WARN but %v", dts[0]["level"]) } - assert.Equal(t, `"github.com/teapots/request-logger/logger.go:61"`, dts[0]["file"]) + assert.Equal(t, `"github.com/teapots/request-logger/logger.go:61"`, dts[0]["module"]) } diff --git a/parser/rest_parser_models.go b/parser/rest_parser_models.go index fca840e39..7d1533f9f 100644 --- a/parser/rest_parser_models.go +++ b/parser/rest_parser_models.go @@ -36,8 +36,7 @@ const ( // Constants for Qiniu const ( - KeyQiniulogPrefix = "qiniulog_prefix" //qiniulog的日志前缀 - KeyLogHeaders = "qiniulog_log_headers" + KeyLogHeaders = "qiniulog_log_headers" ) // Constants for raw @@ -299,20 +298,13 @@ var ModeKeyOptions = map[string][]Option{ }, TypeLogv1: { OptionLabels, - { - KeyName: KeyQiniulogPrefix, - ChooseOnly: false, - Default: "", - DefaultNoUse: false, - Description: "日志前缀(qiniulog_prefix)", - Advance: true, - }, { KeyName: KeyLogHeaders, ChooseOnly: false, Default: "", DefaultNoUse: false, - Description: "日志格式顺序(qiniulog_log_headers)", + Description: "日志字段与顺序(qiniulog_log_headers)", + ToolTip: "用来定义解析的顺序,默认情况下没有prefix,按 date,time,reqid,level,module,file,log的顺序依次解析,可以调换。还可以写 combinedReqidLevel 表示reqid和level组合,代表reqid可能有、可能没有,但是如果都有,前者被认定为一定reqid;combinedModuleFile表示module和file的组合,代表可能有module,可能没有,如果存在中括号开头就认为是module", Advance: true, }, OptionParserName,