github.com/searKing/golang/go@v1.2.117/log/slog/attrs.go (about) 1 // Copyright 2023 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package slog 6 7 import ( 8 "fmt" 9 "log/slog" 10 "slices" 11 "strings" 12 "time" 13 14 strings_ "github.com/searKing/golang/go/strings" 15 ) 16 17 // Error returns an Attr for an error value. 18 func Error(err error) slog.Attr { 19 return slog.Any(ErrorKey, err) 20 } 21 22 // isEmptyAttr reports whether a has an empty key and a nil value. 23 // That can be written as Attr{} or Any("", nil). 24 func isEmptyAttr(a slog.Attr) bool { 25 if a.Key == "" { 26 return true 27 } 28 switch a.Value.Kind() { 29 case slog.KindAny: 30 return a.Equal(slog.Any("", nil)) 31 case slog.KindBool: 32 return a.Equal(slog.Bool("", false)) 33 case slog.KindDuration: 34 return a.Equal(slog.Duration("", 0)) 35 case slog.KindFloat64: 36 return a.Equal(slog.Float64("", 0)) 37 case slog.KindInt64: 38 return a.Equal(slog.Int64("", 0)) 39 case slog.KindString: 40 return a.Equal(slog.String("", "")) 41 case slog.KindTime: 42 return a.Equal(slog.Time("", time.Time{})) 43 case slog.KindUint64: 44 return a.Equal(slog.Uint64("", 0)) 45 case slog.KindGroup: 46 return a.Equal(slog.Group("")) 47 default: 48 s := a.String() 49 return s == "" || s == "<nil>" 50 } 51 } 52 53 // ReplaceAttrTruncate returns [ReplaceAttr] which shrinks attr's key and value[string]'s len to n at most. 54 func ReplaceAttrTruncate(n int) func(groups []string, a slog.Attr) slog.Attr { 55 return func(groups []string, a slog.Attr) slog.Attr { 56 if n > 0 { 57 k := truncate(a.Key, n) 58 switch a.Value.Kind() { 59 case slog.KindString: 60 return slog.String(k, truncate(a.Value.String(), n)) 61 default: 62 return a 63 } 64 } 65 return a 66 } 67 } 68 69 // ReplaceAttrKeys allows customization of the key names for default fields. 70 type ReplaceAttrKeys map[string]string 71 72 func (f ReplaceAttrKeys) resolve(key string) string { 73 if k, ok := f[key]; ok { 74 return k 75 } 76 return key 77 } 78 79 // This is to not silently overwrite `time`, `msg`, `func` and `level` fields when 80 // dumping it. If this code wasn't there doing: 81 // 82 // slog.With("level", 1).Info("hello") 83 // 84 // Would just silently drop the user provided level. Instead with this code 85 // it'll logged as: 86 // 87 // {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} 88 // 89 // It's not exported because it's still using Data in an opinionated way. It's to 90 // avoid code duplication between the two default formatters. 91 func prefixAttrClashes(attrs []slog.Attr, builtinAttrKeys ReplaceAttrKeys) []slog.Attr { 92 if len(builtinAttrKeys) == 0 { 93 return attrs 94 } 95 attrs = replaceAttrClash(attrs, builtinAttrKeys.resolve(slog.TimeKey)) 96 attrs = replaceAttrClash(attrs, builtinAttrKeys.resolve(slog.MessageKey)) 97 attrs = replaceAttrClash(attrs, builtinAttrKeys.resolve(slog.LevelKey)) 98 attrs = replaceAttrClash(attrs, builtinAttrKeys.resolve(slog.SourceKey)) 99 return attrs 100 } 101 102 func replaceAttrClash(attrs []slog.Attr, key string) []slog.Attr { 103 var val slog.Value 104 if !slices.ContainsFunc(attrs, func(attr slog.Attr) bool { 105 return attr.Key == key 106 }) { 107 return attrs 108 } 109 attrs = append(attrs, slog.Attr{ 110 Key: "fields." + key, 111 Value: val, 112 }) 113 return slices.DeleteFunc(attrs, func(attr slog.Attr) bool { 114 return attr.Key == key 115 }) 116 } 117 118 func truncate(s string, n int) string { 119 if len(s) <= n { 120 return s 121 } 122 var buf strings.Builder 123 buf.WriteString(fmt.Sprintf("size: %d, string: ", len(s))) 124 buf.WriteString(strings_.Truncate(s, n)) 125 return buf.String() 126 }