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  }