From 9ad9292321673fc5f78f3b9fc0c3eae43d50b6e6 Mon Sep 17 00:00:00 2001 From: John Guo Date: Fri, 29 Apr 2022 14:13:54 +0800 Subject: [PATCH] improve handler feature for package glog --- os/glog/glog_logger.go | 42 +++++++------ os/glog/glog_logger_handler.go | 71 ++++++++++++++-------- os/glog/glog_logger_handler_json.go | 47 ++++++++++++++ os/glog/glog_z_unit_logger_handler_test.go | 39 ++++++++++++ 4 files changed, 155 insertions(+), 44 deletions(-) create mode 100644 os/glog/glog_logger_handler_json.go diff --git a/os/glog/glog_logger.go b/os/glog/glog_logger.go index 97acbe557..a1f808685 100644 --- a/os/glog/glog_logger.go +++ b/os/glog/glog_logger.go @@ -96,7 +96,7 @@ func (l *Logger) getFilePath(now time.Time) string { } // print prints `s` to defined writer, logging file or passed `std`. -func (l *Logger) print(ctx context.Context, level int, values ...interface{}) { +func (l *Logger) print(ctx context.Context, level int, stack string, values ...interface{}) { // Lazy initialize for rotation feature. // It uses atomic reading operation to enhance the performance checking. // It here uses CAP for performance and concurrent safety. @@ -115,14 +115,26 @@ func (l *Logger) print(ctx context.Context, level int, values ...interface{}) { var ( now = time.Now() input = &HandlerInput{ - Logger: l, - Buffer: bytes.NewBuffer(nil), - Time: now, - Color: defaultLevelColor[level], - Level: level, - handlerIndex: -1, + internalHandlerInfo: internalHandlerInfo{ + index: -1, + }, + Logger: l, + Buffer: bytes.NewBuffer(nil), + Time: now, + Color: defaultLevelColor[level], + Level: level, + Stack: stack, } ) + + // Logging handlers. + if len(l.config.Handlers) > 0 { + input.handlers = append(input.handlers, l.config.Handlers...) + } else if defaultHandler != nil { + input.handlers = []Handler{defaultHandler} + } + input.handlers = append(input.handlers, defaultPrintHandler) + if l.config.HeaderPrint { // Time. timeFormat := "" @@ -146,7 +158,7 @@ func (l *Logger) print(ctx context.Context, level int, values ...interface{}) { } // Level string. - input.LevelFormat = l.getLevelPrefixWithBrackets(level) + input.LevelFormat = l.GetLevelPrefix(level) // Caller path and Fn name. if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 { @@ -178,7 +190,7 @@ func (l *Logger) print(ctx context.Context, level int, values ...interface{}) { // Tracing values. spanCtx := trace.SpanContextFromContext(ctx) if traceId := spanCtx.TraceID(); traceId.IsValid() { - input.CtxStr = traceId.String() + input.TraceId = traceId.String() } // Context values. if len(l.config.CtxKeys) > 0 { @@ -195,9 +207,6 @@ func (l *Logger) print(ctx context.Context, level int, values ...interface{}) { } } } - if input.CtxStr != "" { - input.CtxStr = "{" + input.CtxStr + "}" - } } var tempStr string for _, v := range values { @@ -336,18 +345,17 @@ func (l *Logger) getFilePointer(ctx context.Context, path string) *gfpool.File { // printStd prints content `s` without stack. func (l *Logger) printStd(ctx context.Context, level int, value ...interface{}) { - l.print(ctx, level, value...) + l.print(ctx, level, "", value...) } // printStd prints content `s` with stack check. func (l *Logger) printErr(ctx context.Context, level int, value ...interface{}) { + var stack string if l.config.StStatus == 1 { - if s := l.GetStack(); s != "" { - value = append(value, "\nStack:\n"+s) - } + stack = l.GetStack() } // In matter of sequence, do not use stderr here, but use the same stdout. - l.print(ctx, level, value...) + l.print(ctx, level, stack, value...) } // format formats `values` using fmt.Sprintf. diff --git a/os/glog/glog_logger_handler.go b/os/glog/glog_logger_handler.go index 26303623c..2b69ec593 100644 --- a/os/glog/glog_logger_handler.go +++ b/os/glog/glog_logger_handler.go @@ -17,24 +17,35 @@ type Handler func(ctx context.Context, in *HandlerInput) // HandlerInput is the input parameter struct for logging Handler. type HandlerInput struct { - Logger *Logger // Logger. - Buffer *bytes.Buffer // Buffer for logging content outputs. - Time time.Time // Logging time, which is the time that logging triggers. - TimeFormat string // Formatted time string, like "2016-01-09 12:00:00". - Color int // Using color, like COLOR_RED, COLOR_BLUE, etc. Eg: 34 - Level int // Using level, like LEVEL_INFO, LEVEL_ERRO, etc. Eg: 256 - LevelFormat string // Formatted level string, like "DEBU", "ERRO", etc. Eg: ERRO - CallerFunc string // The source function name that calls logging. - CallerPath string // The source file path and its line number that calls logging. - CtxStr string // The retrieved context value string from context. Usually a TraceId string. - Prefix string // Custom prefix string for logging content. - Content string // Content is the main logging content, containing error stack string produced by logger. - IsAsync bool // IsAsync marks it is in asynchronous logging. - handlerIndex int // Middleware handling index for internal usage. + internalHandlerInfo + Logger *Logger // Logger. + Buffer *bytes.Buffer // Buffer for logging content outputs. + Time time.Time // Logging time, which is the time that logging triggers. + TimeFormat string // Formatted time string, like "2016-01-09 12:00:00". + Color int // Using color, like COLOR_RED, COLOR_BLUE, etc. Eg: 34 + Level int // Using level, like LEVEL_INFO, LEVEL_ERRO, etc. Eg: 256 + LevelFormat string // Formatted level string, like "DEBU", "ERRO", etc. Eg: ERRO + CallerFunc string // The source function name that calls logging, only available if F_CALLER_FN set. + CallerPath string // The source file path and its line number that calls logging, only available if F_FILE_SHORT or F_FILE_LONG set. + CtxStr string // The retrieved context value string from context, only available if Config.CtxKeys configured. + TraceId string // Trace id, only available if tracing is enabled. + Prefix string // Custom prefix string for logging content. + Content string // Content is the main logging content without error stack string produced by logger. + Stack string // Stack string produced by logger, only available if Config.StStatus configured. + IsAsync bool // IsAsync marks it is in asynchronous logging. +} + +type internalHandlerInfo struct { + index int // Middleware handling index for internal usage. + handlers []Handler // Handler array calling bu index. } // defaultHandler is the default handler for package. -var defaultHandler Handler = func(ctx context.Context, in *HandlerInput) { +var defaultHandler Handler + +// defaultPrintHandler is a handler for logging content printing. +// This handler outputs logging content to file/stdout/write if any of them configured. +func defaultPrintHandler(ctx context.Context, in *HandlerInput) { buffer := in.Logger.doDefaultPrint(ctx, in) if in.Buffer.Len() == 0 { in.Buffer = buffer @@ -46,18 +57,16 @@ func SetDefaultHandler(handler Handler) { defaultHandler = handler } -// HandlerJson is a handler for output logging content as a single json string. -func HandlerJson(ctx context.Context, in *HandlerInput) { - +// GetDefaultHandler returns the default handler of package. +func GetDefaultHandler() Handler { + return defaultHandler } // Next calls the next logging handler in middleware way. func (in *HandlerInput) Next(ctx context.Context) { - if len(in.Logger.config.Handlers)-1 > in.handlerIndex { - in.handlerIndex++ - in.Logger.config.Handlers[in.handlerIndex](ctx, in) - } else if defaultHandler != nil { - defaultHandler(ctx, in) + in.index++ + if in.index < len(in.handlers) { + in.handlers[in.index](ctx, in) } } @@ -76,19 +85,23 @@ func (in *HandlerInput) getDefaultBuffer(withColor bool) *bytes.Buffer { buffer.WriteString(in.TimeFormat) } if in.LevelFormat != "" { + var levelStr = "[" + in.LevelFormat + "]" if withColor { in.addStringToBuffer(buffer, in.Logger.getColoredStr( - in.Logger.getColorByLevel(in.Level), in.LevelFormat, + in.Logger.getColorByLevel(in.Level), levelStr, )) } else { - in.addStringToBuffer(buffer, in.LevelFormat) + in.addStringToBuffer(buffer, levelStr) } } if in.Prefix != "" { in.addStringToBuffer(buffer, in.Prefix) } + if in.TraceId != "" { + in.addStringToBuffer(buffer, "{"+in.TraceId+"}") + } if in.CtxStr != "" { - in.addStringToBuffer(buffer, in.CtxStr) + in.addStringToBuffer(buffer, "{"+in.CtxStr+"}") } if in.CallerFunc != "" { in.addStringToBuffer(buffer, in.CallerFunc) @@ -97,7 +110,11 @@ func (in *HandlerInput) getDefaultBuffer(withColor bool) *bytes.Buffer { in.addStringToBuffer(buffer, in.CallerPath) } if in.Content != "" { - in.addStringToBuffer(buffer, in.Content) + if in.Stack != "" { + in.addStringToBuffer(buffer, in.Content+"\nStack:\n"+in.Stack) + } else { + in.addStringToBuffer(buffer, in.Content) + } } // avoid a single space at the end of a line. buffer.WriteString("\n") diff --git a/os/glog/glog_logger_handler_json.go b/os/glog/glog_logger_handler_json.go new file mode 100644 index 000000000..a35536b76 --- /dev/null +++ b/os/glog/glog_logger_handler_json.go @@ -0,0 +1,47 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package glog + +import ( + "context" + + "github.com/gogf/gf/v2/internal/json" +) + +// HandlerOutputJson is the structure outputting logging content as single json. +type HandlerOutputJson struct { + Time string // Formatted time string, like "2016-01-09 12:00:00". + TraceId string // Trace id, only available if tracing is enabled. + CtxStr string // The retrieved context value string from context, only available if Config.CtxKeys configured. + Level string // Formatted level string, like "DEBU", "ERRO", etc. Eg: ERRO + CallerFunc string // The source function name that calls logging, only available if F_CALLER_FN set. + CallerPath string // The source file path and its line number that calls logging, only available if F_FILE_SHORT or F_FILE_LONG set. + Prefix string // Custom prefix string for logging content. + Content string // Content is the main logging content, containing error stack string produced by logger. + Stack string // Stack string produced by logger, only available if Config.StStatus configured. +} + +// HandlerJson is a handler for output logging content as a single json string. +func HandlerJson(ctx context.Context, in *HandlerInput) { + output := HandlerOutputJson{ + Time: in.TimeFormat, + TraceId: in.TraceId, + CtxStr: in.CtxStr, + Level: in.LevelFormat, + CallerFunc: in.CallerFunc, + CallerPath: in.CallerPath, + Prefix: in.Prefix, + Content: in.Content, + Stack: in.Stack, + } + jsonBytes, err := json.Marshal(output) + if err != nil { + panic(err) + } + in.Buffer.Write(jsonBytes) + in.Next(ctx) +} diff --git a/os/glog/glog_z_unit_logger_handler_test.go b/os/glog/glog_z_unit_logger_handler_test.go index b36999d1a..1ba3f8dad 100644 --- a/os/glog/glog_z_unit_logger_handler_test.go +++ b/os/glog/glog_z_unit_logger_handler_test.go @@ -71,3 +71,42 @@ func TestLogger_SetHandlers2(t *testing.T) { t.Assert(gstr.Count(arrayForHandlerTest2.At(0), "1 2 3"), 1) }) } + +func TestLogger_SetHandlers_HandlerJson(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + w := bytes.NewBuffer(nil) + l := glog.NewWithWriter(w) + l.SetHandlers(glog.HandlerJson) + l.SetCtxKeys("Trace-Id", "Span-Id", "Test") + ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890") + ctx = context.WithValue(ctx, "Span-Id", "abcdefg") + + l.Debug(ctx, 1, 2, 3) + t.Assert(gstr.Count(w.String(), "1234567890"), 1) + t.Assert(gstr.Count(w.String(), "abcdefg"), 1) + t.Assert(gstr.Count(w.String(), `"1 2 3"`), 1) + t.Assert(gstr.Count(w.String(), `"DEBU"`), 1) + }) +} + +func Test_SetDefaultHandler(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + oldHandler := glog.GetDefaultHandler() + glog.SetDefaultHandler(func(ctx context.Context, in *glog.HandlerInput) { + glog.HandlerJson(ctx, in) + }) + defer glog.SetDefaultHandler(oldHandler) + + w := bytes.NewBuffer(nil) + l := glog.NewWithWriter(w) + l.SetCtxKeys("Trace-Id", "Span-Id", "Test") + ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890") + ctx = context.WithValue(ctx, "Span-Id", "abcdefg") + + l.Debug(ctx, 1, 2, 3) + t.Assert(gstr.Count(w.String(), "1234567890"), 1) + t.Assert(gstr.Count(w.String(), "abcdefg"), 1) + t.Assert(gstr.Count(w.String(), `"1 2 3"`), 1) + t.Assert(gstr.Count(w.String(), `"DEBU"`), 1) + }) +}