package utils import ( "log/slog" "os" "path/filepath" "github.com/mdobak/go-xerrors" ) type stackFrame struct { Func string `json:"func"` Source string `json:"source"` Line int `json:"line"` } func replaceAttr(_ []string, a slog.Attr) slog.Attr { switch a.Value.Kind() { case slog.KindAny: switch v := a.Value.Any().(type) { case error: a.Value = fmtErr(v) } } return a } // marshalStack extracts stack frames from the error func marshalStack(err error) []stackFrame { trace := xerrors.StackTrace(err) if len(trace) == 0 { return nil } frames := trace.Frames() s := make([]stackFrame, len(frames)) for i, v := range frames { f := stackFrame{ Source: filepath.Join( filepath.Base(filepath.Dir(v.File)), filepath.Base(v.File), ), Func: filepath.Base(v.Function), Line: v.Line, } s[i] = f } return s } // fmtErr returns a slog.Value with keys `msg` and `trace`. If the error // does not implement interface { StackTrace() errors.StackTrace }, the `trace` // key is omitted. func fmtErr(err error) slog.Value { var groupValues []slog.Attr groupValues = append(groupValues, slog.String("msg", err.Error())) frames := marshalStack(err) if frames != nil { groupValues = append(groupValues, slog.Any("trace", frames), ) } return slog.GroupValue(groupValues...) } func GetLogger() *slog.Logger { h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ ReplaceAttr: replaceAttr, }) logger := slog.New(h) return logger }