Files
common-server-public/bbs-go-active/server/cmd/test/main.go
2025-03-03 17:43:50 +08:00

112 lines
2.2 KiB
Go

package main
import (
"fmt"
"log/slog"
"os"
"runtime"
"strings"
"github.com/pkg/errors"
)
func main() {
handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
ReplaceAttr: replaceAttr,
AddSource: true,
})
slog.SetDefault(slog.New(handler))
if err := f(); err != nil {
slog.Info("something went wrong", slog.Any("err", err))
}
}
func f() error {
return g()
}
func g() error {
return errors.New("test error")
}
func replaceAttr(groups []string, a slog.Attr) slog.Attr {
switch a.Value.Kind() {
// other cases
case slog.KindAny:
switch v := a.Value.Any().(type) {
case error:
a.Value = fmtErr(v)
}
}
return a
}
// fmtErr returns a slog.GroupValue 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()))
type StackTracer interface {
StackTrace() errors.StackTrace
}
// Find the trace to the location of the first errors.New,
// errors.Wrap, or errors.WithStack call.
var st StackTracer
for err := err; err != nil; err = errors.Unwrap(err) {
if x, ok := err.(StackTracer); ok {
st = x
}
}
if st != nil {
groupValues = append(groupValues,
slog.Any("trace", traceLines(st.StackTrace())),
)
}
return slog.GroupValue(groupValues...)
}
func traceLines(frames errors.StackTrace) []string {
traceLines := make([]string, len(frames))
// Iterate in reverse to skip uninteresting, consecutive runtime frames at
// the bottom of the trace.
var skipped int
skipping := true
for i := len(frames) - 1; i >= 0; i-- {
// Adapted from errors.Frame.MarshalText(), but avoiding repeated
// calls to FuncForPC and FileLine.
pc := uintptr(frames[i]) - 1
fn := runtime.FuncForPC(pc)
if fn == nil {
traceLines[i] = "unknown"
skipping = false
continue
}
name := fn.Name()
if skipping && strings.HasPrefix(name, "runtime.") {
skipped++
continue
} else {
skipping = false
}
filename, lineNr := fn.FileLine(pc)
traceLines[i] = fmt.Sprintf("%s %s:%d", name, filename, lineNr)
}
return traceLines[:len(traceLines)-skipped]
}