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  }