console_encoder.go raw

   1  // Copyright (c) 2016 Uber Technologies, Inc.
   2  //
   3  // Permission is hereby granted, free of charge, to any person obtaining a copy
   4  // of this software and associated documentation files (the "Software"), to deal
   5  // in the Software without restriction, including without limitation the rights
   6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   7  // copies of the Software, and to permit persons to whom the Software is
   8  // furnished to do so, subject to the following conditions:
   9  //
  10  // The above copyright notice and this permission notice shall be included in
  11  // all copies or substantial portions of the Software.
  12  //
  13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19  // THE SOFTWARE.
  20  
  21  package zapcore
  22  
  23  import (
  24  	"fmt"
  25  
  26  	"go.uber.org/zap/buffer"
  27  	"go.uber.org/zap/internal/bufferpool"
  28  	"go.uber.org/zap/internal/pool"
  29  )
  30  
  31  var _sliceEncoderPool = pool.New(func() *sliceArrayEncoder {
  32  	return &sliceArrayEncoder{
  33  		elems: make([]interface{}, 0, 2),
  34  	}
  35  })
  36  
  37  func getSliceEncoder() *sliceArrayEncoder {
  38  	return _sliceEncoderPool.Get()
  39  }
  40  
  41  func putSliceEncoder(e *sliceArrayEncoder) {
  42  	e.elems = e.elems[:0]
  43  	_sliceEncoderPool.Put(e)
  44  }
  45  
  46  type consoleEncoder struct {
  47  	*jsonEncoder
  48  }
  49  
  50  // NewConsoleEncoder creates an encoder whose output is designed for human -
  51  // rather than machine - consumption. It serializes the core log entry data
  52  // (message, level, timestamp, etc.) in a plain-text format and leaves the
  53  // structured context as JSON.
  54  //
  55  // Note that although the console encoder doesn't use the keys specified in the
  56  // encoder configuration, it will omit any element whose key is set to the empty
  57  // string.
  58  func NewConsoleEncoder(cfg EncoderConfig) Encoder {
  59  	if cfg.ConsoleSeparator == "" {
  60  		// Use a default delimiter of '\t' for backwards compatibility
  61  		cfg.ConsoleSeparator = "\t"
  62  	}
  63  	return consoleEncoder{newJSONEncoder(cfg, true)}
  64  }
  65  
  66  func (c consoleEncoder) Clone() Encoder {
  67  	return consoleEncoder{c.jsonEncoder.Clone().(*jsonEncoder)}
  68  }
  69  
  70  func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) {
  71  	line := bufferpool.Get()
  72  
  73  	// We don't want the entry's metadata to be quoted and escaped (if it's
  74  	// encoded as strings), which means that we can't use the JSON encoder. The
  75  	// simplest option is to use the memory encoder and fmt.Fprint.
  76  	//
  77  	// If this ever becomes a performance bottleneck, we can implement
  78  	// ArrayEncoder for our plain-text format.
  79  	arr := getSliceEncoder()
  80  	if c.TimeKey != "" && c.EncodeTime != nil && !ent.Time.IsZero() {
  81  		c.EncodeTime(ent.Time, arr)
  82  	}
  83  	if c.LevelKey != "" && c.EncodeLevel != nil {
  84  		c.EncodeLevel(ent.Level, arr)
  85  	}
  86  	if ent.LoggerName != "" && c.NameKey != "" {
  87  		nameEncoder := c.EncodeName
  88  
  89  		if nameEncoder == nil {
  90  			// Fall back to FullNameEncoder for backward compatibility.
  91  			nameEncoder = FullNameEncoder
  92  		}
  93  
  94  		nameEncoder(ent.LoggerName, arr)
  95  	}
  96  	if ent.Caller.Defined {
  97  		if c.CallerKey != "" && c.EncodeCaller != nil {
  98  			c.EncodeCaller(ent.Caller, arr)
  99  		}
 100  		if c.FunctionKey != "" {
 101  			arr.AppendString(ent.Caller.Function)
 102  		}
 103  	}
 104  	for i := range arr.elems {
 105  		if i > 0 {
 106  			line.AppendString(c.ConsoleSeparator)
 107  		}
 108  		fmt.Fprint(line, arr.elems[i])
 109  	}
 110  	putSliceEncoder(arr)
 111  
 112  	// Add the message itself.
 113  	if c.MessageKey != "" {
 114  		c.addSeparatorIfNecessary(line)
 115  		line.AppendString(ent.Message)
 116  	}
 117  
 118  	// Add any structured context.
 119  	c.writeContext(line, fields)
 120  
 121  	// If there's no stacktrace key, honor that; this allows users to force
 122  	// single-line output.
 123  	if ent.Stack != "" && c.StacktraceKey != "" {
 124  		line.AppendByte('\n')
 125  		line.AppendString(ent.Stack)
 126  	}
 127  
 128  	line.AppendString(c.LineEnding)
 129  	return line, nil
 130  }
 131  
 132  func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []Field) {
 133  	context := c.jsonEncoder.Clone().(*jsonEncoder)
 134  	defer func() {
 135  		// putJSONEncoder assumes the buffer is still used, but we write out the buffer so
 136  		// we can free it.
 137  		context.buf.Free()
 138  		putJSONEncoder(context)
 139  	}()
 140  
 141  	addFields(context, extra)
 142  	context.closeOpenNamespaces()
 143  	if context.buf.Len() == 0 {
 144  		return
 145  	}
 146  
 147  	c.addSeparatorIfNecessary(line)
 148  	line.AppendByte('{')
 149  	line.Write(context.buf.Bytes())
 150  	line.AppendByte('}')
 151  }
 152  
 153  func (c consoleEncoder) addSeparatorIfNecessary(line *buffer.Buffer) {
 154  	if line.Len() > 0 {
 155  		line.AppendString(c.ConsoleSeparator)
 156  	}
 157  }
 158