forked from toolshed/abra
		
	
		
			
				
	
	
		
			162 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			162 lines
		
	
	
		
			4.7 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"
 | |
| 	"encoding"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"reflect"
 | |
| 	"strconv"
 | |
| 	"unicode"
 | |
| 	"unicode/utf8"
 | |
| )
 | |
| 
 | |
| // TextHandler is a Handler that writes Records to an io.Writer as a
 | |
| // sequence of key=value pairs separated by spaces and followed by a newline.
 | |
| type TextHandler struct {
 | |
| 	*commonHandler
 | |
| }
 | |
| 
 | |
| // NewTextHandler creates a TextHandler that writes to w,
 | |
| // using the given options.
 | |
| // If opts is nil, the default options are used.
 | |
| func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
 | |
| 	if opts == nil {
 | |
| 		opts = &HandlerOptions{}
 | |
| 	}
 | |
| 	return &TextHandler{
 | |
| 		&commonHandler{
 | |
| 			json: false,
 | |
| 			w:    w,
 | |
| 			opts: *opts,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Enabled reports whether the handler handles records at the given level.
 | |
| // The handler ignores records whose level is lower.
 | |
| func (h *TextHandler) Enabled(_ context.Context, level Level) bool {
 | |
| 	return h.commonHandler.enabled(level)
 | |
| }
 | |
| 
 | |
| // WithAttrs returns a new TextHandler whose attributes consists
 | |
| // of h's attributes followed by attrs.
 | |
| func (h *TextHandler) WithAttrs(attrs []Attr) Handler {
 | |
| 	return &TextHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
 | |
| }
 | |
| 
 | |
| func (h *TextHandler) WithGroup(name string) Handler {
 | |
| 	return &TextHandler{commonHandler: h.commonHandler.withGroup(name)}
 | |
| }
 | |
| 
 | |
| // Handle formats its argument Record as a single line of space-separated
 | |
| // key=value items.
 | |
| //
 | |
| // If the Record's time is zero, the time is omitted.
 | |
| // Otherwise, the key is "time"
 | |
| // and the value is output in RFC3339 format with millisecond precision.
 | |
| //
 | |
| // If the Record's level is zero, the level is omitted.
 | |
| // Otherwise, the key is "level"
 | |
| // and the value of [Level.String] is output.
 | |
| //
 | |
| // If the AddSource option is set and source information is available,
 | |
| // the key is "source" and the value is output as FILE:LINE.
 | |
| //
 | |
| // The message's key is "msg".
 | |
| //
 | |
| // To modify these or other attributes, or remove them from the output, use
 | |
| // [HandlerOptions.ReplaceAttr].
 | |
| //
 | |
| // If a value implements [encoding.TextMarshaler], the result of MarshalText is
 | |
| // written. Otherwise, the result of fmt.Sprint is written.
 | |
| //
 | |
| // Keys and values are quoted with [strconv.Quote] if they contain Unicode space
 | |
| // characters, non-printing characters, '"' or '='.
 | |
| //
 | |
| // Keys inside groups consist of components (keys or group names) separated by
 | |
| // dots. No further escaping is performed.
 | |
| // Thus there is no way to determine from the key "a.b.c" whether there
 | |
| // are two groups "a" and "b" and a key "c", or a single group "a.b" and a key "c",
 | |
| // or single group "a" and a key "b.c".
 | |
| // If it is necessary to reconstruct the group structure of a key
 | |
| // even in the presence of dots inside components, use
 | |
| // [HandlerOptions.ReplaceAttr] to encode that information in the key.
 | |
| //
 | |
| // Each call to Handle results in a single serialized call to
 | |
| // io.Writer.Write.
 | |
| func (h *TextHandler) Handle(_ context.Context, r Record) error {
 | |
| 	return h.commonHandler.handle(r)
 | |
| }
 | |
| 
 | |
| func appendTextValue(s *handleState, v Value) error {
 | |
| 	switch v.Kind() {
 | |
| 	case KindString:
 | |
| 		s.appendString(v.str())
 | |
| 	case KindTime:
 | |
| 		s.appendTime(v.time())
 | |
| 	case KindAny:
 | |
| 		if tm, ok := v.any.(encoding.TextMarshaler); ok {
 | |
| 			data, err := tm.MarshalText()
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			// TODO: avoid the conversion to string.
 | |
| 			s.appendString(string(data))
 | |
| 			return nil
 | |
| 		}
 | |
| 		if bs, ok := byteSlice(v.any); ok {
 | |
| 			// As of Go 1.19, this only allocates for strings longer than 32 bytes.
 | |
| 			s.buf.WriteString(strconv.Quote(string(bs)))
 | |
| 			return nil
 | |
| 		}
 | |
| 		s.appendString(fmt.Sprintf("%+v", v.Any()))
 | |
| 	default:
 | |
| 		*s.buf = v.append(*s.buf)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // byteSlice returns its argument as a []byte if the argument's
 | |
| // underlying type is []byte, along with a second return value of true.
 | |
| // Otherwise it returns nil, false.
 | |
| func byteSlice(a any) ([]byte, bool) {
 | |
| 	if bs, ok := a.([]byte); ok {
 | |
| 		return bs, true
 | |
| 	}
 | |
| 	// Like Printf's %s, we allow both the slice type and the byte element type to be named.
 | |
| 	t := reflect.TypeOf(a)
 | |
| 	if t != nil && t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
 | |
| 		return reflect.ValueOf(a).Bytes(), true
 | |
| 	}
 | |
| 	return nil, false
 | |
| }
 | |
| 
 | |
| func needsQuoting(s string) bool {
 | |
| 	if len(s) == 0 {
 | |
| 		return true
 | |
| 	}
 | |
| 	for i := 0; i < len(s); {
 | |
| 		b := s[i]
 | |
| 		if b < utf8.RuneSelf {
 | |
| 			// Quote anything except a backslash that would need quoting in a
 | |
| 			// JSON string, as well as space and '='
 | |
| 			if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) {
 | |
| 				return true
 | |
| 			}
 | |
| 			i++
 | |
| 			continue
 | |
| 		}
 | |
| 		r, size := utf8.DecodeRuneInString(s[i:])
 | |
| 		if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
 | |
| 			return true
 | |
| 		}
 | |
| 		i += size
 | |
| 	}
 | |
| 	return false
 | |
| }
 |