github.com/phuslu/log@v1.0.100/slog.go (about) 1 //go:build go1.21 2 // +build go1.21 3 4 package log 5 6 import ( 7 "context" 8 "io" 9 "log/slog" 10 "strconv" 11 "time" 12 ) 13 14 func slogJSONAttrEval(e *Entry, a slog.Attr) *Entry { 15 if a.Equal(slog.Attr{}) { 16 return e 17 } 18 value := a.Value.Resolve() 19 switch value.Kind() { 20 case slog.KindBool: 21 return e.Bool(a.Key, value.Bool()) 22 case slog.KindInt64: 23 return e.Int64(a.Key, value.Int64()) 24 case slog.KindUint64: 25 return e.Uint64(a.Key, value.Uint64()) 26 case slog.KindFloat64: 27 return e.Float64(a.Key, value.Float64()) 28 case slog.KindString: 29 return e.Str(a.Key, value.String()) 30 case slog.KindTime: 31 return e.TimeFormat(a.Key, time.RFC3339Nano, value.Time()) 32 case slog.KindDuration: 33 return e.Int64(a.Key, int64(value.Duration())) 34 case slog.KindGroup: 35 attrs := value.Group() 36 if len(attrs) == 0 { 37 return e 38 } 39 if a.Key == "" { 40 for _, attr := range attrs { 41 e = slogJSONAttrEval(e, attr) 42 } 43 return e 44 } 45 e.buf = append(e.buf, ',', '"') 46 e.buf = append(e.buf, a.Key...) 47 e.buf = append(e.buf, '"', ':') 48 i := len(e.buf) 49 for _, attr := range attrs { 50 e = slogJSONAttrEval(e, attr) 51 } 52 e.buf[i] = '{' 53 e.buf = append(e.buf, '}') 54 return e 55 case slog.KindAny: 56 return e.Any(a.Key, value.Any()) 57 default: 58 return e.Any(a.Key, value.Any()) 59 } 60 } 61 62 type slogJSONHandler struct { 63 level slog.Level 64 entry Entry 65 grouping bool 66 groups int 67 68 writer io.Writer 69 options *slog.HandlerOptions 70 } 71 72 func (h *slogJSONHandler) Enabled(_ context.Context, level slog.Level) bool { 73 return h.level <= level 74 } 75 76 func (h slogJSONHandler) WithAttrs(attrs []slog.Attr) slog.Handler { 77 if len(attrs) == 0 { 78 return &h 79 } 80 i := len(h.entry.buf) 81 for _, attr := range attrs { 82 h.entry = *slogJSONAttrEval(&h.entry, attr) 83 } 84 if h.grouping { 85 h.entry.buf[i] = '{' 86 } 87 h.grouping = false 88 return &h 89 } 90 91 func (h slogJSONHandler) WithGroup(name string) slog.Handler { 92 if name == "" { 93 return &h 94 } 95 if h.grouping { 96 h.entry.buf = append(h.entry.buf, '{') 97 } else { 98 h.entry.buf = append(h.entry.buf, ',') 99 } 100 h.entry.buf = append(h.entry.buf, '"') 101 h.entry.buf = append(h.entry.buf, name...) 102 h.entry.buf = append(h.entry.buf, '"', ':') 103 h.grouping = true 104 h.groups++ 105 return &h 106 } 107 108 func (h *slogJSONHandler) Handle(_ context.Context, r slog.Record) error { 109 e := epool.Get().(*Entry) 110 e.buf = e.buf[:0] 111 112 e.buf = append(e.buf, '{') 113 114 // time 115 if !r.Time.IsZero() { 116 e.buf = append(e.buf, '"') 117 e.buf = append(e.buf, slog.TimeKey...) 118 e.buf = append(e.buf, `":"`...) 119 e.buf = r.Time.AppendFormat(e.buf, time.RFC3339Nano) 120 e.buf = append(e.buf, `",`...) 121 } 122 123 // level 124 e.buf = append(e.buf, '"') 125 e.buf = append(e.buf, slog.LevelKey...) 126 switch r.Level { 127 case slog.LevelDebug: 128 e.buf = append(e.buf, `":"DEBUG"`...) 129 case slog.LevelInfo: 130 e.buf = append(e.buf, `":"INFO"`...) 131 case slog.LevelWarn: 132 e.buf = append(e.buf, `":"WARN"`...) 133 case slog.LevelError: 134 e.buf = append(e.buf, `":"ERROR"`...) 135 default: 136 e.buf = append(e.buf, `":"`...) 137 e.buf = append(e.buf, r.Level.String()...) 138 e.buf = append(e.buf, '"') 139 } 140 141 // source 142 if h.options != nil && h.options.AddSource && r.PC != 0 { 143 file, line, name := pcFileLineName(r.PC) 144 e.buf = append(e.buf, ',', '"') 145 e.buf = append(e.buf, slog.SourceKey...) 146 e.buf = append(e.buf, `":{"function":"`...) 147 e.buf = append(e.buf, name...) 148 e.buf = append(e.buf, `","file":"`...) 149 e.buf = append(e.buf, file...) 150 e.buf = append(e.buf, `","line":`...) 151 e.buf = strconv.AppendInt(e.buf, int64(line), 10) 152 e.buf = append(e.buf, '}') 153 } 154 155 // msg 156 e = e.Str(slog.MessageKey, r.Message) 157 158 // with 159 if b := h.entry.buf; len(b) != 0 { 160 e = e.Context(b) 161 } 162 i := len(e.buf) 163 164 // attrs 165 r.Attrs(func(attr slog.Attr) bool { 166 e = slogJSONAttrEval(e, attr) 167 return true 168 }) 169 170 // rollback helper 171 lastindex := func(buf []byte) int { 172 for i := len(buf) - 3; i >= 1; i-- { 173 if buf[i] == '"' && (buf[i-1] == ',' || buf[i-1] == '{') { 174 return i 175 } 176 } 177 return -1 178 } 179 180 // group attrs 181 if h.grouping { 182 if r.NumAttrs() > 0 { 183 e.buf[i] = '{' 184 } else if i = lastindex(e.buf); i > 0 { 185 e.buf = e.buf[:i-1] 186 h.groups-- 187 for e.buf[len(e.buf)-1] == ':' { 188 if i = lastindex(e.buf); i > 0 { 189 e.buf = e.buf[:i-1] 190 h.groups-- 191 } 192 } 193 } else { 194 e.buf = append(e.buf, '{') 195 } 196 } 197 198 // brackets closing 199 switch h.groups { 200 case 0: 201 e.buf = append(e.buf, '}', '\n') 202 case 1: 203 e.buf = append(e.buf, '}', '}', '\n') 204 case 2: 205 e.buf = append(e.buf, '}', '}', '}', '\n') 206 case 3: 207 e.buf = append(e.buf, '}', '}', '}', '}', '\n') 208 case 4: 209 e.buf = append(e.buf, '}', '}', '}', '}', '}', '\n') 210 default: 211 for i := 0; i <= h.groups; i++ { 212 e.buf = append(e.buf, '}') 213 } 214 e.buf = append(e.buf, '\n') 215 } 216 217 _, err := h.writer.Write(e.buf) 218 219 if cap(e.buf) <= bbcap { 220 epool.Put(e) 221 } 222 223 return err 224 } 225 226 type slogLevelvarHandler struct { 227 handler slog.Handler 228 level slog.Leveler 229 } 230 231 func (h *slogLevelvarHandler) Enabled(_ context.Context, level slog.Level) bool { 232 return h.level.Level() <= level 233 } 234 235 func (h slogLevelvarHandler) WithAttrs(attrs []slog.Attr) slog.Handler { 236 h.handler = h.handler.WithAttrs(attrs) 237 return &h 238 } 239 240 func (h slogLevelvarHandler) WithGroup(name string) slog.Handler { 241 h.handler = h.handler.WithGroup(name) 242 return &h 243 } 244 245 func (h *slogLevelvarHandler) Handle(ctx context.Context, r slog.Record) error { 246 return h.handler.Handle(ctx, r) 247 } 248 249 // SlogNewJSONHandler returns a drop-in replacement of slog.NewJSONHandler. 250 func SlogNewJSONHandler(writer io.Writer, options *slog.HandlerOptions) slog.Handler { 251 if options != nil && options.ReplaceAttr != nil { 252 // TODO: implement ReplaceAttr in a new handler. 253 return slog.NewJSONHandler(writer, options) 254 } 255 256 handler := &slogJSONHandler{ 257 writer: writer, 258 options: options, 259 } 260 261 if handler.options == nil || handler.options.Level == nil { 262 return handler 263 } 264 265 if level, ok := handler.options.Level.(slog.Level); ok { 266 handler.level = level 267 return handler 268 } 269 270 return &slogLevelvarHandler{ 271 handler: handler, 272 level: handler.options.Level, 273 } 274 }