Source file src/log/slog/text_handler.go

     1  // Copyright 2022 The Go Authors. 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  	"context"
     9  	"encoding"
    10  	"fmt"
    11  	"io"
    12  	"reflect"
    13  	"strconv"
    14  	"sync"
    15  	"unicode"
    16  	"unicode/utf8"
    17  )
    18  
    19  // TextHandler is a [Handler] that writes Records to an [io.Writer] as a
    20  // sequence of key=value pairs separated by spaces and followed by a newline.
    21  type TextHandler struct {
    22  	*commonHandler
    23  }
    24  
    25  // NewTextHandler creates a [TextHandler] that writes to w,
    26  // using the given options.
    27  // If opts is nil, the default options are used.
    28  func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
    29  	if opts == nil {
    30  		opts = &HandlerOptions{}
    31  	}
    32  	return &TextHandler{
    33  		&commonHandler{
    34  			json: false,
    35  			w:    w,
    36  			opts: *opts,
    37  			mu:   &sync.Mutex{},
    38  		},
    39  	}
    40  }
    41  
    42  // Enabled reports whether the handler handles records at the given level.
    43  // The handler ignores records whose level is lower.
    44  func (h *TextHandler) Enabled(_ context.Context, level Level) bool {
    45  	return h.commonHandler.enabled(level)
    46  }
    47  
    48  // WithAttrs returns a new [TextHandler] whose attributes consists
    49  // of h's attributes followed by attrs.
    50  func (h *TextHandler) WithAttrs(attrs []Attr) Handler {
    51  	return &TextHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
    52  }
    53  
    54  func (h *TextHandler) WithGroup(name string) Handler {
    55  	return &TextHandler{commonHandler: h.commonHandler.withGroup(name)}
    56  }
    57  
    58  // Handle formats its argument [Record] as a single line of space-separated
    59  // key=value items.
    60  //
    61  // If the Record's time is zero, the time is omitted.
    62  // Otherwise, the key is "time"
    63  // and the value is output in RFC3339 format with millisecond precision.
    64  //
    65  // If the Record's level is zero, the level is omitted.
    66  // Otherwise, the key is "level"
    67  // and the value of [Level.String] is output.
    68  //
    69  // If the AddSource option is set and source information is available,
    70  // the key is "source" and the value is output as FILE:LINE.
    71  //
    72  // The message's key is "msg".
    73  //
    74  // To modify these or other attributes, or remove them from the output, use
    75  // [HandlerOptions.ReplaceAttr].
    76  //
    77  // If a value implements [encoding.TextMarshaler], the result of MarshalText is
    78  // written. Otherwise, the result of [fmt.Sprint] is written.
    79  //
    80  // Keys and values are quoted with [strconv.Quote] if they contain Unicode space
    81  // characters, non-printing characters, '"' or '='.
    82  //
    83  // Keys inside groups consist of components (keys or group names) separated by
    84  // dots. No further escaping is performed.
    85  // Thus there is no way to determine from the key "a.b.c" whether there
    86  // are two groups "a" and "b" and a key "c", or a single group "a.b" and a key "c",
    87  // or single group "a" and a key "b.c".
    88  // If it is necessary to reconstruct the group structure of a key
    89  // even in the presence of dots inside components, use
    90  // [HandlerOptions.ReplaceAttr] to encode that information in the key.
    91  //
    92  // Each call to Handle results in a single serialized call to
    93  // io.Writer.Write.
    94  func (h *TextHandler) Handle(_ context.Context, r Record) error {
    95  	return h.commonHandler.handle(r)
    96  }
    97  
    98  func appendTextValue(s *handleState, v Value) error {
    99  	switch v.Kind() {
   100  	case KindString:
   101  		s.appendString(v.str())
   102  	case KindTime:
   103  		s.appendTime(v.time())
   104  	case KindAny:
   105  		if tm, ok := v.any.(encoding.TextMarshaler); ok {
   106  			data, err := tm.MarshalText()
   107  			if err != nil {
   108  				return err
   109  			}
   110  			// TODO: avoid the conversion to string.
   111  			s.appendString(string(data))
   112  			return nil
   113  		}
   114  		if bs, ok := byteSlice(v.any); ok {
   115  			// As of Go 1.19, this only allocates for strings longer than 32 bytes.
   116  			s.buf.WriteString(strconv.Quote(string(bs)))
   117  			return nil
   118  		}
   119  		s.appendString(fmt.Sprintf("%+v", v.Any()))
   120  	default:
   121  		*s.buf = v.append(*s.buf)
   122  	}
   123  	return nil
   124  }
   125  
   126  // byteSlice returns its argument as a []byte if the argument's
   127  // underlying type is []byte, along with a second return value of true.
   128  // Otherwise it returns nil, false.
   129  func byteSlice(a any) ([]byte, bool) {
   130  	if bs, ok := a.([]byte); ok {
   131  		return bs, true
   132  	}
   133  	// Like Printf's %s, we allow both the slice type and the byte element type to be named.
   134  	t := reflect.TypeOf(a)
   135  	if t != nil && t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
   136  		return reflect.ValueOf(a).Bytes(), true
   137  	}
   138  	return nil, false
   139  }
   140  
   141  func needsQuoting(s string) bool {
   142  	if len(s) == 0 {
   143  		return true
   144  	}
   145  	for i := 0; i < len(s); {
   146  		b := s[i]
   147  		if b < utf8.RuneSelf {
   148  			// Quote anything except a backslash that would need quoting in a
   149  			// JSON string, as well as space and '='
   150  			if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) {
   151  				return true
   152  			}
   153  			i++
   154  			continue
   155  		}
   156  		r, size := utf8.DecodeRuneInString(s[i:])
   157  		if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
   158  			return true
   159  		}
   160  		i += size
   161  	}
   162  	return false
   163  }
   164  

View as plain text