github.com/Laisky/zap@v1.27.0/zapcore/json_encoder_test.go (about)

     1  // Copyright (c) 2018 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_test
    22  
    23  import (
    24  	"io"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/stretchr/testify/assert"
    29  
    30  	"github.com/Laisky/zap"
    31  	"github.com/Laisky/zap/zapcore"
    32  )
    33  
    34  // TestJSONEncodeEntry is an more "integrated" test that makes it easier to get
    35  // coverage on the json encoder (e.g. putJSONEncoder, let alone EncodeEntry
    36  // itself) than the tests in json_encoder_impl_test.go; it needs to be in the
    37  // zapcore_test package, so that it can import the toplevel zap package for
    38  // field constructor convenience.
    39  func TestJSONEncodeEntry(t *testing.T) {
    40  	type bar struct {
    41  		Key string  `json:"key"`
    42  		Val float64 `json:"val"`
    43  	}
    44  
    45  	type foo struct {
    46  		A string  `json:"aee"`
    47  		B int     `json:"bee"`
    48  		C float64 `json:"cee"`
    49  		D []bar   `json:"dee"`
    50  	}
    51  
    52  	tests := []struct {
    53  		desc     string
    54  		expected string
    55  		ent      zapcore.Entry
    56  		fields   []zapcore.Field
    57  	}{
    58  		{
    59  			desc: "info entry with some fields",
    60  			expected: `{
    61  				"L": "info",
    62  				"T": "2018-06-19T16:33:42.000Z",
    63  				"N": "bob",
    64  				"M": "lob law",
    65  				"so": "passes",
    66  				"answer": 42,
    67  				"a_float32": 2.71,
    68  				"common_pie": 3.14,
    69  				"complex_value": "3.14-2.71i",
    70  				"null_value": null,
    71  				"array_with_null_elements": [{}, null, null, 2],
    72  				"such": {
    73  					"aee": "lol",
    74  					"bee": 123,
    75  					"cee": 0.9999,
    76  					"dee": [
    77  						{"key": "pi", "val": 3.141592653589793},
    78  						{"key": "tau", "val": 6.283185307179586}
    79  					]
    80  				}
    81  			}`,
    82  			ent: zapcore.Entry{
    83  				Level:      zapcore.InfoLevel,
    84  				Time:       time.Date(2018, 6, 19, 16, 33, 42, 99, time.UTC),
    85  				LoggerName: "bob",
    86  				Message:    "lob law",
    87  			},
    88  			fields: []zapcore.Field{
    89  				zap.String("so", "passes"),
    90  				zap.Int("answer", 42),
    91  				zap.Float64("common_pie", 3.14),
    92  				zap.Float32("a_float32", 2.71),
    93  				zap.Complex128("complex_value", 3.14-2.71i),
    94  				// Cover special-cased handling of nil in AddReflect() and
    95  				// AppendReflect(). Note that for the latter, we explicitly test
    96  				// correct results for both the nil static interface{} value
    97  				// (`nil`), as well as the non-nil interface value with a
    98  				// dynamic type and nil value (`(*struct{})(nil)`).
    99  				zap.Reflect("null_value", nil),
   100  				zap.Reflect("array_with_null_elements", []interface{}{&struct{}{}, nil, (*struct{})(nil), 2}),
   101  				zap.Reflect("such", foo{
   102  					A: "lol",
   103  					B: 123,
   104  					C: 0.9999,
   105  					D: []bar{
   106  						{"pi", 3.141592653589793},
   107  						{"tau", 6.283185307179586},
   108  					},
   109  				}),
   110  			},
   111  		},
   112  		{
   113  			desc: "zero_time_omitted",
   114  			expected: `{
   115  				"L": "info",
   116  				"N": "name",
   117  				"M": "message"
   118  			}`,
   119  			ent: zapcore.Entry{
   120  				Level:      zapcore.InfoLevel,
   121  				Time:       time.Time{},
   122  				LoggerName: "name",
   123  				Message:    "message",
   124  			},
   125  		},
   126  	}
   127  
   128  	enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{
   129  		MessageKey:     "M",
   130  		LevelKey:       "L",
   131  		TimeKey:        "T",
   132  		NameKey:        "N",
   133  		CallerKey:      "C",
   134  		FunctionKey:    "F",
   135  		StacktraceKey:  "S",
   136  		EncodeLevel:    zapcore.LowercaseLevelEncoder,
   137  		EncodeTime:     zapcore.ISO8601TimeEncoder,
   138  		EncodeDuration: zapcore.SecondsDurationEncoder,
   139  		EncodeCaller:   zapcore.ShortCallerEncoder,
   140  	})
   141  
   142  	for _, tt := range tests {
   143  		t.Run(tt.desc, func(t *testing.T) {
   144  			buf, err := enc.EncodeEntry(tt.ent, tt.fields)
   145  			if assert.NoError(t, err, "Unexpected JSON encoding error.") {
   146  				assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.")
   147  			}
   148  			buf.Free()
   149  		})
   150  	}
   151  }
   152  
   153  func TestNoEncodeLevelSupplied(t *testing.T) {
   154  	enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{
   155  		MessageKey:     "M",
   156  		LevelKey:       "L",
   157  		TimeKey:        "T",
   158  		NameKey:        "N",
   159  		CallerKey:      "C",
   160  		FunctionKey:    "F",
   161  		StacktraceKey:  "S",
   162  		EncodeTime:     zapcore.ISO8601TimeEncoder,
   163  		EncodeDuration: zapcore.SecondsDurationEncoder,
   164  		EncodeCaller:   zapcore.ShortCallerEncoder,
   165  	})
   166  
   167  	ent := zapcore.Entry{
   168  		Level:      zapcore.InfoLevel,
   169  		Time:       time.Date(2018, 6, 19, 16, 33, 42, 99, time.UTC),
   170  		LoggerName: "bob",
   171  		Message:    "lob law",
   172  	}
   173  
   174  	fields := []zapcore.Field{
   175  		zap.Int("answer", 42),
   176  	}
   177  
   178  	_, err := enc.EncodeEntry(ent, fields)
   179  	assert.NoError(t, err, "Unexpected JSON encoding error.")
   180  }
   181  
   182  func TestJSONEmptyConfig(t *testing.T) {
   183  	tests := []struct {
   184  		name     string
   185  		field    zapcore.Field
   186  		expected string
   187  	}{
   188  		{
   189  			name:     "time",
   190  			field:    zap.Time("foo", time.Unix(1591287718, 0)), // 2020-06-04 09:21:58 -0700 PDT
   191  			expected: `{"foo": 1591287718000000000}`,
   192  		},
   193  		{
   194  			name:     "duration",
   195  			field:    zap.Duration("bar", time.Microsecond),
   196  			expected: `{"bar": 1000}`,
   197  		},
   198  	}
   199  
   200  	for _, tt := range tests {
   201  		t.Run(tt.name, func(t *testing.T) {
   202  			enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{})
   203  
   204  			buf, err := enc.EncodeEntry(zapcore.Entry{
   205  				Level:      zapcore.DebugLevel,
   206  				Time:       time.Now(),
   207  				LoggerName: "mylogger",
   208  				Message:    "things happened",
   209  			}, []zapcore.Field{tt.field})
   210  			if assert.NoError(t, err, "Unexpected JSON encoding error.") {
   211  				assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.")
   212  			}
   213  
   214  			buf.Free()
   215  		})
   216  	}
   217  }
   218  
   219  // Encodes any object into empty json '{}'
   220  type emptyReflectedEncoder struct {
   221  	writer io.Writer
   222  }
   223  
   224  func (enc *emptyReflectedEncoder) Encode(obj interface{}) error {
   225  	_, err := enc.writer.Write([]byte("{}"))
   226  	return err
   227  }
   228  
   229  func TestJSONCustomReflectedEncoder(t *testing.T) {
   230  	tests := []struct {
   231  		name     string
   232  		field    zapcore.Field
   233  		expected string
   234  	}{
   235  		{
   236  			name: "encode custom map object",
   237  			field: zapcore.Field{
   238  				Key:  "data",
   239  				Type: zapcore.ReflectType,
   240  				Interface: map[string]interface{}{
   241  					"foo": "hello",
   242  					"bar": 1111,
   243  				},
   244  			},
   245  			expected: `{"data":{}}`,
   246  		},
   247  		{
   248  			name: "encode nil object",
   249  			field: zapcore.Field{
   250  				Key:  "data",
   251  				Type: zapcore.ReflectType,
   252  			},
   253  			expected: `{"data":null}`,
   254  		},
   255  	}
   256  
   257  	for _, tt := range tests {
   258  		tt := tt
   259  		t.Run(tt.name, func(t *testing.T) {
   260  			t.Parallel()
   261  
   262  			enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{
   263  				NewReflectedEncoder: func(writer io.Writer) zapcore.ReflectedEncoder {
   264  					return &emptyReflectedEncoder{
   265  						writer: writer,
   266  					}
   267  				},
   268  			})
   269  
   270  			buf, err := enc.EncodeEntry(zapcore.Entry{
   271  				Level:      zapcore.DebugLevel,
   272  				Time:       time.Now(),
   273  				LoggerName: "logger",
   274  				Message:    "things happened",
   275  			}, []zapcore.Field{tt.field})
   276  			if assert.NoError(t, err, "Unexpected JSON encoding error.") {
   277  				assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.")
   278  			}
   279  			buf.Free()
   280  		})
   281  	}
   282  }