mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 23:26:38 +08:00
feat: 重构日志处理,使用自定义文本处理器增强日志格式和属性支持
This commit is contained in:
@@ -1,18 +1,76 @@
|
|||||||
package logging
|
package logging
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type customTextHandler struct {
|
||||||
|
writer io.Writer
|
||||||
|
level slog.Level
|
||||||
|
attrs []slog.Attr
|
||||||
|
groups []string
|
||||||
|
}
|
||||||
|
|
||||||
func Setup() {
|
func Setup() {
|
||||||
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
handler := &customTextHandler{
|
||||||
Level: parseLevel(os.Getenv("LOG_LEVEL")),
|
writer: os.Stdout,
|
||||||
})
|
level: parseLevel(os.Getenv("LOG_LEVEL")),
|
||||||
|
}
|
||||||
slog.SetDefault(slog.New(handler))
|
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 {
|
func parseLevel(value string) slog.Level {
|
||||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||||
case "debug":
|
case "debug":
|
||||||
@@ -25,3 +83,51 @@ func parseLevel(value string) slog.Level {
|
|||||||
return slog.LevelInfo
|
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, " ")
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,11 +28,62 @@ var commonLogWriter io.Writer = os.Stdout
|
|||||||
var errorLogWriter io.Writer = os.Stderr
|
var errorLogWriter io.Writer = os.Stderr
|
||||||
var defaultLogger *slog.Logger
|
var defaultLogger *slog.Logger
|
||||||
|
|
||||||
|
type customTextHandler struct {
|
||||||
|
writer io.Writer
|
||||||
|
level slog.Level
|
||||||
|
attrs []slog.Attr
|
||||||
|
groups []string
|
||||||
|
}
|
||||||
|
|
||||||
type levelRouterHandler struct {
|
type levelRouterHandler struct {
|
||||||
commonHandler slog.Handler
|
commonHandler slog.Handler
|
||||||
errorHandler 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 {
|
func (h *levelRouterHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
return h.commonHandler.Enabled(ctx, level) || h.errorHandler.Enabled(ctx, level)
|
return h.commonHandler.Enabled(ctx, level) || h.errorHandler.Enabled(ctx, level)
|
||||||
}
|
}
|
||||||
@@ -81,10 +135,9 @@ func ensureLogger() *slog.Logger {
|
|||||||
if defaultLogger != nil {
|
if defaultLogger != nil {
|
||||||
return defaultLogger
|
return defaultLogger
|
||||||
}
|
}
|
||||||
handlerOptions := &slog.HandlerOptions{Level: slogLevel()}
|
|
||||||
defaultLogger = slog.New(&levelRouterHandler{
|
defaultLogger = slog.New(&levelRouterHandler{
|
||||||
commonHandler: slog.NewTextHandler(commonLogWriter, handlerOptions),
|
commonHandler: &customTextHandler{writer: commonLogWriter, level: slogLevel()},
|
||||||
errorHandler: slog.NewTextHandler(errorLogWriter, handlerOptions),
|
errorHandler: &customTextHandler{writer: errorLogWriter, level: slogLevel()},
|
||||||
})
|
})
|
||||||
slog.SetDefault(defaultLogger)
|
slog.SetDefault(defaultLogger)
|
||||||
return defaultLogger
|
return defaultLogger
|
||||||
@@ -157,3 +210,51 @@ func FatalLog(v ...any) {
|
|||||||
ensureLogger().Error("fatal error", "details", v)
|
ensureLogger().Error("fatal error", "details", v)
|
||||||
os.Exit(1)
|
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, " ")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user