...

Source file src/go.uber.org/zap/zapcore/encoder_test.go

Documentation: go.uber.org/zap/zapcore

     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_test
    22  
    23  import (
    24  	"encoding/json"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  	"gopkg.in/yaml.v3"
    32  
    33  	//revive:disable:dot-imports
    34  	. "go.uber.org/zap/zapcore"
    35  )
    36  
    37  var (
    38  	_epoch     = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
    39  	_testEntry = Entry{
    40  		LoggerName: "main",
    41  		Level:      InfoLevel,
    42  		Message:    `hello`,
    43  		Time:       _epoch,
    44  		Stack:      "fake-stack",
    45  		Caller:     EntryCaller{Defined: true, File: "foo.go", Line: 42, Function: "foo.Foo"},
    46  	}
    47  )
    48  
    49  func testEncoderConfig() EncoderConfig {
    50  	return EncoderConfig{
    51  		MessageKey:     "msg",
    52  		LevelKey:       "level",
    53  		NameKey:        "name",
    54  		TimeKey:        "ts",
    55  		CallerKey:      "caller",
    56  		FunctionKey:    "func",
    57  		StacktraceKey:  "stacktrace",
    58  		LineEnding:     "\n",
    59  		EncodeTime:     EpochTimeEncoder,
    60  		EncodeLevel:    LowercaseLevelEncoder,
    61  		EncodeDuration: SecondsDurationEncoder,
    62  		EncodeCaller:   ShortCallerEncoder,
    63  	}
    64  }
    65  
    66  func humanEncoderConfig() EncoderConfig {
    67  	cfg := testEncoderConfig()
    68  	cfg.EncodeTime = ISO8601TimeEncoder
    69  	cfg.EncodeLevel = CapitalLevelEncoder
    70  	cfg.EncodeDuration = StringDurationEncoder
    71  	return cfg
    72  }
    73  
    74  func capitalNameEncoder(loggerName string, enc PrimitiveArrayEncoder) {
    75  	enc.AppendString(strings.ToUpper(loggerName))
    76  }
    77  
    78  func TestEncoderConfiguration(t *testing.T) {
    79  	base := testEncoderConfig()
    80  
    81  	tests := []struct {
    82  		desc            string
    83  		cfg             EncoderConfig
    84  		amendEntry      func(Entry) Entry
    85  		extra           func(Encoder)
    86  		expectedJSON    string
    87  		expectedConsole string
    88  	}{
    89  		{
    90  			desc: "messages to be escaped",
    91  			cfg:  base,
    92  			amendEntry: func(ent Entry) Entry {
    93  				ent.Message = `hello\`
    94  				return ent
    95  			},
    96  			expectedJSON:    `{"level":"info","ts":0,"name":"main","caller":"foo.go:42","func":"foo.Foo","msg":"hello\\","stacktrace":"fake-stack"}` + "\n",
    97  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\\\nfake-stack\n",
    98  		},
    99  		{
   100  			desc: "use custom entry keys in JSON output and ignore them in console output",
   101  			cfg: EncoderConfig{
   102  				LevelKey:       "L",
   103  				TimeKey:        "T",
   104  				MessageKey:     "M",
   105  				NameKey:        "N",
   106  				CallerKey:      "C",
   107  				FunctionKey:    "F",
   108  				StacktraceKey:  "S",
   109  				LineEnding:     base.LineEnding,
   110  				EncodeTime:     base.EncodeTime,
   111  				EncodeDuration: base.EncodeDuration,
   112  				EncodeLevel:    base.EncodeLevel,
   113  				EncodeCaller:   base.EncodeCaller,
   114  			},
   115  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   116  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n",
   117  		},
   118  		{
   119  			desc: "skip line ending if SkipLineEnding is 'true'",
   120  			cfg: EncoderConfig{
   121  				LevelKey:       "L",
   122  				TimeKey:        "T",
   123  				MessageKey:     "M",
   124  				NameKey:        "N",
   125  				CallerKey:      "C",
   126  				FunctionKey:    "F",
   127  				StacktraceKey:  "S",
   128  				LineEnding:     base.LineEnding,
   129  				SkipLineEnding: true,
   130  				EncodeTime:     base.EncodeTime,
   131  				EncodeDuration: base.EncodeDuration,
   132  				EncodeLevel:    base.EncodeLevel,
   133  				EncodeCaller:   base.EncodeCaller,
   134  			},
   135  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}`,
   136  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack",
   137  		},
   138  		{
   139  			desc: "skip level if LevelKey is omitted",
   140  			cfg: EncoderConfig{
   141  				LevelKey:       OmitKey,
   142  				TimeKey:        "T",
   143  				MessageKey:     "M",
   144  				NameKey:        "N",
   145  				CallerKey:      "C",
   146  				FunctionKey:    "F",
   147  				StacktraceKey:  "S",
   148  				LineEnding:     base.LineEnding,
   149  				EncodeTime:     base.EncodeTime,
   150  				EncodeDuration: base.EncodeDuration,
   151  				EncodeLevel:    base.EncodeLevel,
   152  				EncodeCaller:   base.EncodeCaller,
   153  			},
   154  			expectedJSON:    `{"T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   155  			expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n",
   156  		},
   157  		{
   158  			desc: "skip timestamp if TimeKey is omitted",
   159  			cfg: EncoderConfig{
   160  				LevelKey:       "L",
   161  				TimeKey:        OmitKey,
   162  				MessageKey:     "M",
   163  				NameKey:        "N",
   164  				CallerKey:      "C",
   165  				FunctionKey:    "F",
   166  				StacktraceKey:  "S",
   167  				LineEnding:     base.LineEnding,
   168  				EncodeTime:     base.EncodeTime,
   169  				EncodeDuration: base.EncodeDuration,
   170  				EncodeLevel:    base.EncodeLevel,
   171  				EncodeCaller:   base.EncodeCaller,
   172  			},
   173  			expectedJSON:    `{"L":"info","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   174  			expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n",
   175  		},
   176  		{
   177  			desc: "skip message if MessageKey is omitted",
   178  			cfg: EncoderConfig{
   179  				LevelKey:       "L",
   180  				TimeKey:        "T",
   181  				MessageKey:     OmitKey,
   182  				NameKey:        "N",
   183  				CallerKey:      "C",
   184  				FunctionKey:    "F",
   185  				StacktraceKey:  "S",
   186  				LineEnding:     base.LineEnding,
   187  				EncodeTime:     base.EncodeTime,
   188  				EncodeDuration: base.EncodeDuration,
   189  				EncodeLevel:    base.EncodeLevel,
   190  				EncodeCaller:   base.EncodeCaller,
   191  			},
   192  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","S":"fake-stack"}` + "\n",
   193  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\nfake-stack\n",
   194  		},
   195  		{
   196  			desc: "skip name if NameKey is omitted",
   197  			cfg: EncoderConfig{
   198  				LevelKey:       "L",
   199  				TimeKey:        "T",
   200  				MessageKey:     "M",
   201  				NameKey:        OmitKey,
   202  				CallerKey:      "C",
   203  				FunctionKey:    "F",
   204  				StacktraceKey:  "S",
   205  				LineEnding:     base.LineEnding,
   206  				EncodeTime:     base.EncodeTime,
   207  				EncodeDuration: base.EncodeDuration,
   208  				EncodeLevel:    base.EncodeLevel,
   209  				EncodeCaller:   base.EncodeCaller,
   210  			},
   211  			expectedJSON:    `{"L":"info","T":0,"C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   212  			expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n",
   213  		},
   214  		{
   215  			desc: "skip caller if CallerKey is omitted",
   216  			cfg: EncoderConfig{
   217  				LevelKey:       "L",
   218  				TimeKey:        "T",
   219  				MessageKey:     "M",
   220  				NameKey:        "N",
   221  				CallerKey:      OmitKey,
   222  				FunctionKey:    "F",
   223  				StacktraceKey:  "S",
   224  				LineEnding:     base.LineEnding,
   225  				EncodeTime:     base.EncodeTime,
   226  				EncodeDuration: base.EncodeDuration,
   227  				EncodeLevel:    base.EncodeLevel,
   228  				EncodeCaller:   base.EncodeCaller,
   229  			},
   230  			expectedJSON:    `{"L":"info","T":0,"N":"main","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   231  			expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n",
   232  		},
   233  		{
   234  			desc: "skip function if FunctionKey is omitted",
   235  			cfg: EncoderConfig{
   236  				LevelKey:       "L",
   237  				TimeKey:        "T",
   238  				MessageKey:     "M",
   239  				NameKey:        "N",
   240  				CallerKey:      "C",
   241  				FunctionKey:    OmitKey,
   242  				StacktraceKey:  "S",
   243  				LineEnding:     base.LineEnding,
   244  				EncodeTime:     base.EncodeTime,
   245  				EncodeDuration: base.EncodeDuration,
   246  				EncodeLevel:    base.EncodeLevel,
   247  				EncodeCaller:   base.EncodeCaller,
   248  			},
   249  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n",
   250  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack\n",
   251  		},
   252  		{
   253  			desc: "skip stacktrace if StacktraceKey is omitted",
   254  			cfg: EncoderConfig{
   255  				LevelKey:       "L",
   256  				TimeKey:        "T",
   257  				MessageKey:     "M",
   258  				NameKey:        "N",
   259  				CallerKey:      "C",
   260  				FunctionKey:    "F",
   261  				StacktraceKey:  OmitKey,
   262  				LineEnding:     base.LineEnding,
   263  				EncodeTime:     base.EncodeTime,
   264  				EncodeDuration: base.EncodeDuration,
   265  				EncodeLevel:    base.EncodeLevel,
   266  				EncodeCaller:   base.EncodeCaller,
   267  			},
   268  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello"}` + "\n",
   269  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\n",
   270  		},
   271  		{
   272  			desc: "use the supplied EncodeTime, for both the entry and any times added",
   273  			cfg: EncoderConfig{
   274  				LevelKey:       "L",
   275  				TimeKey:        "T",
   276  				MessageKey:     "M",
   277  				NameKey:        "N",
   278  				CallerKey:      "C",
   279  				FunctionKey:    "F",
   280  				StacktraceKey:  "S",
   281  				LineEnding:     base.LineEnding,
   282  				EncodeTime:     func(t time.Time, enc PrimitiveArrayEncoder) { enc.AppendString(t.String()) },
   283  				EncodeDuration: base.EncodeDuration,
   284  				EncodeLevel:    base.EncodeLevel,
   285  				EncodeCaller:   base.EncodeCaller,
   286  			},
   287  			extra: func(enc Encoder) {
   288  				enc.AddTime("extra", _epoch)
   289  				err := enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error {
   290  					enc.AppendTime(_epoch)
   291  					return nil
   292  				}))
   293  				assert.NoError(t, err)
   294  			},
   295  			expectedJSON: `{"L":"info","T":"1970-01-01 00:00:00 +0000 UTC","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1970-01-01 00:00:00 +0000 UTC","extras":["1970-01-01 00:00:00 +0000 UTC"],"S":"fake-stack"}` + "\n",
   296  			expectedConsole: "1970-01-01 00:00:00 +0000 UTC\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // plain-text preamble
   297  				`{"extra": "1970-01-01 00:00:00 +0000 UTC", "extras": ["1970-01-01 00:00:00 +0000 UTC"]}` + // JSON context
   298  				"\nfake-stack\n", // stacktrace after newline
   299  		},
   300  		{
   301  			desc: "use the supplied EncodeDuration for any durations added",
   302  			cfg: EncoderConfig{
   303  				LevelKey:       "L",
   304  				TimeKey:        "T",
   305  				MessageKey:     "M",
   306  				NameKey:        "N",
   307  				CallerKey:      "C",
   308  				FunctionKey:    "F",
   309  				StacktraceKey:  "S",
   310  				LineEnding:     base.LineEnding,
   311  				EncodeTime:     base.EncodeTime,
   312  				EncodeDuration: StringDurationEncoder,
   313  				EncodeLevel:    base.EncodeLevel,
   314  				EncodeCaller:   base.EncodeCaller,
   315  			},
   316  			extra: func(enc Encoder) {
   317  				enc.AddDuration("extra", time.Second)
   318  				err := enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error {
   319  					enc.AppendDuration(time.Minute)
   320  					return nil
   321  				}))
   322  				assert.NoError(t, err)
   323  			},
   324  			expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1s","extras":["1m0s"],"S":"fake-stack"}` + "\n",
   325  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // preamble
   326  				`{"extra": "1s", "extras": ["1m0s"]}` + // context
   327  				"\nfake-stack\n", // stacktrace
   328  		},
   329  		{
   330  			desc: "use the supplied EncodeLevel",
   331  			cfg: EncoderConfig{
   332  				LevelKey:       "L",
   333  				TimeKey:        "T",
   334  				MessageKey:     "M",
   335  				NameKey:        "N",
   336  				CallerKey:      "C",
   337  				FunctionKey:    "F",
   338  				StacktraceKey:  "S",
   339  				LineEnding:     base.LineEnding,
   340  				EncodeTime:     base.EncodeTime,
   341  				EncodeDuration: base.EncodeDuration,
   342  				EncodeLevel:    CapitalLevelEncoder,
   343  				EncodeCaller:   base.EncodeCaller,
   344  			},
   345  			expectedJSON:    `{"L":"INFO","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   346  			expectedConsole: "0\tINFO\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n",
   347  		},
   348  		{
   349  			desc: "use the supplied EncodeName",
   350  			cfg: EncoderConfig{
   351  				LevelKey:       "L",
   352  				TimeKey:        "T",
   353  				MessageKey:     "M",
   354  				NameKey:        "N",
   355  				CallerKey:      "C",
   356  				FunctionKey:    "F",
   357  				StacktraceKey:  "S",
   358  				LineEnding:     base.LineEnding,
   359  				EncodeTime:     base.EncodeTime,
   360  				EncodeDuration: base.EncodeDuration,
   361  				EncodeLevel:    base.EncodeLevel,
   362  				EncodeCaller:   base.EncodeCaller,
   363  				EncodeName:     capitalNameEncoder,
   364  			},
   365  			expectedJSON:    `{"L":"info","T":0,"N":"MAIN","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   366  			expectedConsole: "0\tinfo\tMAIN\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n",
   367  		},
   368  		{
   369  			desc: "close all open namespaces",
   370  			cfg: EncoderConfig{
   371  				LevelKey:       "L",
   372  				TimeKey:        "T",
   373  				MessageKey:     "M",
   374  				NameKey:        "N",
   375  				CallerKey:      "C",
   376  				FunctionKey:    "F",
   377  				StacktraceKey:  "S",
   378  				LineEnding:     base.LineEnding,
   379  				EncodeTime:     base.EncodeTime,
   380  				EncodeDuration: base.EncodeDuration,
   381  				EncodeLevel:    base.EncodeLevel,
   382  				EncodeCaller:   base.EncodeCaller,
   383  			},
   384  			extra: func(enc Encoder) {
   385  				enc.OpenNamespace("outer")
   386  				enc.OpenNamespace("inner")
   387  				enc.AddString("foo", "bar")
   388  				enc.OpenNamespace("innermost")
   389  			},
   390  			expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","outer":{"inner":{"foo":"bar","innermost":{}}},"S":"fake-stack"}` + "\n",
   391  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" +
   392  				`{"outer": {"inner": {"foo": "bar", "innermost": {}}}}` +
   393  				"\nfake-stack\n",
   394  		},
   395  		{
   396  			desc: "handle no-op EncodeTime",
   397  			cfg: EncoderConfig{
   398  				LevelKey:       "L",
   399  				TimeKey:        "T",
   400  				MessageKey:     "M",
   401  				NameKey:        "N",
   402  				CallerKey:      "C",
   403  				FunctionKey:    "F",
   404  				StacktraceKey:  "S",
   405  				LineEnding:     base.LineEnding,
   406  				EncodeTime:     func(time.Time, PrimitiveArrayEncoder) {},
   407  				EncodeDuration: base.EncodeDuration,
   408  				EncodeLevel:    base.EncodeLevel,
   409  				EncodeCaller:   base.EncodeCaller,
   410  			},
   411  			extra:           func(enc Encoder) { enc.AddTime("sometime", time.Unix(0, 100)) },
   412  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","sometime":100,"S":"fake-stack"}` + "\n",
   413  			expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"sometime": 100}` + "\nfake-stack\n",
   414  		},
   415  		{
   416  			desc: "handle no-op EncodeDuration",
   417  			cfg: EncoderConfig{
   418  				LevelKey:       "L",
   419  				TimeKey:        "T",
   420  				MessageKey:     "M",
   421  				NameKey:        "N",
   422  				CallerKey:      "C",
   423  				FunctionKey:    "F",
   424  				StacktraceKey:  "S",
   425  				LineEnding:     base.LineEnding,
   426  				EncodeTime:     base.EncodeTime,
   427  				EncodeDuration: func(time.Duration, PrimitiveArrayEncoder) {},
   428  				EncodeLevel:    base.EncodeLevel,
   429  				EncodeCaller:   base.EncodeCaller,
   430  			},
   431  			extra:           func(enc Encoder) { enc.AddDuration("someduration", time.Microsecond) },
   432  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","someduration":1000,"S":"fake-stack"}` + "\n",
   433  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"someduration": 1000}` + "\nfake-stack\n",
   434  		},
   435  		{
   436  			desc: "handle no-op EncodeLevel",
   437  			cfg: EncoderConfig{
   438  				LevelKey:       "L",
   439  				TimeKey:        "T",
   440  				MessageKey:     "M",
   441  				NameKey:        "N",
   442  				CallerKey:      "C",
   443  				FunctionKey:    "F",
   444  				StacktraceKey:  "S",
   445  				LineEnding:     base.LineEnding,
   446  				EncodeTime:     base.EncodeTime,
   447  				EncodeDuration: base.EncodeDuration,
   448  				EncodeLevel:    func(Level, PrimitiveArrayEncoder) {},
   449  				EncodeCaller:   base.EncodeCaller,
   450  			},
   451  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   452  			expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n",
   453  		},
   454  		{
   455  			desc: "handle no-op EncodeCaller",
   456  			cfg: EncoderConfig{
   457  				LevelKey:       "L",
   458  				TimeKey:        "T",
   459  				MessageKey:     "M",
   460  				NameKey:        "N",
   461  				CallerKey:      "C",
   462  				FunctionKey:    "F",
   463  				StacktraceKey:  "S",
   464  				LineEnding:     base.LineEnding,
   465  				EncodeTime:     base.EncodeTime,
   466  				EncodeDuration: base.EncodeDuration,
   467  				EncodeLevel:    base.EncodeLevel,
   468  				EncodeCaller:   func(EntryCaller, PrimitiveArrayEncoder) {},
   469  			},
   470  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   471  			expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n",
   472  		},
   473  		{
   474  			desc: "handle no-op EncodeName",
   475  			cfg: EncoderConfig{
   476  				LevelKey:       "L",
   477  				TimeKey:        "T",
   478  				MessageKey:     "M",
   479  				NameKey:        "N",
   480  				CallerKey:      "C",
   481  				FunctionKey:    "F",
   482  				StacktraceKey:  "S",
   483  				LineEnding:     base.LineEnding,
   484  				EncodeTime:     base.EncodeTime,
   485  				EncodeDuration: base.EncodeDuration,
   486  				EncodeLevel:    base.EncodeLevel,
   487  				EncodeCaller:   base.EncodeCaller,
   488  				EncodeName:     func(string, PrimitiveArrayEncoder) {},
   489  			},
   490  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n",
   491  			expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n",
   492  		},
   493  		{
   494  			desc: "use custom line separator",
   495  			cfg: EncoderConfig{
   496  				LevelKey:       "L",
   497  				TimeKey:        "T",
   498  				MessageKey:     "M",
   499  				NameKey:        "N",
   500  				CallerKey:      "C",
   501  				FunctionKey:    "F",
   502  				StacktraceKey:  "S",
   503  				LineEnding:     "\r\n",
   504  				EncodeTime:     base.EncodeTime,
   505  				EncodeDuration: base.EncodeDuration,
   506  				EncodeLevel:    base.EncodeLevel,
   507  				EncodeCaller:   base.EncodeCaller,
   508  			},
   509  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\r\n",
   510  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\r\n",
   511  		},
   512  		{
   513  			desc: "omit line separator definition - fall back to default",
   514  			cfg: EncoderConfig{
   515  				LevelKey:       "L",
   516  				TimeKey:        "T",
   517  				MessageKey:     "M",
   518  				NameKey:        "N",
   519  				CallerKey:      "C",
   520  				FunctionKey:    "F",
   521  				StacktraceKey:  "S",
   522  				EncodeTime:     base.EncodeTime,
   523  				EncodeDuration: base.EncodeDuration,
   524  				EncodeLevel:    base.EncodeLevel,
   525  				EncodeCaller:   base.EncodeCaller,
   526  			},
   527  			expectedJSON:    `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + DefaultLineEnding,
   528  			expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack" + DefaultLineEnding,
   529  		},
   530  	}
   531  
   532  	for i, tt := range tests {
   533  		json := NewJSONEncoder(tt.cfg)
   534  		console := NewConsoleEncoder(tt.cfg)
   535  		if tt.extra != nil {
   536  			tt.extra(json)
   537  			tt.extra(console)
   538  		}
   539  		entry := _testEntry
   540  		if tt.amendEntry != nil {
   541  			entry = tt.amendEntry(_testEntry)
   542  		}
   543  		jsonOut, jsonErr := json.EncodeEntry(entry, nil)
   544  		if assert.NoError(t, jsonErr, "Unexpected error JSON-encoding entry in case #%d.", i) {
   545  			assert.Equal(
   546  				t,
   547  				tt.expectedJSON,
   548  				jsonOut.String(),
   549  				"Unexpected JSON output: expected to %v.", tt.desc,
   550  			)
   551  		}
   552  		consoleOut, consoleErr := console.EncodeEntry(entry, nil)
   553  		if assert.NoError(t, consoleErr, "Unexpected error console-encoding entry in case #%d.", i) {
   554  			assert.Equal(
   555  				t,
   556  				tt.expectedConsole,
   557  				consoleOut.String(),
   558  				"Unexpected console output: expected to %v.", tt.desc,
   559  			)
   560  		}
   561  	}
   562  }
   563  
   564  func TestLevelEncoders(t *testing.T) {
   565  	tests := []struct {
   566  		name     string
   567  		expected interface{} // output of encoding InfoLevel
   568  	}{
   569  		{"capital", "INFO"},
   570  		{"lower", "info"},
   571  		{"", "info"},
   572  		{"something-random", "info"},
   573  	}
   574  
   575  	for _, tt := range tests {
   576  		var le LevelEncoder
   577  		require.NoError(t, le.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name)
   578  		assertAppended(
   579  			t,
   580  			tt.expected,
   581  			func(arr ArrayEncoder) { le(InfoLevel, arr) },
   582  			"Unexpected output serializing InfoLevel with %q.", tt.name,
   583  		)
   584  	}
   585  }
   586  
   587  func TestTimeEncoders(t *testing.T) {
   588  	moment := time.Unix(100, 50005000).UTC()
   589  	tests := []struct {
   590  		yamlDoc  string
   591  		expected interface{} // output of serializing moment
   592  	}{
   593  		{"timeEncoder: iso8601", "1970-01-01T00:01:40.050Z"},
   594  		{"timeEncoder: ISO8601", "1970-01-01T00:01:40.050Z"},
   595  		{"timeEncoder: millis", 100050.005},
   596  		{"timeEncoder: nanos", int64(100050005000)},
   597  		{"timeEncoder: {layout: 06/01/02 03:04pm}", "70/01/01 12:01am"},
   598  		{"timeEncoder: ''", 100.050005},
   599  		{"timeEncoder: something-random", 100.050005},
   600  		{"timeEncoder: rfc3339", "1970-01-01T00:01:40Z"},
   601  		{"timeEncoder: RFC3339", "1970-01-01T00:01:40Z"},
   602  		{"timeEncoder: rfc3339nano", "1970-01-01T00:01:40.050005Z"},
   603  		{"timeEncoder: RFC3339Nano", "1970-01-01T00:01:40.050005Z"},
   604  	}
   605  
   606  	for _, tt := range tests {
   607  		cfg := EncoderConfig{}
   608  		require.NoError(t, yaml.Unmarshal([]byte(tt.yamlDoc), &cfg), "Unexpected error unmarshaling %q.", tt.yamlDoc)
   609  		require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.yamlDoc)
   610  		assertAppended(
   611  			t,
   612  			tt.expected,
   613  			func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) },
   614  			"Unexpected output serializing %v with %q.", moment, tt.yamlDoc,
   615  		)
   616  	}
   617  }
   618  
   619  func TestTimeEncodersWrongYAML(t *testing.T) {
   620  	tests := []string{
   621  		"timeEncoder: [1, 2, 3]", // wrong type
   622  		"timeEncoder: {foo:bar",  // broken yaml
   623  	}
   624  	for _, tt := range tests {
   625  		cfg := EncoderConfig{}
   626  		assert.Error(t, yaml.Unmarshal([]byte(tt), &cfg), "Expected unmarshaling %q to become error, but not.", tt)
   627  	}
   628  }
   629  
   630  func TestTimeEncodersParseFromJSON(t *testing.T) {
   631  	moment := time.Unix(100, 50005000).UTC()
   632  	tests := []struct {
   633  		jsonDoc  string
   634  		expected interface{} // output of serializing moment
   635  	}{
   636  		{`{"timeEncoder": "iso8601"}`, "1970-01-01T00:01:40.050Z"},
   637  		{`{"timeEncoder": {"layout": "06/01/02 03:04pm"}}`, "70/01/01 12:01am"},
   638  	}
   639  
   640  	for _, tt := range tests {
   641  		cfg := EncoderConfig{}
   642  		require.NoError(t, json.Unmarshal([]byte(tt.jsonDoc), &cfg), "Unexpected error unmarshaling %q.", tt.jsonDoc)
   643  		require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.jsonDoc)
   644  		assertAppended(
   645  			t,
   646  			tt.expected,
   647  			func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) },
   648  			"Unexpected output serializing %v with %q.", moment, tt.jsonDoc,
   649  		)
   650  	}
   651  }
   652  
   653  func TestDurationEncoders(t *testing.T) {
   654  	elapsed := time.Second + 500*time.Nanosecond
   655  	tests := []struct {
   656  		name     string
   657  		expected interface{} // output of serializing elapsed
   658  	}{
   659  		{"string", "1.0000005s"},
   660  		{"nanos", int64(1000000500)},
   661  		{"ms", int64(1000)},
   662  		{"", 1.0000005},
   663  		{"something-random", 1.0000005},
   664  	}
   665  
   666  	for _, tt := range tests {
   667  		var de DurationEncoder
   668  		require.NoError(t, de.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name)
   669  		assertAppended(
   670  			t,
   671  			tt.expected,
   672  			func(arr ArrayEncoder) { de(elapsed, arr) },
   673  			"Unexpected output serializing %v with %q.", elapsed, tt.name,
   674  		)
   675  	}
   676  }
   677  
   678  func TestCallerEncoders(t *testing.T) {
   679  	caller := EntryCaller{Defined: true, File: "/home/jack/src/github.com/foo/foo.go", Line: 42}
   680  	tests := []struct {
   681  		name     string
   682  		expected interface{} // output of serializing caller
   683  	}{
   684  		{"", "foo/foo.go:42"},
   685  		{"something-random", "foo/foo.go:42"},
   686  		{"short", "foo/foo.go:42"},
   687  		{"full", "/home/jack/src/github.com/foo/foo.go:42"},
   688  	}
   689  
   690  	for _, tt := range tests {
   691  		var ce CallerEncoder
   692  		require.NoError(t, ce.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name)
   693  		assertAppended(
   694  			t,
   695  			tt.expected,
   696  			func(arr ArrayEncoder) { ce(caller, arr) },
   697  			"Unexpected output serializing file name as %v with %q.", tt.expected, tt.name,
   698  		)
   699  	}
   700  }
   701  
   702  func TestNameEncoders(t *testing.T) {
   703  	tests := []struct {
   704  		name     string
   705  		expected interface{} // output of encoding InfoLevel
   706  	}{
   707  		{"", "main"},
   708  		{"full", "main"},
   709  		{"something-random", "main"},
   710  	}
   711  
   712  	for _, tt := range tests {
   713  		var ne NameEncoder
   714  		require.NoError(t, ne.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name)
   715  		assertAppended(
   716  			t,
   717  			tt.expected,
   718  			func(arr ArrayEncoder) { ne("main", arr) },
   719  			"Unexpected output serializing logger name with %q.", tt.name,
   720  		)
   721  	}
   722  }
   723  
   724  func assertAppended(t testing.TB, expected interface{}, f func(ArrayEncoder), msgAndArgs ...interface{}) {
   725  	mem := NewMapObjectEncoder()
   726  	err := mem.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error {
   727  		f(arr)
   728  		return nil
   729  	}))
   730  	assert.NoError(t, err, msgAndArgs...)
   731  	arr := mem.Fields["k"].([]interface{})
   732  	require.Equal(t, 1, len(arr), "Expected to append exactly one element to array.")
   733  	assert.Equal(t, expected, arr[0], msgAndArgs...)
   734  }
   735  

View as plain text