mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 19:22:25 -05:00 
			
		
		
		
	
		
			
	
	
		
			578 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			578 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | // Copyright 2022 The Go Authors. All rights reserved. | ||
|  | // Use of this source code is governed by a BSD-style | ||
|  | // license that can be found in the LICENSE file. | ||
|  | 
 | ||
|  | package slog | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"context" | ||
|  | 	"fmt" | ||
|  | 	"io" | ||
|  | 	"reflect" | ||
|  | 	"strconv" | ||
|  | 	"sync" | ||
|  | 	"time" | ||
|  | 
 | ||
|  | 	"golang.org/x/exp/slices" | ||
|  | 	"golang.org/x/exp/slog/internal/buffer" | ||
|  | ) | ||
|  | 
 | ||
|  | // A Handler handles log records produced by a Logger.. | ||
|  | // | ||
|  | // A typical handler may print log records to standard error, | ||
|  | // or write them to a file or database, or perhaps augment them | ||
|  | // with additional attributes and pass them on to another handler. | ||
|  | // | ||
|  | // Any of the Handler's methods may be called concurrently with itself | ||
|  | // or with other methods. It is the responsibility of the Handler to | ||
|  | // manage this concurrency. | ||
|  | // | ||
|  | // Users of the slog package should not invoke Handler methods directly. | ||
|  | // They should use the methods of [Logger] instead. | ||
|  | type Handler interface { | ||
|  | 	// Enabled reports whether the handler handles records at the given level. | ||
|  | 	// The handler ignores records whose level is lower. | ||
|  | 	// It is called early, before any arguments are processed, | ||
|  | 	// to save effort if the log event should be discarded. | ||
|  | 	// If called from a Logger method, the first argument is the context | ||
|  | 	// passed to that method, or context.Background() if nil was passed | ||
|  | 	// or the method does not take a context. | ||
|  | 	// The context is passed so Enabled can use its values | ||
|  | 	// to make a decision. | ||
|  | 	Enabled(context.Context, Level) bool | ||
|  | 
 | ||
|  | 	// Handle handles the Record. | ||
|  | 	// It will only be called when Enabled returns true. | ||
|  | 	// The Context argument is as for Enabled. | ||
|  | 	// It is present solely to provide Handlers access to the context's values. | ||
|  | 	// Canceling the context should not affect record processing. | ||
|  | 	// (Among other things, log messages may be necessary to debug a | ||
|  | 	// cancellation-related problem.) | ||
|  | 	// | ||
|  | 	// Handle methods that produce output should observe the following rules: | ||
|  | 	//   - If r.Time is the zero time, ignore the time. | ||
|  | 	//   - If r.PC is zero, ignore it. | ||
|  | 	//   - Attr's values should be resolved. | ||
|  | 	//   - If an Attr's key and value are both the zero value, ignore the Attr. | ||
|  | 	//     This can be tested with attr.Equal(Attr{}). | ||
|  | 	//   - If a group's key is empty, inline the group's Attrs. | ||
|  | 	//   - If a group has no Attrs (even if it has a non-empty key), | ||
|  | 	//     ignore it. | ||
|  | 	Handle(context.Context, Record) error | ||
|  | 
 | ||
|  | 	// WithAttrs returns a new Handler whose attributes consist of | ||
|  | 	// both the receiver's attributes and the arguments. | ||
|  | 	// The Handler owns the slice: it may retain, modify or discard it. | ||
|  | 	WithAttrs(attrs []Attr) Handler | ||
|  | 
 | ||
|  | 	// WithGroup returns a new Handler with the given group appended to | ||
|  | 	// the receiver's existing groups. | ||
|  | 	// The keys of all subsequent attributes, whether added by With or in a | ||
|  | 	// Record, should be qualified by the sequence of group names. | ||
|  | 	// | ||
|  | 	// How this qualification happens is up to the Handler, so long as | ||
|  | 	// this Handler's attribute keys differ from those of another Handler | ||
|  | 	// with a different sequence of group names. | ||
|  | 	// | ||
|  | 	// A Handler should treat WithGroup as starting a Group of Attrs that ends | ||
|  | 	// at the end of the log event. That is, | ||
|  | 	// | ||
|  | 	//     logger.WithGroup("s").LogAttrs(level, msg, slog.Int("a", 1), slog.Int("b", 2)) | ||
|  | 	// | ||
|  | 	// should behave like | ||
|  | 	// | ||
|  | 	//     logger.LogAttrs(level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2))) | ||
|  | 	// | ||
|  | 	// If the name is empty, WithGroup returns the receiver. | ||
|  | 	WithGroup(name string) Handler | ||
|  | } | ||
|  | 
 | ||
|  | type defaultHandler struct { | ||
|  | 	ch *commonHandler | ||
|  | 	// log.Output, except for testing | ||
|  | 	output func(calldepth int, message string) error | ||
|  | } | ||
|  | 
 | ||
|  | func newDefaultHandler(output func(int, string) error) *defaultHandler { | ||
|  | 	return &defaultHandler{ | ||
|  | 		ch:     &commonHandler{json: false}, | ||
|  | 		output: output, | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (*defaultHandler) Enabled(_ context.Context, l Level) bool { | ||
|  | 	return l >= LevelInfo | ||
|  | } | ||
|  | 
 | ||
|  | // Collect the level, attributes and message in a string and | ||
|  | // write it with the default log.Logger. | ||
|  | // Let the log.Logger handle time and file/line. | ||
|  | func (h *defaultHandler) Handle(ctx context.Context, r Record) error { | ||
|  | 	buf := buffer.New() | ||
|  | 	buf.WriteString(r.Level.String()) | ||
|  | 	buf.WriteByte(' ') | ||
|  | 	buf.WriteString(r.Message) | ||
|  | 	state := h.ch.newHandleState(buf, true, " ", nil) | ||
|  | 	defer state.free() | ||
|  | 	state.appendNonBuiltIns(r) | ||
|  | 
 | ||
|  | 	// skip [h.output, defaultHandler.Handle, handlerWriter.Write, log.Output] | ||
|  | 	return h.output(4, buf.String()) | ||
|  | } | ||
|  | 
 | ||
|  | func (h *defaultHandler) WithAttrs(as []Attr) Handler { | ||
|  | 	return &defaultHandler{h.ch.withAttrs(as), h.output} | ||
|  | } | ||
|  | 
 | ||
|  | func (h *defaultHandler) WithGroup(name string) Handler { | ||
|  | 	return &defaultHandler{h.ch.withGroup(name), h.output} | ||
|  | } | ||
|  | 
 | ||
|  | // HandlerOptions are options for a TextHandler or JSONHandler. | ||
|  | // A zero HandlerOptions consists entirely of default values. | ||
|  | type HandlerOptions struct { | ||
|  | 	// AddSource causes the handler to compute the source code position | ||
|  | 	// of the log statement and add a SourceKey attribute to the output. | ||
|  | 	AddSource bool | ||
|  | 
 | ||
|  | 	// Level reports the minimum record level that will be logged. | ||
|  | 	// The handler discards records with lower levels. | ||
|  | 	// If Level is nil, the handler assumes LevelInfo. | ||
|  | 	// The handler calls Level.Level for each record processed; | ||
|  | 	// to adjust the minimum level dynamically, use a LevelVar. | ||
|  | 	Level Leveler | ||
|  | 
 | ||
|  | 	// ReplaceAttr is called to rewrite each non-group attribute before it is logged. | ||
|  | 	// The attribute's value has been resolved (see [Value.Resolve]). | ||
|  | 	// If ReplaceAttr returns an Attr with Key == "", the attribute is discarded. | ||
|  | 	// | ||
|  | 	// The built-in attributes with keys "time", "level", "source", and "msg" | ||
|  | 	// are passed to this function, except that time is omitted | ||
|  | 	// if zero, and source is omitted if AddSource is false. | ||
|  | 	// | ||
|  | 	// The first argument is a list of currently open groups that contain the | ||
|  | 	// Attr. It must not be retained or modified. ReplaceAttr is never called | ||
|  | 	// for Group attributes, only their contents. For example, the attribute | ||
|  | 	// list | ||
|  | 	// | ||
|  | 	//     Int("a", 1), Group("g", Int("b", 2)), Int("c", 3) | ||
|  | 	// | ||
|  | 	// results in consecutive calls to ReplaceAttr with the following arguments: | ||
|  | 	// | ||
|  | 	//     nil, Int("a", 1) | ||
|  | 	//     []string{"g"}, Int("b", 2) | ||
|  | 	//     nil, Int("c", 3) | ||
|  | 	// | ||
|  | 	// ReplaceAttr can be used to change the default keys of the built-in | ||
|  | 	// attributes, convert types (for example, to replace a `time.Time` with the | ||
|  | 	// integer seconds since the Unix epoch), sanitize personal information, or | ||
|  | 	// remove attributes from the output. | ||
|  | 	ReplaceAttr func(groups []string, a Attr) Attr | ||
|  | } | ||
|  | 
 | ||
|  | // Keys for "built-in" attributes. | ||
|  | const ( | ||
|  | 	// TimeKey is the key used by the built-in handlers for the time | ||
|  | 	// when the log method is called. The associated Value is a [time.Time]. | ||
|  | 	TimeKey = "time" | ||
|  | 	// LevelKey is the key used by the built-in handlers for the level | ||
|  | 	// of the log call. The associated value is a [Level]. | ||
|  | 	LevelKey = "level" | ||
|  | 	// MessageKey is the key used by the built-in handlers for the | ||
|  | 	// message of the log call. The associated value is a string. | ||
|  | 	MessageKey = "msg" | ||
|  | 	// SourceKey is the key used by the built-in handlers for the source file | ||
|  | 	// and line of the log call. The associated value is a string. | ||
|  | 	SourceKey = "source" | ||
|  | ) | ||
|  | 
 | ||
|  | type commonHandler struct { | ||
|  | 	json              bool // true => output JSON; false => output text | ||
|  | 	opts              HandlerOptions | ||
|  | 	preformattedAttrs []byte | ||
|  | 	groupPrefix       string   // for text: prefix of groups opened in preformatting | ||
|  | 	groups            []string // all groups started from WithGroup | ||
|  | 	nOpenGroups       int      // the number of groups opened in preformattedAttrs | ||
|  | 	mu                sync.Mutex | ||
|  | 	w                 io.Writer | ||
|  | } | ||
|  | 
 | ||
|  | func (h *commonHandler) clone() *commonHandler { | ||
|  | 	// We can't use assignment because we can't copy the mutex. | ||
|  | 	return &commonHandler{ | ||
|  | 		json:              h.json, | ||
|  | 		opts:              h.opts, | ||
|  | 		preformattedAttrs: slices.Clip(h.preformattedAttrs), | ||
|  | 		groupPrefix:       h.groupPrefix, | ||
|  | 		groups:            slices.Clip(h.groups), | ||
|  | 		nOpenGroups:       h.nOpenGroups, | ||
|  | 		w:                 h.w, | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // enabled reports whether l is greater than or equal to the | ||
|  | // minimum level. | ||
|  | func (h *commonHandler) enabled(l Level) bool { | ||
|  | 	minLevel := LevelInfo | ||
|  | 	if h.opts.Level != nil { | ||
|  | 		minLevel = h.opts.Level.Level() | ||
|  | 	} | ||
|  | 	return l >= minLevel | ||
|  | } | ||
|  | 
 | ||
|  | func (h *commonHandler) withAttrs(as []Attr) *commonHandler { | ||
|  | 	h2 := h.clone() | ||
|  | 	// Pre-format the attributes as an optimization. | ||
|  | 	prefix := buffer.New() | ||
|  | 	defer prefix.Free() | ||
|  | 	prefix.WriteString(h.groupPrefix) | ||
|  | 	state := h2.newHandleState((*buffer.Buffer)(&h2.preformattedAttrs), false, "", prefix) | ||
|  | 	defer state.free() | ||
|  | 	if len(h2.preformattedAttrs) > 0 { | ||
|  | 		state.sep = h.attrSep() | ||
|  | 	} | ||
|  | 	state.openGroups() | ||
|  | 	for _, a := range as { | ||
|  | 		state.appendAttr(a) | ||
|  | 	} | ||
|  | 	// Remember the new prefix for later keys. | ||
|  | 	h2.groupPrefix = state.prefix.String() | ||
|  | 	// Remember how many opened groups are in preformattedAttrs, | ||
|  | 	// so we don't open them again when we handle a Record. | ||
|  | 	h2.nOpenGroups = len(h2.groups) | ||
|  | 	return h2 | ||
|  | } | ||
|  | 
 | ||
|  | func (h *commonHandler) withGroup(name string) *commonHandler { | ||
|  | 	if name == "" { | ||
|  | 		return h | ||
|  | 	} | ||
|  | 	h2 := h.clone() | ||
|  | 	h2.groups = append(h2.groups, name) | ||
|  | 	return h2 | ||
|  | } | ||
|  | 
 | ||
|  | func (h *commonHandler) handle(r Record) error { | ||
|  | 	state := h.newHandleState(buffer.New(), true, "", nil) | ||
|  | 	defer state.free() | ||
|  | 	if h.json { | ||
|  | 		state.buf.WriteByte('{') | ||
|  | 	} | ||
|  | 	// Built-in attributes. They are not in a group. | ||
|  | 	stateGroups := state.groups | ||
|  | 	state.groups = nil // So ReplaceAttrs sees no groups instead of the pre groups. | ||
|  | 	rep := h.opts.ReplaceAttr | ||
|  | 	// time | ||
|  | 	if !r.Time.IsZero() { | ||
|  | 		key := TimeKey | ||
|  | 		val := r.Time.Round(0) // strip monotonic to match Attr behavior | ||
|  | 		if rep == nil { | ||
|  | 			state.appendKey(key) | ||
|  | 			state.appendTime(val) | ||
|  | 		} else { | ||
|  | 			state.appendAttr(Time(key, val)) | ||
|  | 		} | ||
|  | 	} | ||
|  | 	// level | ||
|  | 	key := LevelKey | ||
|  | 	val := r.Level | ||
|  | 	if rep == nil { | ||
|  | 		state.appendKey(key) | ||
|  | 		state.appendString(val.String()) | ||
|  | 	} else { | ||
|  | 		state.appendAttr(Any(key, val)) | ||
|  | 	} | ||
|  | 	// source | ||
|  | 	if h.opts.AddSource { | ||
|  | 		state.appendAttr(Any(SourceKey, r.source())) | ||
|  | 	} | ||
|  | 	key = MessageKey | ||
|  | 	msg := r.Message | ||
|  | 	if rep == nil { | ||
|  | 		state.appendKey(key) | ||
|  | 		state.appendString(msg) | ||
|  | 	} else { | ||
|  | 		state.appendAttr(String(key, msg)) | ||
|  | 	} | ||
|  | 	state.groups = stateGroups // Restore groups passed to ReplaceAttrs. | ||
|  | 	state.appendNonBuiltIns(r) | ||
|  | 	state.buf.WriteByte('\n') | ||
|  | 
 | ||
|  | 	h.mu.Lock() | ||
|  | 	defer h.mu.Unlock() | ||
|  | 	_, err := h.w.Write(*state.buf) | ||
|  | 	return err | ||
|  | } | ||
|  | 
 | ||
|  | func (s *handleState) appendNonBuiltIns(r Record) { | ||
|  | 	// preformatted Attrs | ||
|  | 	if len(s.h.preformattedAttrs) > 0 { | ||
|  | 		s.buf.WriteString(s.sep) | ||
|  | 		s.buf.Write(s.h.preformattedAttrs) | ||
|  | 		s.sep = s.h.attrSep() | ||
|  | 	} | ||
|  | 	// Attrs in Record -- unlike the built-in ones, they are in groups started | ||
|  | 	// from WithGroup. | ||
|  | 	s.prefix = buffer.New() | ||
|  | 	defer s.prefix.Free() | ||
|  | 	s.prefix.WriteString(s.h.groupPrefix) | ||
|  | 	s.openGroups() | ||
|  | 	r.Attrs(func(a Attr) bool { | ||
|  | 		s.appendAttr(a) | ||
|  | 		return true | ||
|  | 	}) | ||
|  | 	if s.h.json { | ||
|  | 		// Close all open groups. | ||
|  | 		for range s.h.groups { | ||
|  | 			s.buf.WriteByte('}') | ||
|  | 		} | ||
|  | 		// Close the top-level object. | ||
|  | 		s.buf.WriteByte('}') | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // attrSep returns the separator between attributes. | ||
|  | func (h *commonHandler) attrSep() string { | ||
|  | 	if h.json { | ||
|  | 		return "," | ||
|  | 	} | ||
|  | 	return " " | ||
|  | } | ||
|  | 
 | ||
|  | // handleState holds state for a single call to commonHandler.handle. | ||
|  | // The initial value of sep determines whether to emit a separator | ||
|  | // before the next key, after which it stays true. | ||
|  | type handleState struct { | ||
|  | 	h       *commonHandler | ||
|  | 	buf     *buffer.Buffer | ||
|  | 	freeBuf bool           // should buf be freed? | ||
|  | 	sep     string         // separator to write before next key | ||
|  | 	prefix  *buffer.Buffer // for text: key prefix | ||
|  | 	groups  *[]string      // pool-allocated slice of active groups, for ReplaceAttr | ||
|  | } | ||
|  | 
 | ||
|  | var groupPool = sync.Pool{New: func() any { | ||
|  | 	s := make([]string, 0, 10) | ||
|  | 	return &s | ||
|  | }} | ||
|  | 
 | ||
|  | func (h *commonHandler) newHandleState(buf *buffer.Buffer, freeBuf bool, sep string, prefix *buffer.Buffer) handleState { | ||
|  | 	s := handleState{ | ||
|  | 		h:       h, | ||
|  | 		buf:     buf, | ||
|  | 		freeBuf: freeBuf, | ||
|  | 		sep:     sep, | ||
|  | 		prefix:  prefix, | ||
|  | 	} | ||
|  | 	if h.opts.ReplaceAttr != nil { | ||
|  | 		s.groups = groupPool.Get().(*[]string) | ||
|  | 		*s.groups = append(*s.groups, h.groups[:h.nOpenGroups]...) | ||
|  | 	} | ||
|  | 	return s | ||
|  | } | ||
|  | 
 | ||
|  | func (s *handleState) free() { | ||
|  | 	if s.freeBuf { | ||
|  | 		s.buf.Free() | ||
|  | 	} | ||
|  | 	if gs := s.groups; gs != nil { | ||
|  | 		*gs = (*gs)[:0] | ||
|  | 		groupPool.Put(gs) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (s *handleState) openGroups() { | ||
|  | 	for _, n := range s.h.groups[s.h.nOpenGroups:] { | ||
|  | 		s.openGroup(n) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // Separator for group names and keys. | ||
|  | const keyComponentSep = '.' | ||
|  | 
 | ||
|  | // openGroup starts a new group of attributes | ||
|  | // with the given name. | ||
|  | func (s *handleState) openGroup(name string) { | ||
|  | 	if s.h.json { | ||
|  | 		s.appendKey(name) | ||
|  | 		s.buf.WriteByte('{') | ||
|  | 		s.sep = "" | ||
|  | 	} else { | ||
|  | 		s.prefix.WriteString(name) | ||
|  | 		s.prefix.WriteByte(keyComponentSep) | ||
|  | 	} | ||
|  | 	// Collect group names for ReplaceAttr. | ||
|  | 	if s.groups != nil { | ||
|  | 		*s.groups = append(*s.groups, name) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // closeGroup ends the group with the given name. | ||
|  | func (s *handleState) closeGroup(name string) { | ||
|  | 	if s.h.json { | ||
|  | 		s.buf.WriteByte('}') | ||
|  | 	} else { | ||
|  | 		(*s.prefix) = (*s.prefix)[:len(*s.prefix)-len(name)-1 /* for keyComponentSep */] | ||
|  | 	} | ||
|  | 	s.sep = s.h.attrSep() | ||
|  | 	if s.groups != nil { | ||
|  | 		*s.groups = (*s.groups)[:len(*s.groups)-1] | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // appendAttr appends the Attr's key and value using app. | ||
|  | // It handles replacement and checking for an empty key. | ||
|  | // after replacement). | ||
|  | func (s *handleState) appendAttr(a Attr) { | ||
|  | 	if rep := s.h.opts.ReplaceAttr; rep != nil && a.Value.Kind() != KindGroup { | ||
|  | 		var gs []string | ||
|  | 		if s.groups != nil { | ||
|  | 			gs = *s.groups | ||
|  | 		} | ||
|  | 		// Resolve before calling ReplaceAttr, so the user doesn't have to. | ||
|  | 		a.Value = a.Value.Resolve() | ||
|  | 		a = rep(gs, a) | ||
|  | 	} | ||
|  | 	a.Value = a.Value.Resolve() | ||
|  | 	// Elide empty Attrs. | ||
|  | 	if a.isEmpty() { | ||
|  | 		return | ||
|  | 	} | ||
|  | 	// Special case: Source. | ||
|  | 	if v := a.Value; v.Kind() == KindAny { | ||
|  | 		if src, ok := v.Any().(*Source); ok { | ||
|  | 			if s.h.json { | ||
|  | 				a.Value = src.group() | ||
|  | 			} else { | ||
|  | 				a.Value = StringValue(fmt.Sprintf("%s:%d", src.File, src.Line)) | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 	if a.Value.Kind() == KindGroup { | ||
|  | 		attrs := a.Value.Group() | ||
|  | 		// Output only non-empty groups. | ||
|  | 		if len(attrs) > 0 { | ||
|  | 			// Inline a group with an empty key. | ||
|  | 			if a.Key != "" { | ||
|  | 				s.openGroup(a.Key) | ||
|  | 			} | ||
|  | 			for _, aa := range attrs { | ||
|  | 				s.appendAttr(aa) | ||
|  | 			} | ||
|  | 			if a.Key != "" { | ||
|  | 				s.closeGroup(a.Key) | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} else { | ||
|  | 		s.appendKey(a.Key) | ||
|  | 		s.appendValue(a.Value) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (s *handleState) appendError(err error) { | ||
|  | 	s.appendString(fmt.Sprintf("!ERROR:%v", err)) | ||
|  | } | ||
|  | 
 | ||
|  | func (s *handleState) appendKey(key string) { | ||
|  | 	s.buf.WriteString(s.sep) | ||
|  | 	if s.prefix != nil { | ||
|  | 		// TODO: optimize by avoiding allocation. | ||
|  | 		s.appendString(string(*s.prefix) + key) | ||
|  | 	} else { | ||
|  | 		s.appendString(key) | ||
|  | 	} | ||
|  | 	if s.h.json { | ||
|  | 		s.buf.WriteByte(':') | ||
|  | 	} else { | ||
|  | 		s.buf.WriteByte('=') | ||
|  | 	} | ||
|  | 	s.sep = s.h.attrSep() | ||
|  | } | ||
|  | 
 | ||
|  | func (s *handleState) appendString(str string) { | ||
|  | 	if s.h.json { | ||
|  | 		s.buf.WriteByte('"') | ||
|  | 		*s.buf = appendEscapedJSONString(*s.buf, str) | ||
|  | 		s.buf.WriteByte('"') | ||
|  | 	} else { | ||
|  | 		// text | ||
|  | 		if needsQuoting(str) { | ||
|  | 			*s.buf = strconv.AppendQuote(*s.buf, str) | ||
|  | 		} else { | ||
|  | 			s.buf.WriteString(str) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (s *handleState) appendValue(v Value) { | ||
|  | 	defer func() { | ||
|  | 		if r := recover(); r != nil { | ||
|  | 			// If it panics with a nil pointer, the most likely cases are | ||
|  | 			// an encoding.TextMarshaler or error fails to guard against nil, | ||
|  | 			// in which case "<nil>" seems to be the feasible choice. | ||
|  | 			// | ||
|  | 			// Adapted from the code in fmt/print.go. | ||
|  | 			if v := reflect.ValueOf(v.any); v.Kind() == reflect.Pointer && v.IsNil() { | ||
|  | 				s.appendString("<nil>") | ||
|  | 				return | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// Otherwise just print the original panic message. | ||
|  | 			s.appendString(fmt.Sprintf("!PANIC: %v", r)) | ||
|  | 		} | ||
|  | 	}() | ||
|  | 
 | ||
|  | 	var err error | ||
|  | 	if s.h.json { | ||
|  | 		err = appendJSONValue(s, v) | ||
|  | 	} else { | ||
|  | 		err = appendTextValue(s, v) | ||
|  | 	} | ||
|  | 	if err != nil { | ||
|  | 		s.appendError(err) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (s *handleState) appendTime(t time.Time) { | ||
|  | 	if s.h.json { | ||
|  | 		appendJSONTime(s, t) | ||
|  | 	} else { | ||
|  | 		writeTimeRFC3339Millis(s.buf, t) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // This takes half the time of Time.AppendFormat. | ||
|  | func writeTimeRFC3339Millis(buf *buffer.Buffer, t time.Time) { | ||
|  | 	year, month, day := t.Date() | ||
|  | 	buf.WritePosIntWidth(year, 4) | ||
|  | 	buf.WriteByte('-') | ||
|  | 	buf.WritePosIntWidth(int(month), 2) | ||
|  | 	buf.WriteByte('-') | ||
|  | 	buf.WritePosIntWidth(day, 2) | ||
|  | 	buf.WriteByte('T') | ||
|  | 	hour, min, sec := t.Clock() | ||
|  | 	buf.WritePosIntWidth(hour, 2) | ||
|  | 	buf.WriteByte(':') | ||
|  | 	buf.WritePosIntWidth(min, 2) | ||
|  | 	buf.WriteByte(':') | ||
|  | 	buf.WritePosIntWidth(sec, 2) | ||
|  | 	ns := t.Nanosecond() | ||
|  | 	buf.WriteByte('.') | ||
|  | 	buf.WritePosIntWidth(ns/1e6, 3) | ||
|  | 	_, offsetSeconds := t.Zone() | ||
|  | 	if offsetSeconds == 0 { | ||
|  | 		buf.WriteByte('Z') | ||
|  | 	} else { | ||
|  | 		offsetMinutes := offsetSeconds / 60 | ||
|  | 		if offsetMinutes < 0 { | ||
|  | 			buf.WriteByte('-') | ||
|  | 			offsetMinutes = -offsetMinutes | ||
|  | 		} else { | ||
|  | 			buf.WriteByte('+') | ||
|  | 		} | ||
|  | 		buf.WritePosIntWidth(offsetMinutes/60, 2) | ||
|  | 		buf.WriteByte(':') | ||
|  | 		buf.WritePosIntWidth(offsetMinutes%60, 2) | ||
|  | 	} | ||
|  | } |