13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
17 func TestLogger(t *testing.T) {
18 t.Run("formats log entries", func(t *testing.T) {
21 logger := New(&LoggerOptions{
26 logger.Info("this is test", "who", "programmer", "why", "testing")
30 dataIdx := strings.IndexByte(str, ' ')
32 // ts := str[:dataIdx]
33 rest := str[dataIdx+1:]
35 assert.Equal(t, "[INFO ] test: this is test: who=programmer why=testing\n", rest)
38 t.Run("quotes values with spaces", func(t *testing.T) {
41 logger := New(&LoggerOptions{
46 logger.Info("this is test", "who", "programmer", "why", "testing is fun")
50 dataIdx := strings.IndexByte(str, ' ')
52 // ts := str[:dataIdx]
53 rest := str[dataIdx+1:]
55 assert.Equal(t, "[INFO ] test: this is test: who=programmer why=\"testing is fun\"\n", rest)
58 t.Run("outputs stack traces", func(t *testing.T) {
61 logger := New(&LoggerOptions{
66 logger.Info("who", "programmer", "why", "testing", Stacktrace())
68 lines := strings.Split(buf.String(), "\n")
70 require.True(t, len(lines) > 1)
72 assert.Equal(t, "github.com/hashicorp/go-hclog.Stacktrace", lines[1])
75 t.Run("outputs stack traces with it's given a name", func(t *testing.T) {
78 logger := New(&LoggerOptions{
83 logger.Info("who", "programmer", "why", "testing", "foo", Stacktrace())
85 lines := strings.Split(buf.String(), "\n")
87 require.True(t, len(lines) > 1)
89 assert.Equal(t, "github.com/hashicorp/go-hclog.Stacktrace", lines[1])
92 t.Run("includes the caller location", func(t *testing.T) {
95 logger := New(&LoggerOptions{
98 IncludeLocation: true,
101 logger.Info("this is test", "who", "programmer", "why", "testing is fun")
105 dataIdx := strings.IndexByte(str, ' ')
107 // ts := str[:dataIdx]
108 rest := str[dataIdx+1:]
110 // This test will break if you move this around, it's line dependent, just fyi
111 assert.Equal(t, "[INFO ] go-hclog/logger_test.go:101: test: this is test: who=programmer why=\"testing is fun\"\n", rest)
114 t.Run("prefixes the name", func(t *testing.T) {
117 logger := New(&LoggerOptions{
122 logger.Info("this is test")
124 dataIdx := strings.IndexByte(str, ' ')
125 rest := str[dataIdx+1:]
126 assert.Equal(t, "[INFO ] this is test\n", rest)
130 another := logger.Named("sublogger")
131 another.Info("this is test")
133 dataIdx = strings.IndexByte(str, ' ')
134 rest = str[dataIdx+1:]
135 assert.Equal(t, "[INFO ] sublogger: this is test\n", rest)
138 t.Run("use a different time format", func(t *testing.T) {
141 logger := New(&LoggerOptions{
144 TimeFormat: time.Kitchen,
147 logger.Info("this is test", "who", "programmer", "why", "testing is fun")
151 dataIdx := strings.IndexByte(str, ' ')
153 assert.Equal(t, str[:dataIdx], time.Now().Format(time.Kitchen))
156 t.Run("use with", func(t *testing.T) {
159 rootLogger := New(&LoggerOptions{
164 // Build the root logger in two steps, which triggers a slice capacity increase
165 // and is part of the test for inadvertant slice aliasing.
166 rootLogger = rootLogger.With("a", 1, "b", 2)
167 rootLogger = rootLogger.With("c", 3)
169 // Derive two new loggers which should be completely independent
170 derived1 := rootLogger.With("cat", 30)
171 derived2 := rootLogger.With("dog", 40)
173 derived1.Info("test1")
174 output := buf.String()
175 dataIdx := strings.IndexByte(output, ' ')
176 assert.Equal(t, "[INFO ] with_test: test1: a=1 b=2 c=3 cat=30\n", output[dataIdx+1:])
180 derived2.Info("test2")
181 output = buf.String()
182 dataIdx = strings.IndexByte(output, ' ')
183 assert.Equal(t, "[INFO ] with_test: test2: a=1 b=2 c=3 dog=40\n", output[dataIdx+1:])
186 t.Run("unpaired with", func(t *testing.T) {
188 if r := recover(); r == nil {
189 t.Fatal("expected panic")
195 rootLogger := New(&LoggerOptions{
200 rootLogger = rootLogger.With("a")
203 t.Run("use with and log", func(t *testing.T) {
206 rootLogger := New(&LoggerOptions{
211 // Build the root logger in two steps, which triggers a slice capacity increase
212 // and is part of the test for inadvertant slice aliasing.
213 rootLogger = rootLogger.With("a", 1, "b", 2)
214 // This line is here to test that when calling With with the same key,
215 // only the last value remains (see issue #21)
216 rootLogger = rootLogger.With("c", 4)
217 rootLogger = rootLogger.With("c", 3)
219 // Derive another logger which should be completely independent of rootLogger
220 derived := rootLogger.With("cat", 30)
222 rootLogger.Info("root_test", "bird", 10)
223 output := buf.String()
224 dataIdx := strings.IndexByte(output, ' ')
225 assert.Equal(t, "[INFO ] with_test: root_test: a=1 b=2 c=3 bird=10\n", output[dataIdx+1:])
229 derived.Info("derived_test")
230 output = buf.String()
231 dataIdx = strings.IndexByte(output, ' ')
232 assert.Equal(t, "[INFO ] with_test: derived_test: a=1 b=2 c=3 cat=30\n", output[dataIdx+1:])
235 t.Run("use with and log and change levels", func(t *testing.T) {
238 rootLogger := New(&LoggerOptions{
244 // Build the root logger in two steps, which triggers a slice capacity increase
245 // and is part of the test for inadvertant slice aliasing.
246 rootLogger = rootLogger.With("a", 1, "b", 2)
247 rootLogger = rootLogger.With("c", 3)
249 // Derive another logger which should be completely independent of rootLogger
250 derived := rootLogger.With("cat", 30)
252 rootLogger.Info("root_test", "bird", 10)
253 output := buf.String()
255 t.Fatalf("unexpected output: %s", output)
260 derived.Info("derived_test")
261 output = buf.String()
263 t.Fatalf("unexpected output: %s", output)
266 derived.SetLevel(Info)
268 rootLogger.Info("root_test", "bird", 10)
269 output = buf.String()
270 dataIdx := strings.IndexByte(output, ' ')
271 assert.Equal(t, "[INFO ] with_test: root_test: a=1 b=2 c=3 bird=10\n", output[dataIdx+1:])
275 derived.Info("derived_test")
276 output = buf.String()
277 dataIdx = strings.IndexByte(output, ' ')
278 assert.Equal(t, "[INFO ] with_test: derived_test: a=1 b=2 c=3 cat=30\n", output[dataIdx+1:])
281 t.Run("supports Printf style expansions when requested", func(t *testing.T) {
284 logger := New(&LoggerOptions{
289 logger.Info("this is test", "production", Fmt("%d beans/day", 12))
293 dataIdx := strings.IndexByte(str, ' ')
295 // ts := str[:dataIdx]
296 rest := str[dataIdx+1:]
298 assert.Equal(t, "[INFO ] test: this is test: production=\"12 beans/day\"\n", rest)
302 func TestLogger_JSON(t *testing.T) {
303 t.Run("json formatting", func(t *testing.T) {
305 logger := New(&LoggerOptions{
311 logger.Info("this is test", "who", "programmer", "why", "testing is fun")
315 var raw map[string]interface{}
316 if err := json.Unmarshal(b, &raw); err != nil {
320 assert.Equal(t, "this is test", raw["@message"])
321 assert.Equal(t, "programmer", raw["who"])
322 assert.Equal(t, "testing is fun", raw["why"])
325 t.Run("json formatting with", func(t *testing.T) {
327 logger := New(&LoggerOptions{
332 logger = logger.With("cat", "in the hat", "dog", 42)
334 logger.Info("this is test", "who", "programmer", "why", "testing is fun")
338 var raw map[string]interface{}
339 if err := json.Unmarshal(b, &raw); err != nil {
343 assert.Equal(t, "this is test", raw["@message"])
344 assert.Equal(t, "programmer", raw["who"])
345 assert.Equal(t, "testing is fun", raw["why"])
346 assert.Equal(t, "in the hat", raw["cat"])
347 assert.Equal(t, float64(42), raw["dog"])
350 t.Run("json formatting error type", func(t *testing.T) {
353 logger := New(&LoggerOptions{
359 errMsg := errors.New("this is an error")
360 logger.Info("this is test", "who", "programmer", "err", errMsg)
364 var raw map[string]interface{}
365 if err := json.Unmarshal(b, &raw); err != nil {
369 assert.Equal(t, "this is test", raw["@message"])
370 assert.Equal(t, "programmer", raw["who"])
371 assert.Equal(t, errMsg.Error(), raw["err"])
374 t.Run("json formatting custom error type json marshaler", func(t *testing.T) {
377 logger := New(&LoggerOptions{
383 errMsg := &customErrJSON{"this is an error"}
384 rawMsg, err := errMsg.MarshalJSON()
388 expectedMsg, err := strconv.Unquote(string(rawMsg))
393 logger.Info("this is test", "who", "programmer", "err", errMsg)
397 var raw map[string]interface{}
398 if err := json.Unmarshal(b, &raw); err != nil {
402 assert.Equal(t, "this is test", raw["@message"])
403 assert.Equal(t, "programmer", raw["who"])
404 assert.Equal(t, expectedMsg, raw["err"])
407 t.Run("json formatting custom error type text marshaler", func(t *testing.T) {
410 logger := New(&LoggerOptions{
416 errMsg := &customErrText{"this is an error"}
417 rawMsg, err := errMsg.MarshalText()
421 expectedMsg := string(rawMsg)
423 logger.Info("this is test", "who", "programmer", "err", errMsg)
427 var raw map[string]interface{}
428 if err := json.Unmarshal(b, &raw); err != nil {
432 assert.Equal(t, "this is test", raw["@message"])
433 assert.Equal(t, "programmer", raw["who"])
434 assert.Equal(t, expectedMsg, raw["err"])
437 t.Run("supports Printf style expansions when requested", func(t *testing.T) {
440 logger := New(&LoggerOptions{
446 logger.Info("this is test", "production", Fmt("%d beans/day", 12))
450 var raw map[string]interface{}
451 if err := json.Unmarshal(b, &raw); err != nil {
455 assert.Equal(t, "this is test", raw["@message"])
456 assert.Equal(t, "12 beans/day", raw["production"])
460 type customErrJSON struct {
465 func (c *customErrJSON) Error() string {
469 // json.Marshaler impl.
470 func (c customErrJSON) MarshalJSON() ([]byte, error) {
471 return []byte(strconv.Quote(fmt.Sprintf("json-marshaler: %s", c.Message))), nil
474 type customErrText struct {
479 func (c *customErrText) Error() string {
483 // text.Marshaler impl.
484 func (c customErrText) MarshalText() ([]byte, error) {
485 return []byte(fmt.Sprintf("text-marshaler: %s", c.Message)), nil
488 func BenchmarkLogger(b *testing.B) {
489 b.Run("info with 10 pairs", func(b *testing.B) {
492 logger := New(&LoggerOptions{
495 IncludeLocation: true,
498 for i := 0; i < b.N; i++ {
499 logger.Info("this is some message",
501 "what", "benchmarking yourself",
502 "why", "to see what's slow",