From 79a7e024d3501adfbd7aebf0f1849fe6cd19d583 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 13 Mar 2026 15:12:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E4=BD=BF=E7=94=A8=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E6=96=87=E6=9C=AC=E5=A4=84=E7=90=86=E5=99=A8=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E6=97=A5=E5=BF=97=E6=A0=BC=E5=BC=8F=E5=92=8C=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- atsf_agent/internal/logging/logger.go | 112 +++++++++++++++++++++++++- atsf_server/common/logger.go | 107 +++++++++++++++++++++++- 2 files changed, 213 insertions(+), 6 deletions(-) diff --git a/atsf_agent/internal/logging/logger.go b/atsf_agent/internal/logging/logger.go index a556a69d..a25227af 100644 --- a/atsf_agent/internal/logging/logger.go +++ b/atsf_agent/internal/logging/logger.go @@ -1,18 +1,76 @@ package logging import ( + "context" + "fmt" + "io" "log/slog" "os" + "path/filepath" + "runtime" + "slices" "strings" ) +type customTextHandler struct { + writer io.Writer + level slog.Level + attrs []slog.Attr + groups []string +} + func Setup() { - handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ - Level: parseLevel(os.Getenv("LOG_LEVEL")), - }) + handler := &customTextHandler{ + writer: os.Stdout, + level: parseLevel(os.Getenv("LOG_LEVEL")), + } slog.SetDefault(slog.New(handler)) } +func (h *customTextHandler) Enabled(_ context.Context, level slog.Level) bool { + return level >= h.level +} + +func (h *customTextHandler) Handle(_ context.Context, record slog.Record) error { + var builder strings.Builder + builder.WriteString(record.Time.Format("2006-01-02 15:04:05.000")) + builder.WriteString(" | ") + builder.WriteString(fmt.Sprintf("%-8s", levelLabel(record.Level))) + builder.WriteString(" | ") + builder.WriteString(sourceLocation(record.PC)) + builder.WriteString(" - ") + builder.WriteString(record.Message) + + attrs := make([]slog.Attr, 0, len(h.attrs)+record.NumAttrs()) + attrs = append(attrs, h.attrs...) + record.Attrs(func(attr slog.Attr) bool { + attrs = append(attrs, attr) + return true + }) + if len(attrs) > 0 { + builder.WriteString(" | ") + builder.WriteString(formatAttrs(h.groups, attrs)) + } + builder.WriteByte('\n') + _, err := io.WriteString(h.writer, builder.String()) + return err +} + +func (h *customTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + cloned := *h + cloned.attrs = append(slices.Clone(h.attrs), attrs...) + return &cloned +} + +func (h *customTextHandler) WithGroup(name string) slog.Handler { + if strings.TrimSpace(name) == "" { + return h + } + cloned := *h + cloned.groups = append(slices.Clone(h.groups), name) + return &cloned +} + func parseLevel(value string) slog.Level { switch strings.ToLower(strings.TrimSpace(value)) { case "debug": @@ -25,3 +83,51 @@ func parseLevel(value string) slog.Level { return slog.LevelInfo } } + +func levelLabel(level slog.Level) string { + switch { + case level <= slog.LevelDebug: + return "DEBUG" + case level < slog.LevelWarn: + return "INFO" + case level < slog.LevelError: + return "WARNING" + default: + return "ERROR" + } +} + +func sourceLocation(pc uintptr) string { + if pc == 0 { + return "unknown:unknown:0" + } + frame, _ := runtime.CallersFrames([]uintptr{pc}).Next() + fileName := strings.TrimSuffix(filepath.Base(frame.File), filepath.Ext(frame.File)) + if fileName == "" { + fileName = "unknown" + } + functionName := "unknown" + if frame.Function != "" { + parts := strings.Split(frame.Function, "/") + functionName = parts[len(parts)-1] + if dot := strings.LastIndex(functionName, "."); dot >= 0 && dot < len(functionName)-1 { + functionName = functionName[dot+1:] + } + } + return fmt.Sprintf("%s:%s:%d", fileName, functionName, frame.Line) +} + +func formatAttrs(groups []string, attrs []slog.Attr) string { + parts := make([]string, 0, len(attrs)) + for _, attr := range attrs { + key := attr.Key + if key == "" { + continue + } + if len(groups) > 0 { + key = strings.Join(append(slices.Clone(groups), key), ".") + } + parts = append(parts, fmt.Sprintf("%s=%v", key, attr.Value.Any())) + } + return strings.Join(parts, " ") +} diff --git a/atsf_server/common/logger.go b/atsf_server/common/logger.go index d13b8ab9..3eba74fa 100644 --- a/atsf_server/common/logger.go +++ b/atsf_server/common/logger.go @@ -2,11 +2,14 @@ package common import ( "context" + "fmt" "github.com/gin-gonic/gin" "io" "log/slog" "os" "path/filepath" + "runtime" + "slices" "strings" ) @@ -25,11 +28,62 @@ var commonLogWriter io.Writer = os.Stdout var errorLogWriter io.Writer = os.Stderr var defaultLogger *slog.Logger +type customTextHandler struct { + writer io.Writer + level slog.Level + attrs []slog.Attr + groups []string +} + type levelRouterHandler struct { commonHandler slog.Handler errorHandler slog.Handler } +func (h *customTextHandler) Enabled(_ context.Context, level slog.Level) bool { + return level >= h.level +} + +func (h *customTextHandler) Handle(_ context.Context, record slog.Record) error { + var builder strings.Builder + builder.WriteString(record.Time.Format("2006-01-02 15:04:05.000")) + builder.WriteString(" | ") + builder.WriteString(fmt.Sprintf("%-8s", levelLabel(record.Level))) + builder.WriteString(" | ") + builder.WriteString(sourceLocation(record.PC)) + builder.WriteString(" - ") + builder.WriteString(record.Message) + + attrs := make([]slog.Attr, 0, len(h.attrs)+record.NumAttrs()) + attrs = append(attrs, h.attrs...) + record.Attrs(func(attr slog.Attr) bool { + attrs = append(attrs, attr) + return true + }) + if len(attrs) > 0 { + builder.WriteString(" | ") + builder.WriteString(formatAttrs(h.groups, attrs)) + } + builder.WriteByte('\n') + _, err := io.WriteString(h.writer, builder.String()) + return err +} + +func (h *customTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + cloned := *h + cloned.attrs = append(slices.Clone(h.attrs), attrs...) + return &cloned +} + +func (h *customTextHandler) WithGroup(name string) slog.Handler { + if strings.TrimSpace(name) == "" { + return h + } + cloned := *h + cloned.groups = append(slices.Clone(h.groups), name) + return &cloned +} + func (h *levelRouterHandler) Enabled(ctx context.Context, level slog.Level) bool { return h.commonHandler.Enabled(ctx, level) || h.errorHandler.Enabled(ctx, level) } @@ -81,10 +135,9 @@ func ensureLogger() *slog.Logger { if defaultLogger != nil { return defaultLogger } - handlerOptions := &slog.HandlerOptions{Level: slogLevel()} defaultLogger = slog.New(&levelRouterHandler{ - commonHandler: slog.NewTextHandler(commonLogWriter, handlerOptions), - errorHandler: slog.NewTextHandler(errorLogWriter, handlerOptions), + commonHandler: &customTextHandler{writer: commonLogWriter, level: slogLevel()}, + errorHandler: &customTextHandler{writer: errorLogWriter, level: slogLevel()}, }) slog.SetDefault(defaultLogger) return defaultLogger @@ -157,3 +210,51 @@ func FatalLog(v ...any) { ensureLogger().Error("fatal error", "details", v) os.Exit(1) } + +func levelLabel(level slog.Level) string { + switch { + case level <= slog.LevelDebug: + return "DEBUG" + case level < slog.LevelWarn: + return "INFO" + case level < slog.LevelError: + return "WARNING" + default: + return "ERROR" + } +} + +func sourceLocation(pc uintptr) string { + if pc == 0 { + return "unknown:unknown:0" + } + frame, _ := runtime.CallersFrames([]uintptr{pc}).Next() + fileName := strings.TrimSuffix(filepath.Base(frame.File), filepath.Ext(frame.File)) + if fileName == "" { + fileName = "unknown" + } + functionName := "unknown" + if frame.Function != "" { + parts := strings.Split(frame.Function, "/") + functionName = parts[len(parts)-1] + if dot := strings.LastIndex(functionName, "."); dot >= 0 && dot < len(functionName)-1 { + functionName = functionName[dot+1:] + } + } + return fmt.Sprintf("%s:%s:%d", fileName, functionName, frame.Line) +} + +func formatAttrs(groups []string, attrs []slog.Attr) string { + parts := make([]string, 0, len(attrs)) + for _, attr := range attrs { + key := attr.Key + if key == "" { + continue + } + if len(groups) > 0 { + key = strings.Join(append(slices.Clone(groups), key), ".") + } + parts = append(parts, fmt.Sprintf("%s=%v", key, attr.Value.Any())) + } + return strings.Join(parts, " ") +}