// 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 slogimport ()// JSONHandler is a [Handler] that writes Records to an [io.Writer] as// line-delimited JSON objects.typeJSONHandlerstruct { *commonHandler}// NewJSONHandler creates a [JSONHandler] that writes to w,// using the given options.// If opts is nil, the default options are used.func ( io.Writer, *HandlerOptions) *JSONHandler {if == nil { = &HandlerOptions{} }return &JSONHandler{ &commonHandler{json: true,w: ,opts: *,mu: &sync.Mutex{}, }, }}// Enabled reports whether the handler handles records at the given level.// The handler ignores records whose level is lower.func ( *JSONHandler) ( context.Context, Level) bool {return .commonHandler.enabled()}// WithAttrs returns a new [JSONHandler] whose attributes consists// of h's attributes followed by attrs.func ( *JSONHandler) ( []Attr) Handler {return &JSONHandler{commonHandler: .commonHandler.withAttrs()}}func ( *JSONHandler) ( string) Handler {return &JSONHandler{commonHandler: .commonHandler.withGroup()}}// Handle formats its argument [Record] as a JSON object on a single line.//// If the Record's time is zero, the time is omitted.// Otherwise, the key is "time"// and the value is output as with json.Marshal.//// 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 a record of type [Source].//// The message's key is "msg".//// To modify these or other attributes, or remove them from the output, use// [HandlerOptions.ReplaceAttr].//// Values are formatted as with an [encoding/json.Encoder] with SetEscapeHTML(false),// with two exceptions.//// First, an Attr whose Value is of type error is formatted as a string, by// calling its Error method. Only errors in Attrs receive this special treatment,// not errors embedded in structs, slices, maps or other data structures that// are processed by the [encoding/json] package.//// Second, an encoding failure does not cause Handle to return an error.// Instead, the error message is formatted as a string.//// Each call to Handle results in a single serialized call to io.Writer.Write.func ( *JSONHandler) ( context.Context, Record) error {return .commonHandler.handle()}// Adapted from time.Time.MarshalJSON to avoid allocation.func appendJSONTime( *handleState, time.Time) {if := .Year(); < 0 || >= 10000 {// RFC 3339 is clear that years are 4 digits exactly. // See golang.org/issue/4556#c15 for more discussion. .appendError(errors.New("time.Time year outside of range [0,9999]")) } .buf.WriteByte('"') *.buf = .AppendFormat(*.buf, time.RFC3339Nano) .buf.WriteByte('"')}func appendJSONValue( *handleState, Value) error {switch .Kind() {caseKindString: .appendString(.str())caseKindInt64: *.buf = strconv.AppendInt(*.buf, .Int64(), 10)caseKindUint64: *.buf = strconv.AppendUint(*.buf, .Uint64(), 10)caseKindFloat64:// json.Marshal is funny about floats; it doesn't // always match strconv.AppendFloat. So just call it. // That's expensive, but floats are rare.if := appendJSONMarshal(.buf, .Float64()); != nil {return }caseKindBool: *.buf = strconv.AppendBool(*.buf, .Bool())caseKindDuration:// Do what json.Marshal does. *.buf = strconv.AppendInt(*.buf, int64(.Duration()), 10)caseKindTime: .appendTime(.Time())caseKindAny: := .Any() , := .(json.Marshaler)if , := .(error); && ! { .appendString(.Error()) } else {returnappendJSONMarshal(.buf, ) }default:panic(fmt.Sprintf("bad kind: %s", .Kind())) }returnnil}func appendJSONMarshal( *buffer.Buffer, any) error {// Use a json.Encoder to avoid escaping HTML.varbytes.Buffer := json.NewEncoder(&) .SetEscapeHTML(false)if := .Encode(); != nil {return } := .Bytes() .Write([:len()-1]) // remove final newlinereturnnil}// appendEscapedJSONString escapes s for JSON and appends it to buf.// It does not surround the string in quotation marks.//// Modified from encoding/json/encode.go:encodeState.string,// with escapeHTML set to false.func appendEscapedJSONString( []byte, string) []byte { := func( byte) { = append(, ) } := func( string) { = append(, ...) } := 0for := 0; < len(); {if := []; < utf8.RuneSelf {ifsafeSet[] { ++continue }if < { ([:]) } ('\\')switch {case'\\', '"': ()case'\n': ('n')case'\r': ('r')case'\t': ('t')default:// This encodes bytes < 0x20 except for \t, \n and \r. (`u00`) (hex[>>4]) (hex[&0xF]) } ++ = continue } , := utf8.DecodeRuneInString([:])if == utf8.RuneError && == 1 {if < { ([:]) } (`\ufffd`) += = continue }// U+2028 is LINE SEPARATOR. // U+2029 is PARAGRAPH SEPARATOR. // They are both technically valid characters in JSON strings, // but don't work in JSONP, which has to be evaluated as JavaScript, // and can lead to security holes there. It is valid JSON to // escape them, so we do so unconditionally. // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.if == '\u2028' || == '\u2029' {if < { ([:]) } (`\u202`) (hex[&0xF]) += = continue } += }if < len() { ([:]) }return}const hex = "0123456789abcdef"// Copied from encoding/json/tables.go.//// safeSet holds the value true if the ASCII character with the given array// position can be represented inside a JSON string without any further// escaping.//// All values are true except for the ASCII control characters (0-31), the// double quote ("), and the backslash character ("\").var safeSet = [utf8.RuneSelf]bool{' ': true,'!': true,'"': false,'#': true,'$': true,'%': true,'&': true,'\'': true,'(': true,')': true,'*': true,'+': true,',': true,'-': true,'.': true,'/': true,'0': true,'1': true,'2': true,'3': true,'4': true,'5': true,'6': true,'7': true,'8': true,'9': true,':': true,';': true,'<': true,'=': true,'>': true,'?': true,'@': true,'A': true,'B': true,'C': true,'D': true,'E': true,'F': true,'G': true,'H': true,'I': true,'J': true,'K': true,'L': true,'M': true,'N': true,'O': true,'P': true,'Q': true,'R': true,'S': true,'T': true,'U': true,'V': true,'W': true,'X': true,'Y': true,'Z': true,'[': true,'\\': false,']': true,'^': true,'_': true,'`': true,'a': true,'b': true,'c': true,'d': true,'e': true,'f': true,'g': true,'h': true,'i': true,'j': true,'k': true,'l': true,'m': true,'n': true,'o': true,'p': true,'q': true,'r': true,'s': true,'t': true,'u': true,'v': true,'w': true,'x': true,'y': true,'z': true,'{': true,'|': true,'}': true,'~': true,'\u007f': true,}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.