// 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 ()// 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.//// Before implementing your own handler, consult https://go.dev/s/slog-handler-guide.typeHandlerinterface {// 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. // // [Logger] discards any errors from Handle. Wrap the Handle method to // process any errors from Handlers.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(ctx, level, msg, slog.Int("a", 1), slog.Int("b", 2)) // // should behave like // // logger.LogAttrs(ctx, 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// internal.DefaultOutput, except for testing output func(pc uintptr, data []byte) error}func newDefaultHandler( func(uintptr, []byte) error) *defaultHandler {return &defaultHandler{ch: &commonHandler{json: false},output: , }}func (*defaultHandler) ( context.Context, Level) bool {return >= logLoggerLevel.Level()}// 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 ( *defaultHandler) ( context.Context, Record) error { := buffer.New() .WriteString(.Level.String()) .WriteByte(' ') .WriteString(.Message) := .ch.newHandleState(, true, " ")defer .free() .appendNonBuiltIns()return .output(.PC, *)}func ( *defaultHandler) ( []Attr) Handler {return &defaultHandler{.ch.withAttrs(), .output}}func ( *defaultHandler) ( string) Handler {return &defaultHandler{.ch.withGroup(), .output}}// HandlerOptions are options for a [TextHandler] or [JSONHandler].// A zero HandlerOptions consists entirely of default values.typeHandlerOptionsstruct {// 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 a zero Attr, 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 *[Source].SourceKey = "source")type commonHandler struct { json bool// true => output JSON; false => output text opts HandlerOptions preformattedAttrs []byte// groupPrefix is for the text handler only. // It holds the prefix for groups that were already pre-formatted. // A group will appear here when a call to WithGroup is followed by // a call to WithAttrs. groupPrefix string groups []string// all groups started from WithGroup nOpenGroups int// the number of groups opened in preformattedAttrs mu *sync.Mutex w io.Writer}func ( *commonHandler) () *commonHandler {// We can't use assignment because we can't copy the mutex.return &commonHandler{json: .json,opts: .opts,preformattedAttrs: slices.Clip(.preformattedAttrs),groupPrefix: .groupPrefix,groups: slices.Clip(.groups),nOpenGroups: .nOpenGroups,w: .w,mu: .mu, // mutex shared among all clones of this handler }}// enabled reports whether l is greater than or equal to the// minimum level.func ( *commonHandler) ( Level) bool { := LevelInfoif .opts.Level != nil { = .opts.Level.Level() }return >= }func ( *commonHandler) ( []Attr) *commonHandler {// We are going to ignore empty groups, so if the entire slice consists of // them, there is nothing to do.ifcountEmptyGroups() == len() {return } := .clone()// Pre-format the attributes as an optimization. := .newHandleState((*buffer.Buffer)(&.preformattedAttrs), false, "")defer .free() .prefix.WriteString(.groupPrefix)if := .preformattedAttrs; len() > 0 { .sep = .attrSep()if .json && [len()-1] == '{' { .sep = "" } }// Remember the position in the buffer, in case all attrs are empty. := .buf.Len() .openGroups()if !.appendAttrs() { .buf.SetLen() } else {// Remember the new prefix for later keys. .groupPrefix = .prefix.String()// Remember how many opened groups are in preformattedAttrs, // so we don't open them again when we handle a Record. .nOpenGroups = len(.groups) }return}func ( *commonHandler) ( string) *commonHandler { := .clone() .groups = append(.groups, )return}// handle is the internal implementation of Handler.Handle// used by TextHandler and JSONHandler.func ( *commonHandler) ( Record) error { := .newHandleState(buffer.New(), true, "")defer .free()if .json { .buf.WriteByte('{') }// Built-in attributes. They are not in a group. := .groups .groups = nil// So ReplaceAttrs sees no groups instead of the pre groups. := .opts.ReplaceAttr// timeif !.Time.IsZero() { := TimeKey := .Time.Round(0) // strip monotonic to match Attr behaviorif == nil { .appendKey() .appendTime() } else { .appendAttr(Time(, )) } }// level := LevelKey := .Levelif == nil { .appendKey() .appendString(.String()) } else { .appendAttr(Any(, )) }// sourceif .opts.AddSource { := .Source()if == nil { = &Source{} } .appendAttr(Any(SourceKey, )) } = MessageKey := .Messageif == nil { .appendKey() .appendString() } else { .appendAttr(String(, )) } .groups = // Restore groups passed to ReplaceAttrs. .appendNonBuiltIns() .buf.WriteByte('\n') .mu.Lock()defer .mu.Unlock() , := .w.Write(*.buf)return}func ( *handleState) ( Record) {// preformatted Attrsif := .h.preformattedAttrs; len() > 0 { .buf.WriteString(.sep) .buf.Write() .sep = .h.attrSep()if .h.json && [len()-1] == '{' { .sep = "" } }// Attrs in Record -- unlike the built-in ones, they are in groups started // from WithGroup. // If the record has no Attrs, don't output any groups. := .h.nOpenGroupsif .NumAttrs() > 0 { .prefix.WriteString(.h.groupPrefix)// The group may turn out to be empty even though it has attrs (for // example, ReplaceAttr may delete all the attrs). // So remember where we are in the buffer, to restore the position // later if necessary. := .buf.Len() .openGroups() = len(.h.groups) := true .Attrs(func( Attr) bool {if .appendAttr() { = false }returntrue })if { .buf.SetLen() = .h.nOpenGroups } }if .h.json {// Close all open groups.forrange .h.groups[:] { .buf.WriteByte('}') }// Close the top-level object. .buf.WriteByte('}') }}// attrSep returns the separator between attributes.func ( *commonHandler) () string {if .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 { := make([]string, 0, 10)return &}}func ( *commonHandler) ( *buffer.Buffer, bool, string) handleState { := handleState{h: ,buf: ,freeBuf: ,sep: ,prefix: buffer.New(), }if .opts.ReplaceAttr != nil { .groups = groupPool.Get().(*[]string) *.groups = append(*.groups, .groups[:.nOpenGroups]...) }return}func ( *handleState) () {if .freeBuf { .buf.Free() }if := .groups; != nil { * = (*)[:0]groupPool.Put() } .prefix.Free()}func ( *handleState) () {for , := range .h.groups[.h.nOpenGroups:] { .openGroup() }}// Separator for group names and keys.const keyComponentSep = '.'// openGroup starts a new group of attributes// with the given name.func ( *handleState) ( string) {if .h.json { .appendKey() .buf.WriteByte('{') .sep = "" } else { .prefix.WriteString() .prefix.WriteByte(keyComponentSep) }// Collect group names for ReplaceAttr.if .groups != nil { *.groups = append(*.groups, ) }}// closeGroup ends the group with the given name.func ( *handleState) ( string) {if .h.json { .buf.WriteByte('}') } else { (*.prefix) = (*.prefix)[:len(*.prefix)-len()-1/* for keyComponentSep */] } .sep = .h.attrSep()if .groups != nil { *.groups = (*.groups)[:len(*.groups)-1] }}// appendAttrs appends the slice of Attrs.// It reports whether something was appended.func ( *handleState) ( []Attr) bool { := falsefor , := range {if .appendAttr() { = true } }return}// appendAttr appends the Attr's key and value.// It handles replacement and checking for an empty key.// It reports whether something was appended.func ( *handleState) ( Attr) bool { .Value = .Value.Resolve()if := .h.opts.ReplaceAttr; != nil && .Value.Kind() != KindGroup {var []stringif .groups != nil { = *.groups }// a.Value is resolved before calling ReplaceAttr, so the user doesn't have to. = (, )// The ReplaceAttr function may return an unresolved Attr. .Value = .Value.Resolve() }// Elide empty Attrs.if .isEmpty() {returnfalse }// Special case: Source.if := .Value; .Kind() == KindAny {if , := .Any().(*Source); {if .isEmpty() {returnfalse }if .h.json { .Value = .group() } else { .Value = StringValue(fmt.Sprintf("%s:%d", .File, .Line)) } } }if .Value.Kind() == KindGroup { := .Value.Group()// Output only non-empty groups.iflen() > 0 {// The group may turn out to be empty even though it has attrs (for // example, ReplaceAttr may delete all the attrs). // So remember where we are in the buffer, to restore the position // later if necessary. := .buf.Len()// Inline a group with an empty key.if .Key != "" { .openGroup(.Key) }if !.appendAttrs() { .buf.SetLen()returnfalse }if .Key != "" { .closeGroup(.Key) } } } else { .appendKey(.Key) .appendValue(.Value) }returntrue}func ( *handleState) ( error) { .appendString(fmt.Sprintf("!ERROR:%v", ))}func ( *handleState) ( string) { .buf.WriteString(.sep)if .prefix != nil && len(*.prefix) > 0 { .appendTwoStrings(string(*.prefix), ) } else { .appendString() }if .h.json { .buf.WriteByte(':') } else { .buf.WriteByte('=') } .sep = .h.attrSep()}// appendTwoStrings implements appendString(prefix + key), but faster.func ( *handleState) (, string) { := *.bufswitch {case .h.json: .WriteByte('"') = appendEscapedJSONString(, ) = appendEscapedJSONString(, ) .WriteByte('"')case !needsQuoting() && !needsQuoting(): .WriteString() .WriteString()default: = strconv.AppendQuote(, +) } *.buf = }func ( *handleState) ( string) {if .h.json { .buf.WriteByte('"') *.buf = appendEscapedJSONString(*.buf, ) .buf.WriteByte('"') } else {// textifneedsQuoting() { *.buf = strconv.AppendQuote(*.buf, ) } else { .buf.WriteString() } }}func ( *handleState) ( Value) {deferfunc() {if := recover(); != 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 := reflect.ValueOf(.any); .Kind() == reflect.Pointer && .IsNil() { .appendString("<nil>")return }// Otherwise just print the original panic message. .appendString(fmt.Sprintf("!PANIC: %v", )) } }()varerrorif .h.json { = appendJSONValue(, ) } else { = appendTextValue(, ) }if != nil { .appendError() }}func ( *handleState) ( time.Time) {if .h.json {appendJSONTime(, ) } else { *.buf = appendRFC3339Millis(*.buf, ) }}func appendRFC3339Millis( []byte, time.Time) []byte {// Format according to time.RFC3339Nano since it is highly optimized, // but truncate it to use millisecond resolution. // Unfortunately, that format trims trailing 0s, so add 1/10 millisecond // to guarantee that there are exactly 4 digits after the period.const = len("2006-01-02T15:04:05.000") := len() = .Truncate(time.Millisecond).Add(time.Millisecond / 10) = .AppendFormat(, time.RFC3339Nano) = append([:+], [++1:]...) // drop the 4th digitreturn}// DiscardHandler discards all log output.// DiscardHandler.Enabled returns false for all Levels.varDiscardHandlerHandler = discardHandler{}type discardHandler struct{}func ( discardHandler) (context.Context, Level) bool { returnfalse }func ( discardHandler) (context.Context, Record) error { returnnil }func ( discardHandler) ( []Attr) Handler { return }func ( discardHandler) ( string) Handler { return }
The pages are generated with Goldsv0.7.7-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.