github.com/searKing/golang/go@v1.2.117/log/slog/handler_state.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 "reflect" 11 "strconv" 12 "strings" 13 "time" 14 15 "github.com/searKing/golang/go/log/slog/internal/buffer" 16 "github.com/searKing/golang/go/runtime/goroutine" 17 strings_ "github.com/searKing/golang/go/strings" 18 time_ "github.com/searKing/golang/go/time" 19 unsafe_ "github.com/searKing/golang/go/unsafe" 20 ) 21 22 // handleState holds state for a single call to commonHandler.handle. 23 // The initial value of sep determines whether to emit a separator 24 // before the next key, after which it stays true. 25 type handleState struct { 26 h *commonHandler 27 buf *buffer.Buffer 28 freeBuf bool // should buf be freed? 29 sep string // separator to write before next key 30 prefix *buffer.Buffer // for text: key prefix 31 groups *[]string // pool-allocated slice of active groups, for ReplaceAttr 32 33 color string // for color of level and attr key 34 } 35 36 func (s *handleState) free() { 37 if s.freeBuf { 38 s.buf.Free() 39 } 40 if gs := s.groups; gs != nil { 41 *gs = (*gs)[:0] 42 groupPool.Put(gs) 43 } 44 s.prefix.Free() 45 } 46 47 func (s *handleState) openGroups() { 48 for _, n := range s.h.groups[s.h.nOpenGroups:] { 49 s.openGroup(n) 50 } 51 } 52 53 // Separator for group names and keys. 54 const keyComponentSep = '.' 55 56 // openGroup starts a new group of attributes 57 // with the given name. 58 func (s *handleState) openGroup(name string) { 59 s.prefix.WriteString(name) 60 s.prefix.WriteByte(keyComponentSep) 61 // Collect group names for ReplaceAttr. 62 if s.groups != nil { 63 *s.groups = append(*s.groups, name) 64 } 65 } 66 67 // closeGroup ends the group with the given name. 68 func (s *handleState) closeGroup(name string) { 69 s.prefix.Truncate(s.prefix.Len() - len(name) - 1) /* -1 for keyComponentSep */ 70 s.sep = s.h.attrSep() 71 if s.groups != nil { 72 *s.groups = (*s.groups)[:len(*s.groups)-1] 73 } 74 } 75 76 // replaceAttr handles replacement and checking for an empty key after replacement. 77 func (s *handleState) replaceAttr(a slog.Attr) slog.Attr { 78 if rep := s.h.opts.ReplaceAttr; rep != nil && a.Value.Kind() != slog.KindGroup { 79 var gs []string 80 if s.groups != nil { 81 gs = *s.groups 82 } 83 // Resolve before calling ReplaceAttr, so the user doesn't have to. 84 a.Value = a.Value.Resolve() 85 a = rep(gs, a) 86 } 87 a.Value = a.Value.Resolve() 88 // Elide empty Attrs. 89 if isEmptyAttr(a) { 90 return a 91 } 92 // Special case: Source. 93 if v := a.Value; v.Kind() == slog.KindAny { 94 if src, ok := v.Any().(*slog.Source); ok { 95 a.Value = sourceAsGroup(src) 96 } 97 } 98 return a 99 } 100 101 // replaceKey handles replacement and checking for an empty key after replacement. 102 func (s *handleState) replaceKey(key string) string { 103 if s.prefix != nil && s.prefix.Len() > 0 { 104 return s.prefix.String() + key 105 } 106 return key 107 } 108 109 // appendAttr appends the Attr's key and value using app. 110 // It handles replacement and checking for an empty key. 111 // after replacement. 112 func (s *handleState) appendAttr(a slog.Attr) { 113 a = s.replaceAttr(a) 114 // Elide empty Attrs. 115 if isEmptyAttr(a) { 116 return 117 } 118 if a.Value.Kind() == slog.KindGroup { 119 attrs := a.Value.Group() 120 // Output only non-empty groups. 121 if len(attrs) > 0 { 122 // Inline a group with an empty key. 123 if a.Key != "" { 124 s.openGroup(a.Key) 125 } 126 for _, aa := range attrs { 127 s.appendAttr(aa) 128 } 129 if a.Key != "" { 130 s.closeGroup(a.Key) 131 } 132 } 133 } else { 134 s.appendKey(a.Key) 135 s.appendValue(a.Value) 136 } 137 } 138 139 func (s *handleState) appendError(err error) { 140 s.appendStringMayQuote(fmt.Sprintf("!ERROR:%v", err.Error())) 141 } 142 143 func (s *handleState) appendKey(key string) { 144 s.buf.WriteString(s.sep) 145 if s.color != "" { 146 s.buf.WriteString(s.color) 147 } 148 s.appendStringMayQuote(s.replaceKey(key)) 149 if s.color != "" { 150 s.buf.WriteString(reset) 151 } 152 s.buf.WriteByte('=') 153 s.sep = s.h.attrSep() 154 } 155 156 func (s *handleState) appendString(str string) { 157 s.buf.WriteString(str) 158 } 159 160 func (s *handleState) appendBytesMayQuote(b []byte) { 161 // avoid the conversion to string. 162 // converts byte slice to string without a memory allocation. 163 s.appendStringMayQuote(unsafe_.BytesToString(b)) 164 } 165 166 func (s *handleState) appendStringMayQuote(str string) { 167 // text 168 if s.h.ForceQuote || 169 (!s.h.DisableQuote && needsQuoting(str, false)) || 170 (s.h.DisableQuote && needsQuoting(str, true)) { 171 172 // sharp format, if the string str can be backquoted, than 173 // represented unchanged as a single-line backquoted string 174 // without control characters other than tab. 175 _, _ = fmt.Fprintf(s.buf, "%#q", str) 176 } else { 177 s.buf.WriteString(str) 178 } 179 } 180 181 func (s *handleState) appendValue(v slog.Value) { 182 defer func() { 183 if r := recover(); r != nil { 184 // If it panics with a nil pointer, the most likely cases are 185 // an encoding.TextMarshaler or error fails to guard against nil, 186 // in which case "<nil>" seems to be the feasible choice. 187 // 188 // Adapted from the code in fmt/print.go. 189 if v := reflect.ValueOf(v.Any()); v.Kind() == reflect.Pointer && v.IsNil() { 190 s.appendStringMayQuote("<nil>") 191 return 192 } 193 194 // Otherwise just print the original panic message. 195 s.appendStringMayQuote(fmt.Sprintf("!PANIC: %v", r)) 196 } 197 }() 198 199 var err error 200 err = appendTextValue(s, v) 201 if err != nil { 202 s.appendError(err) 203 } 204 } 205 206 func (s *handleState) appendLevel(level slog.Level, padLevelText bool, maxLevelText int, humanReadable bool) { 207 // level 208 key := slog.LevelKey 209 var val string 210 a := s.replaceAttr(slog.Any(key, level)) 211 if !a.Equal(slog.Attr{}) { 212 var limit int 213 // Handle custom level values. 214 level, ok := a.Value.Any().(slog.Level) 215 if ok { 216 if f := s.h.ReplaceLevelString; f != nil { 217 val = s.h.ReplaceLevelString(level) 218 } else { 219 val = level.String() 220 } 221 limit = maxLevelText 222 if limit > 0 && limit < len(val) { 223 val = val[0:limit] 224 } 225 } else { 226 val = a.Value.String() 227 } 228 if padLevelText && limit > 0 { 229 // Generates the format string used in the next line, for example "%-6s" or "%-7s". 230 // Based on the max level text length. 231 var pad strings.Builder 232 pad.WriteString("%-") 233 pad.WriteString(strconv.Itoa(limit)) 234 pad.WriteString("s") 235 236 // Formats the level text by appending spaces up to the max length, for example: 237 // - "INFO " 238 // - "WARNING" 239 val = fmt.Sprintf(pad.String(), val) 240 } 241 } 242 243 // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. 244 // It's worth about 3X. Fprintf is hard. 245 if humanReadable { 246 s.buf.WriteString("[") 247 } 248 if s.color != "" { 249 s.buf.WriteString(s.color) 250 } 251 s.buf.WriteString(val) 252 if s.color != "" { 253 s.buf.WriteString(reset) 254 } 255 if humanReadable { 256 s.buf.WriteString("]") 257 } 258 } 259 260 func (s *handleState) appendGlogTime(t time.Time, layout string, mode TimestampMode, humanReadable bool) { 261 if mode == DisableTimestamp { 262 return 263 } 264 val := t.Round(0) // strip monotonic to match Attr behavior 265 switch mode { 266 case SinceStartTimestamp: 267 if humanReadable { 268 s.buf.WriteString(fmt.Sprintf(" [%04d]", int(val.Sub(baseTimestamp)/time.Second))) 269 return 270 } 271 s.buf.WriteString(fmt.Sprintf("%04d", int(val.Sub(baseTimestamp)/time.Second))) 272 default: 273 if humanReadable { 274 s.buf.WriteString(fmt.Sprintf("[%s]", val.Format(strings_.ValueOrDefault(layout, time_.GLogDate)))) 275 return 276 } 277 s.buf.WriteString(val.Format(strings_.ValueOrDefault(layout, time_.GLogDate))) 278 } 279 } 280 281 func (s *handleState) appendTime(t time.Time) { 282 writeTimeRFC3339Millis(s.buf, t) 283 } 284 285 func (s *handleState) appendPid(forceGoroutineId bool, humanReadable bool) { 286 if s.buf.Len() > 0 { 287 s.buf.WriteString(" ") 288 } 289 if forceGoroutineId { 290 if humanReadable { 291 s.buf.WriteString(fmt.Sprintf("[%-3d]", goroutine.ID())) 292 } else { 293 s.buf.WriteString(fmt.Sprintf("%-3d", goroutine.ID())) 294 } 295 } else { 296 if humanReadable { 297 // " [{pid}]" 298 s.buf.WriteString("[") 299 s.buf.WriteString(strconv.Itoa(s.h.sharedVar.pid)) 300 s.buf.WriteString("]") 301 } else { 302 // " {pid}" 303 s.buf.WriteString(strconv.Itoa(s.h.sharedVar.pid)) 304 } 305 } 306 } 307 308 func (s *handleState) appendSource(src *slog.Source, withFuncName bool, humanReadable bool) { 309 if withFuncName && src.Function != "" { 310 if humanReadable { 311 s.buf.WriteString(fmt.Sprintf(" [%s:%d](%s)", src.File, src.Line, src.Function)) 312 } else { 313 s.buf.WriteString(fmt.Sprintf(" %s:%d(%s)]", src.File, src.Line, src.Function)) 314 } 315 } else { 316 if humanReadable { 317 s.buf.WriteString(fmt.Sprintf(" [%s:%d]", src.File, src.Line)) 318 } else { 319 s.buf.WriteString(fmt.Sprintf(" %s:%d]", src.File, src.Line)) 320 } 321 } 322 } 323 324 // This takes half the time of Time.AppendFormat. 325 func writeTimeRFC3339Millis(buf *buffer.Buffer, t time.Time) { 326 year, month, day := t.Date() 327 buf.WritePosIntWidth(year, 4) 328 buf.WriteByte('-') 329 buf.WritePosIntWidth(int(month), 2) 330 buf.WriteByte('-') 331 buf.WritePosIntWidth(day, 2) 332 buf.WriteByte('T') 333 hour, min, sec := t.Clock() 334 buf.WritePosIntWidth(hour, 2) 335 buf.WriteByte(':') 336 buf.WritePosIntWidth(min, 2) 337 buf.WriteByte(':') 338 buf.WritePosIntWidth(sec, 2) 339 ns := t.Nanosecond() 340 buf.WriteByte('.') 341 buf.WritePosIntWidth(ns/1e6, 3) 342 _, offsetSeconds := t.Zone() 343 if offsetSeconds == 0 { 344 buf.WriteByte('Z') 345 } else { 346 offsetMinutes := offsetSeconds / 60 347 if offsetMinutes < 0 { 348 buf.WriteByte('-') 349 offsetMinutes = -offsetMinutes 350 } else { 351 buf.WriteByte('+') 352 } 353 buf.WritePosIntWidth(offsetMinutes/60, 2) 354 buf.WriteByte(':') 355 buf.WritePosIntWidth(offsetMinutes%60, 2) 356 } 357 }