Files
vRp.CD2g_test/utils/log/v2/handler.go
2024-04-02 19:22:39 +08:00

341 lines
8.4 KiB
Go

package log
import (
"context"
"encoding"
"fmt"
"io"
"log/slog"
"runtime"
"strconv"
"strings"
"sync"
"unicode"
"github.com/kercylan98/minotaur/utils/str"
)
func NewHandler(w io.Writer, opts ...*Options) *MinotaurHandler {
h := &MinotaurHandler{
opts: DefaultOptions(),
w: w,
}
for _, opt := range opts {
h.opts.Apply(opt)
}
return h
}
type MinotaurHandler struct {
opts *Options
groupPrefix string
groups []string
mu sync.Mutex
w io.Writer
}
func (h *MinotaurHandler) GetOptions() *Options {
return h.opts
}
func (h *MinotaurHandler) Enabled(ctx context.Context, level slog.Level) bool {
return level >= h.opts.GetLevel()
}
func (h *MinotaurHandler) Handle(ctx context.Context, record slog.Record) (err error) {
h.opts.getMany(func(opt *Options) {
if !h.Enabled(ctx, opt.level) {
return
}
var buffer = new(strings.Builder)
defer buffer.Reset()
processTime(buffer, record, opt)
processLevel(buffer, record, opt)
processCaller(buffer, record, opt)
processMessage(buffer, record, opt)
processAttrs(buffer, h, record, opt, record.Level, h.groupPrefix, h.groups)
if buffer.Len() == 0 {
return
}
buffer.WriteByte('\n')
h.mu.Lock()
defer h.mu.Unlock()
_, err = h.w.Write([]byte(buffer.String()))
return
})
return
}
func (h *MinotaurHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
if len(attrs) == 0 {
return h
}
handler := h.clone()
buffer := new(strings.Builder)
defer buffer.Reset()
h.opts.getMany(func(opt *Options) {
for _, attr := range attrs {
processAttrsAttr(buffer, h, attr, opt, levelNone, h.groupPrefix, h.groups)
}
})
//MinotaurHandler.opts.w = h.opts.FieldPrefix + string(*buf.bytes)
return handler
}
func (h *MinotaurHandler) WithGroup(name string) slog.Handler {
if name == "" {
return h
}
handler := h.clone()
handler.groupPrefix += name + "."
handler.groups = append(handler.groups, name)
return handler
}
func (h *MinotaurHandler) clone() *MinotaurHandler {
return &MinotaurHandler{
groupPrefix: h.groupPrefix,
opts: DefaultOptions().Apply(h.opts),
groups: h.groups,
w: h.w,
}
}
func processTime(buffer *strings.Builder, record slog.Record, opt *Options) {
if record.Time.IsZero() {
return
}
processAttrType(buffer, opt, AttrTypeTime, record.Time.Format(opt.timeLayout))
}
func processLevel(buffer *strings.Builder, record slog.Record, opt *Options) {
var levelColor = opt.levelColor[record.Level]
var levelText = opt.levelText[record.Level]
if levelText == str.None {
return
}
if opt.disabledColor || levelColor == nil {
buffer.WriteString(levelText)
} else {
buffer.WriteString(levelColor.Sprint(levelText))
}
buffer.WriteByte(' ')
}
func processCaller(buffer *strings.Builder, record slog.Record, opt *Options) {
if opt.disabledCaller {
return
}
pcs := make([]uintptr, 1)
runtime.CallersFrames(pcs[:runtime.Callers(opt.callerSkip, pcs)])
fs := runtime.CallersFrames(pcs)
f, _ := fs.Next()
if f.File == str.None {
return
}
file, line := opt.callerFormatter(f.File, f.Line)
processAttrType(buffer, opt, AttrTypeCaller, file+":"+line)
}
func processMessage(buffer *strings.Builder, record slog.Record, opt *Options) {
processAttrType(buffer, opt, AttrTypeMessage, record.Message)
}
func processAttrs(buffer *strings.Builder, handler *MinotaurHandler, record slog.Record, opt *Options, level Level, groupsPrefix string, groups []string) {
record.Attrs(func(attr slog.Attr) bool {
processAttrsAttr(buffer, handler, attr, opt, level, groupsPrefix, groups)
return true
})
}
func processAttrsAttr(buffer *strings.Builder, handler *MinotaurHandler, attr slog.Attr, opt *Options, level Level, groupsPrefix string, groups []string) {
attr.Value = attr.Value.Resolve()
if attr.Equal(slog.Attr{}) {
return
}
switch attr.Value.Kind() {
case slog.KindGroup:
if attr.Key != "" {
groupsPrefix += attr.Key + "."
groups = append(groups, attr.Key)
}
for _, groupAttr := range attr.Value.Group() {
processAttrsAttr(buffer, handler, groupAttr, opt, level, groupsPrefix, groups)
}
default:
switch v := attr.Value.Any().(type) {
case error:
if opt.stackTrace[level] {
stackTraceAttr := slog.Attr{Key: attr.Key, Value: formatTraceError(v, opt.stackTraceBeauty[level])}
processAttrsAttr(buffer, handler, stackTraceAttr, opt, level, handler.groupPrefix, handler.groups)
return
}
color := opt.keyColor[AttrTypeError]
if opt.disabledColor || color == nil {
processAttrsKey(buffer, opt, attr.Key, groupsPrefix)
} else {
processAttrsKey(buffer, opt, attr.Key, groupsPrefix, color)
}
processAttrsValue(buffer, opt, attr.Value, true)
buffer.WriteByte(' ')
case *beautyTrace:
color := opt.valueColor[AttrTypeTrace]
for _, s := range v.trace {
buffer.WriteString("\n\t")
if opt.disabledColor || color == nil {
buffer.WriteString(s)
} else {
buffer.WriteString(color.Sprint(s))
}
}
default:
processAttrsKey(buffer, opt, attr.Key, groupsPrefix)
processAttrsValue(buffer, opt, attr.Value, true)
buffer.WriteByte(' ')
}
}
}
func processAttrsString(s string, quote bool) string {
quoting := len(s) == 0
for _, r := range s {
if unicode.IsSpace(r) || r == '"' || r == '=' || !unicode.IsPrint(r) {
quoting = true
break
}
}
if quote && quoting {
return strconv.Quote(s)
} else {
return s
}
}
func processAttrsKey(buffer *strings.Builder, opt *Options, key, groups string, replaceColor ...*Color) {
if key == str.None {
return
}
color := opt.keyColor[AttrTypeField]
if len(replaceColor) > 0 {
color = replaceColor[0]
}
if opt.disabledColor || color == nil {
buffer.WriteString(processAttrsString(groups+key, true))
} else {
buffer.WriteString(color.Sprint(processAttrsString(groups+key, true)))
}
delimiterText := opt.delimiterText[AttrTypeField]
if delimiterText != str.None {
delimiterColor := opt.delimiterColor[AttrTypeField]
if len(replaceColor) > 1 {
delimiterColor = replaceColor[1]
}
if opt.disabledColor || delimiterColor == nil {
buffer.WriteString(delimiterText)
} else {
buffer.WriteString(delimiterColor.Sprint(delimiterText))
}
}
}
func processAttrsValue(buffer *strings.Builder, opt *Options, v slog.Value, quote bool) {
var text string
var color = opt.valueColor[AttrTypeField]
switch v.Kind() {
case slog.KindString:
text = processAttrsString(v.String(), quote)
case slog.KindInt64:
text = strconv.FormatInt(v.Int64(), 10)
case slog.KindUint64:
text = strconv.FormatUint(v.Uint64(), 10)
case slog.KindFloat64:
text = strconv.FormatFloat(v.Float64(), 'g', -1, 64)
case slog.KindBool:
text = strconv.FormatBool(v.Bool())
case slog.KindDuration:
text = processAttrsString(v.Duration().String(), quote)
case slog.KindTime:
text = processAttrsString(v.Time().String(), quote)
case slog.KindAny:
switch cv := v.Any().(type) {
case slog.Level:
processLevel(buffer, slog.Record{Level: cv}, opt)
case encoding.TextMarshaler:
data, err := cv.MarshalText()
if err != nil {
break
}
text = processAttrsString(string(data), quote)
case *slog.Source:
file, line := opt.callerFormatter(cv.File, cv.Line)
callerColor := opt.valueColor[AttrTypeCaller]
if opt.disabledColor || callerColor == nil {
buffer.WriteString(file + ":" + line)
} else {
buffer.WriteString(callerColor.Sprintf("%s:%s", file, line))
}
buffer.WriteByte(' ')
default:
text = processAttrsString(fmt.Sprint(v.Any()), quote)
}
default:
}
if text == str.None {
return
}
if opt.disabledColor || color == nil {
buffer.WriteString(text)
} else {
buffer.WriteString(color.Sprint(text))
}
}
func processAttrType(buffer *strings.Builder, opt *Options, attrType AttrType, value string) {
prefixText := opt.keyText[attrType]
prefixColor := opt.keyColor[attrType]
delimiterText := opt.delimiterText[attrType]
delimiterColor := opt.delimiterColor[attrType]
valueColor := opt.valueColor[attrType]
if prefixText != str.None {
// 前缀
if opt.disabledColor || prefixColor == nil {
buffer.WriteString(prefixText)
} else {
buffer.WriteString(prefixColor.Sprint(prefixText))
}
// 分隔符
if delimiterText != str.None {
if opt.disabledColor || delimiterColor == nil {
buffer.WriteString(delimiterText)
} else {
buffer.WriteString(delimiterColor.Sprint(delimiterText))
}
}
}
// 时间信息
if opt.disabledColor || valueColor == nil {
buffer.WriteString(value)
} else {
buffer.WriteString(valueColor.Sprint(value))
}
buffer.WriteByte(' ')
}