123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804 |
- /*
- Copyright 2021 The logr Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- // Package funcr implements formatting of structured log messages and
- // optionally captures the call site and timestamp.
- //
- // The simplest way to use it is via its implementation of a
- // github.com/go-logr/logr.LogSink with output through an arbitrary
- // "write" function. See New and NewJSON for details.
- //
- // # Custom LogSinks
- //
- // For users who need more control, a funcr.Formatter can be embedded inside
- // your own custom LogSink implementation. This is useful when the LogSink
- // needs to implement additional methods, for example.
- //
- // # Formatting
- //
- // This will respect logr.Marshaler, fmt.Stringer, and error interfaces for
- // values which are being logged. When rendering a struct, funcr will use Go's
- // standard JSON tags (all except "string").
- package funcr
- import (
- "bytes"
- "encoding"
- "encoding/json"
- "fmt"
- "path/filepath"
- "reflect"
- "runtime"
- "strconv"
- "strings"
- "time"
- "github.com/go-logr/logr"
- )
- // New returns a logr.Logger which is implemented by an arbitrary function.
- func New(fn func(prefix, args string), opts Options) logr.Logger {
- return logr.New(newSink(fn, NewFormatter(opts)))
- }
- // NewJSON returns a logr.Logger which is implemented by an arbitrary function
- // and produces JSON output.
- func NewJSON(fn func(obj string), opts Options) logr.Logger {
- fnWrapper := func(_, obj string) {
- fn(obj)
- }
- return logr.New(newSink(fnWrapper, NewFormatterJSON(opts)))
- }
- // Underlier exposes access to the underlying logging function. Since
- // callers only have a logr.Logger, they have to know which
- // implementation is in use, so this interface is less of an
- // abstraction and more of a way to test type conversion.
- type Underlier interface {
- GetUnderlying() func(prefix, args string)
- }
- func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink {
- l := &fnlogger{
- Formatter: formatter,
- write: fn,
- }
- // For skipping fnlogger.Info and fnlogger.Error.
- l.Formatter.AddCallDepth(1)
- return l
- }
- // Options carries parameters which influence the way logs are generated.
- type Options struct {
- // LogCaller tells funcr to add a "caller" key to some or all log lines.
- // This has some overhead, so some users might not want it.
- LogCaller MessageClass
- // LogCallerFunc tells funcr to also log the calling function name. This
- // has no effect if caller logging is not enabled (see Options.LogCaller).
- LogCallerFunc bool
- // LogTimestamp tells funcr to add a "ts" key to log lines. This has some
- // overhead, so some users might not want it.
- LogTimestamp bool
- // TimestampFormat tells funcr how to render timestamps when LogTimestamp
- // is enabled. If not specified, a default format will be used. For more
- // details, see docs for Go's time.Layout.
- TimestampFormat string
- // Verbosity tells funcr which V logs to produce. Higher values enable
- // more logs. Info logs at or below this level will be written, while logs
- // above this level will be discarded.
- Verbosity int
- // RenderBuiltinsHook allows users to mutate the list of key-value pairs
- // while a log line is being rendered. The kvList argument follows logr
- // conventions - each pair of slice elements is comprised of a string key
- // and an arbitrary value (verified and sanitized before calling this
- // hook). The value returned must follow the same conventions. This hook
- // can be used to audit or modify logged data. For example, you might want
- // to prefix all of funcr's built-in keys with some string. This hook is
- // only called for built-in (provided by funcr itself) key-value pairs.
- // Equivalent hooks are offered for key-value pairs saved via
- // logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and
- // for user-provided pairs (see RenderArgsHook).
- RenderBuiltinsHook func(kvList []interface{}) []interface{}
- // RenderValuesHook is the same as RenderBuiltinsHook, except that it is
- // only called for key-value pairs saved via logr.Logger.WithValues. See
- // RenderBuiltinsHook for more details.
- RenderValuesHook func(kvList []interface{}) []interface{}
- // RenderArgsHook is the same as RenderBuiltinsHook, except that it is only
- // called for key-value pairs passed directly to Info and Error. See
- // RenderBuiltinsHook for more details.
- RenderArgsHook func(kvList []interface{}) []interface{}
- // MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct
- // that contains a struct, etc.) it may log. Every time it finds a struct,
- // slice, array, or map the depth is increased by one. When the maximum is
- // reached, the value will be converted to a string indicating that the max
- // depth has been exceeded. If this field is not specified, a default
- // value will be used.
- MaxLogDepth int
- }
- // MessageClass indicates which category or categories of messages to consider.
- type MessageClass int
- const (
- // None ignores all message classes.
- None MessageClass = iota
- // All considers all message classes.
- All
- // Info only considers info messages.
- Info
- // Error only considers error messages.
- Error
- )
- // fnlogger inherits some of its LogSink implementation from Formatter
- // and just needs to add some glue code.
- type fnlogger struct {
- Formatter
- write func(prefix, args string)
- }
- func (l fnlogger) WithName(name string) logr.LogSink {
- l.Formatter.AddName(name)
- return &l
- }
- func (l fnlogger) WithValues(kvList ...interface{}) logr.LogSink {
- l.Formatter.AddValues(kvList)
- return &l
- }
- func (l fnlogger) WithCallDepth(depth int) logr.LogSink {
- l.Formatter.AddCallDepth(depth)
- return &l
- }
- func (l fnlogger) Info(level int, msg string, kvList ...interface{}) {
- prefix, args := l.FormatInfo(level, msg, kvList)
- l.write(prefix, args)
- }
- func (l fnlogger) Error(err error, msg string, kvList ...interface{}) {
- prefix, args := l.FormatError(err, msg, kvList)
- l.write(prefix, args)
- }
- func (l fnlogger) GetUnderlying() func(prefix, args string) {
- return l.write
- }
- // Assert conformance to the interfaces.
- var _ logr.LogSink = &fnlogger{}
- var _ logr.CallDepthLogSink = &fnlogger{}
- var _ Underlier = &fnlogger{}
- // NewFormatter constructs a Formatter which emits a JSON-like key=value format.
- func NewFormatter(opts Options) Formatter {
- return newFormatter(opts, outputKeyValue)
- }
- // NewFormatterJSON constructs a Formatter which emits strict JSON.
- func NewFormatterJSON(opts Options) Formatter {
- return newFormatter(opts, outputJSON)
- }
- // Defaults for Options.
- const defaultTimestampFormat = "2006-01-02 15:04:05.000000"
- const defaultMaxLogDepth = 16
- func newFormatter(opts Options, outfmt outputFormat) Formatter {
- if opts.TimestampFormat == "" {
- opts.TimestampFormat = defaultTimestampFormat
- }
- if opts.MaxLogDepth == 0 {
- opts.MaxLogDepth = defaultMaxLogDepth
- }
- f := Formatter{
- outputFormat: outfmt,
- prefix: "",
- values: nil,
- depth: 0,
- opts: &opts,
- }
- return f
- }
- // Formatter is an opaque struct which can be embedded in a LogSink
- // implementation. It should be constructed with NewFormatter. Some of
- // its methods directly implement logr.LogSink.
- type Formatter struct {
- outputFormat outputFormat
- prefix string
- values []interface{}
- valuesStr string
- depth int
- opts *Options
- }
- // outputFormat indicates which outputFormat to use.
- type outputFormat int
- const (
- // outputKeyValue emits a JSON-like key=value format, but not strict JSON.
- outputKeyValue outputFormat = iota
- // outputJSON emits strict JSON.
- outputJSON
- )
- // PseudoStruct is a list of key-value pairs that gets logged as a struct.
- type PseudoStruct []interface{}
- // render produces a log line, ready to use.
- func (f Formatter) render(builtins, args []interface{}) string {
- // Empirically bytes.Buffer is faster than strings.Builder for this.
- buf := bytes.NewBuffer(make([]byte, 0, 1024))
- if f.outputFormat == outputJSON {
- buf.WriteByte('{')
- }
- vals := builtins
- if hook := f.opts.RenderBuiltinsHook; hook != nil {
- vals = hook(f.sanitize(vals))
- }
- f.flatten(buf, vals, false, false) // keys are ours, no need to escape
- continuing := len(builtins) > 0
- if len(f.valuesStr) > 0 {
- if continuing {
- if f.outputFormat == outputJSON {
- buf.WriteByte(',')
- } else {
- buf.WriteByte(' ')
- }
- }
- continuing = true
- buf.WriteString(f.valuesStr)
- }
- vals = args
- if hook := f.opts.RenderArgsHook; hook != nil {
- vals = hook(f.sanitize(vals))
- }
- f.flatten(buf, vals, continuing, true) // escape user-provided keys
- if f.outputFormat == outputJSON {
- buf.WriteByte('}')
- }
- return buf.String()
- }
- // flatten renders a list of key-value pairs into a buffer. If continuing is
- // true, it assumes that the buffer has previous values and will emit a
- // separator (which depends on the output format) before the first pair it
- // writes. If escapeKeys is true, the keys are assumed to have
- // non-JSON-compatible characters in them and must be evaluated for escapes.
- //
- // This function returns a potentially modified version of kvList, which
- // ensures that there is a value for every key (adding a value if needed) and
- // that each key is a string (substituting a key if needed).
- func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) []interface{} {
- // This logic overlaps with sanitize() but saves one type-cast per key,
- // which can be measurable.
- if len(kvList)%2 != 0 {
- kvList = append(kvList, noValue)
- }
- for i := 0; i < len(kvList); i += 2 {
- k, ok := kvList[i].(string)
- if !ok {
- k = f.nonStringKey(kvList[i])
- kvList[i] = k
- }
- v := kvList[i+1]
- if i > 0 || continuing {
- if f.outputFormat == outputJSON {
- buf.WriteByte(',')
- } else {
- // In theory the format could be something we don't understand. In
- // practice, we control it, so it won't be.
- buf.WriteByte(' ')
- }
- }
- if escapeKeys {
- buf.WriteString(prettyString(k))
- } else {
- // this is faster
- buf.WriteByte('"')
- buf.WriteString(k)
- buf.WriteByte('"')
- }
- if f.outputFormat == outputJSON {
- buf.WriteByte(':')
- } else {
- buf.WriteByte('=')
- }
- buf.WriteString(f.pretty(v))
- }
- return kvList
- }
- func (f Formatter) pretty(value interface{}) string {
- return f.prettyWithFlags(value, 0, 0)
- }
- const (
- flagRawStruct = 0x1 // do not print braces on structs
- )
- // TODO: This is not fast. Most of the overhead goes here.
- func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) string {
- if depth > f.opts.MaxLogDepth {
- return `"<max-log-depth-exceeded>"`
- }
- // Handle types that take full control of logging.
- if v, ok := value.(logr.Marshaler); ok {
- // Replace the value with what the type wants to get logged.
- // That then gets handled below via reflection.
- value = invokeMarshaler(v)
- }
- // Handle types that want to format themselves.
- switch v := value.(type) {
- case fmt.Stringer:
- value = invokeStringer(v)
- case error:
- value = invokeError(v)
- }
- // Handling the most common types without reflect is a small perf win.
- switch v := value.(type) {
- case bool:
- return strconv.FormatBool(v)
- case string:
- return prettyString(v)
- case int:
- return strconv.FormatInt(int64(v), 10)
- case int8:
- return strconv.FormatInt(int64(v), 10)
- case int16:
- return strconv.FormatInt(int64(v), 10)
- case int32:
- return strconv.FormatInt(int64(v), 10)
- case int64:
- return strconv.FormatInt(int64(v), 10)
- case uint:
- return strconv.FormatUint(uint64(v), 10)
- case uint8:
- return strconv.FormatUint(uint64(v), 10)
- case uint16:
- return strconv.FormatUint(uint64(v), 10)
- case uint32:
- return strconv.FormatUint(uint64(v), 10)
- case uint64:
- return strconv.FormatUint(v, 10)
- case uintptr:
- return strconv.FormatUint(uint64(v), 10)
- case float32:
- return strconv.FormatFloat(float64(v), 'f', -1, 32)
- case float64:
- return strconv.FormatFloat(v, 'f', -1, 64)
- case complex64:
- return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"`
- case complex128:
- return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"`
- case PseudoStruct:
- buf := bytes.NewBuffer(make([]byte, 0, 1024))
- v = f.sanitize(v)
- if flags&flagRawStruct == 0 {
- buf.WriteByte('{')
- }
- for i := 0; i < len(v); i += 2 {
- if i > 0 {
- buf.WriteByte(',')
- }
- k, _ := v[i].(string) // sanitize() above means no need to check success
- // arbitrary keys might need escaping
- buf.WriteString(prettyString(k))
- buf.WriteByte(':')
- buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
- }
- if flags&flagRawStruct == 0 {
- buf.WriteByte('}')
- }
- return buf.String()
- }
- buf := bytes.NewBuffer(make([]byte, 0, 256))
- t := reflect.TypeOf(value)
- if t == nil {
- return "null"
- }
- v := reflect.ValueOf(value)
- switch t.Kind() {
- case reflect.Bool:
- return strconv.FormatBool(v.Bool())
- case reflect.String:
- return prettyString(v.String())
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return strconv.FormatInt(int64(v.Int()), 10)
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return strconv.FormatUint(uint64(v.Uint()), 10)
- case reflect.Float32:
- return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32)
- case reflect.Float64:
- return strconv.FormatFloat(v.Float(), 'f', -1, 64)
- case reflect.Complex64:
- return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"`
- case reflect.Complex128:
- return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"`
- case reflect.Struct:
- if flags&flagRawStruct == 0 {
- buf.WriteByte('{')
- }
- printComma := false // testing i>0 is not enough because of JSON omitted fields
- for i := 0; i < t.NumField(); i++ {
- fld := t.Field(i)
- if fld.PkgPath != "" {
- // reflect says this field is only defined for non-exported fields.
- continue
- }
- if !v.Field(i).CanInterface() {
- // reflect isn't clear exactly what this means, but we can't use it.
- continue
- }
- name := ""
- omitempty := false
- if tag, found := fld.Tag.Lookup("json"); found {
- if tag == "-" {
- continue
- }
- if comma := strings.Index(tag, ","); comma != -1 {
- if n := tag[:comma]; n != "" {
- name = n
- }
- rest := tag[comma:]
- if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") {
- omitempty = true
- }
- } else {
- name = tag
- }
- }
- if omitempty && isEmpty(v.Field(i)) {
- continue
- }
- if printComma {
- buf.WriteByte(',')
- }
- printComma = true // if we got here, we are rendering a field
- if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
- buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1))
- continue
- }
- if name == "" {
- name = fld.Name
- }
- // field names can't contain characters which need escaping
- buf.WriteByte('"')
- buf.WriteString(name)
- buf.WriteByte('"')
- buf.WriteByte(':')
- buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
- }
- if flags&flagRawStruct == 0 {
- buf.WriteByte('}')
- }
- return buf.String()
- case reflect.Slice, reflect.Array:
- // If this is outputing as JSON make sure this isn't really a json.RawMessage.
- // If so just emit "as-is" and don't pretty it as that will just print
- // it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want.
- if f.outputFormat == outputJSON {
- if rm, ok := value.(json.RawMessage); ok {
- // If it's empty make sure we emit an empty value as the array style would below.
- if len(rm) > 0 {
- buf.Write(rm)
- } else {
- buf.WriteString("null")
- }
- return buf.String()
- }
- }
- buf.WriteByte('[')
- for i := 0; i < v.Len(); i++ {
- if i > 0 {
- buf.WriteByte(',')
- }
- e := v.Index(i)
- buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
- }
- buf.WriteByte(']')
- return buf.String()
- case reflect.Map:
- buf.WriteByte('{')
- // This does not sort the map keys, for best perf.
- it := v.MapRange()
- i := 0
- for it.Next() {
- if i > 0 {
- buf.WriteByte(',')
- }
- // If a map key supports TextMarshaler, use it.
- keystr := ""
- if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok {
- txt, err := m.MarshalText()
- if err != nil {
- keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error())
- } else {
- keystr = string(txt)
- }
- keystr = prettyString(keystr)
- } else {
- // prettyWithFlags will produce already-escaped values
- keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1)
- if t.Key().Kind() != reflect.String {
- // JSON only does string keys. Unlike Go's standard JSON, we'll
- // convert just about anything to a string.
- keystr = prettyString(keystr)
- }
- }
- buf.WriteString(keystr)
- buf.WriteByte(':')
- buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
- i++
- }
- buf.WriteByte('}')
- return buf.String()
- case reflect.Ptr, reflect.Interface:
- if v.IsNil() {
- return "null"
- }
- return f.prettyWithFlags(v.Elem().Interface(), 0, depth)
- }
- return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
- }
- func prettyString(s string) string {
- // Avoid escaping (which does allocations) if we can.
- if needsEscape(s) {
- return strconv.Quote(s)
- }
- b := bytes.NewBuffer(make([]byte, 0, 1024))
- b.WriteByte('"')
- b.WriteString(s)
- b.WriteByte('"')
- return b.String()
- }
- // needsEscape determines whether the input string needs to be escaped or not,
- // without doing any allocations.
- func needsEscape(s string) bool {
- for _, r := range s {
- if !strconv.IsPrint(r) || r == '\\' || r == '"' {
- return true
- }
- }
- return false
- }
- func isEmpty(v reflect.Value) bool {
- switch v.Kind() {
- case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
- return v.Len() == 0
- case reflect.Bool:
- return !v.Bool()
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- return v.Int() == 0
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- return v.Uint() == 0
- case reflect.Float32, reflect.Float64:
- return v.Float() == 0
- case reflect.Complex64, reflect.Complex128:
- return v.Complex() == 0
- case reflect.Interface, reflect.Ptr:
- return v.IsNil()
- }
- return false
- }
- func invokeMarshaler(m logr.Marshaler) (ret interface{}) {
- defer func() {
- if r := recover(); r != nil {
- ret = fmt.Sprintf("<panic: %s>", r)
- }
- }()
- return m.MarshalLog()
- }
- func invokeStringer(s fmt.Stringer) (ret string) {
- defer func() {
- if r := recover(); r != nil {
- ret = fmt.Sprintf("<panic: %s>", r)
- }
- }()
- return s.String()
- }
- func invokeError(e error) (ret string) {
- defer func() {
- if r := recover(); r != nil {
- ret = fmt.Sprintf("<panic: %s>", r)
- }
- }()
- return e.Error()
- }
- // Caller represents the original call site for a log line, after considering
- // logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and
- // Line fields will always be provided, while the Func field is optional.
- // Users can set the render hook fields in Options to examine logged key-value
- // pairs, one of which will be {"caller", Caller} if the Options.LogCaller
- // field is enabled for the given MessageClass.
- type Caller struct {
- // File is the basename of the file for this call site.
- File string `json:"file"`
- // Line is the line number in the file for this call site.
- Line int `json:"line"`
- // Func is the function name for this call site, or empty if
- // Options.LogCallerFunc is not enabled.
- Func string `json:"function,omitempty"`
- }
- func (f Formatter) caller() Caller {
- // +1 for this frame, +1 for Info/Error.
- pc, file, line, ok := runtime.Caller(f.depth + 2)
- if !ok {
- return Caller{"<unknown>", 0, ""}
- }
- fn := ""
- if f.opts.LogCallerFunc {
- if fp := runtime.FuncForPC(pc); fp != nil {
- fn = fp.Name()
- }
- }
- return Caller{filepath.Base(file), line, fn}
- }
- const noValue = "<no-value>"
- func (f Formatter) nonStringKey(v interface{}) string {
- return fmt.Sprintf("<non-string-key: %s>", f.snippet(v))
- }
- // snippet produces a short snippet string of an arbitrary value.
- func (f Formatter) snippet(v interface{}) string {
- const snipLen = 16
- snip := f.pretty(v)
- if len(snip) > snipLen {
- snip = snip[:snipLen]
- }
- return snip
- }
- // sanitize ensures that a list of key-value pairs has a value for every key
- // (adding a value if needed) and that each key is a string (substituting a key
- // if needed).
- func (f Formatter) sanitize(kvList []interface{}) []interface{} {
- if len(kvList)%2 != 0 {
- kvList = append(kvList, noValue)
- }
- for i := 0; i < len(kvList); i += 2 {
- _, ok := kvList[i].(string)
- if !ok {
- kvList[i] = f.nonStringKey(kvList[i])
- }
- }
- return kvList
- }
- // Init configures this Formatter from runtime info, such as the call depth
- // imposed by logr itself.
- // Note that this receiver is a pointer, so depth can be saved.
- func (f *Formatter) Init(info logr.RuntimeInfo) {
- f.depth += info.CallDepth
- }
- // Enabled checks whether an info message at the given level should be logged.
- func (f Formatter) Enabled(level int) bool {
- return level <= f.opts.Verbosity
- }
- // GetDepth returns the current depth of this Formatter. This is useful for
- // implementations which do their own caller attribution.
- func (f Formatter) GetDepth() int {
- return f.depth
- }
- // FormatInfo renders an Info log message into strings. The prefix will be
- // empty when no names were set (via AddNames), or when the output is
- // configured for JSON.
- func (f Formatter) FormatInfo(level int, msg string, kvList []interface{}) (prefix, argsStr string) {
- args := make([]interface{}, 0, 64) // using a constant here impacts perf
- prefix = f.prefix
- if f.outputFormat == outputJSON {
- args = append(args, "logger", prefix)
- prefix = ""
- }
- if f.opts.LogTimestamp {
- args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
- }
- if policy := f.opts.LogCaller; policy == All || policy == Info {
- args = append(args, "caller", f.caller())
- }
- args = append(args, "level", level, "msg", msg)
- return prefix, f.render(args, kvList)
- }
- // FormatError renders an Error log message into strings. The prefix will be
- // empty when no names were set (via AddNames), or when the output is
- // configured for JSON.
- func (f Formatter) FormatError(err error, msg string, kvList []interface{}) (prefix, argsStr string) {
- args := make([]interface{}, 0, 64) // using a constant here impacts perf
- prefix = f.prefix
- if f.outputFormat == outputJSON {
- args = append(args, "logger", prefix)
- prefix = ""
- }
- if f.opts.LogTimestamp {
- args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
- }
- if policy := f.opts.LogCaller; policy == All || policy == Error {
- args = append(args, "caller", f.caller())
- }
- args = append(args, "msg", msg)
- var loggableErr interface{}
- if err != nil {
- loggableErr = err.Error()
- }
- args = append(args, "error", loggableErr)
- return f.prefix, f.render(args, kvList)
- }
- // AddName appends the specified name. funcr uses '/' characters to separate
- // name elements. Callers should not pass '/' in the provided name string, but
- // this library does not actually enforce that.
- func (f *Formatter) AddName(name string) {
- if len(f.prefix) > 0 {
- f.prefix += "/"
- }
- f.prefix += name
- }
- // AddValues adds key-value pairs to the set of saved values to be logged with
- // each log line.
- func (f *Formatter) AddValues(kvList []interface{}) {
- // Three slice args forces a copy.
- n := len(f.values)
- f.values = append(f.values[:n:n], kvList...)
- vals := f.values
- if hook := f.opts.RenderValuesHook; hook != nil {
- vals = hook(f.sanitize(vals))
- }
- // Pre-render values, so we don't have to do it on each Info/Error call.
- buf := bytes.NewBuffer(make([]byte, 0, 1024))
- f.flatten(buf, vals, false, true) // escape user-provided keys
- f.valuesStr = buf.String()
- }
- // AddCallDepth increases the number of stack-frames to skip when attributing
- // the log line to a file and line.
- func (f *Formatter) AddCallDepth(depth int) {
- f.depth += depth
- }
|