github.com/blend/go-sdk@v1.20240719.1/logger/logger_test.go (about)

     1  /*
     2  
     3  Copyright (c) 2024 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package logger
     9  
    10  import (
    11  	"bytes"
    12  	"context"
    13  	"fmt"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/blend/go-sdk/assert"
    18  	"github.com/blend/go-sdk/uuid"
    19  )
    20  
    21  func TestNew(t *testing.T) {
    22  	assert := assert.New(t)
    23  
    24  	log, err := New()
    25  	assert.Nil(err)
    26  	assert.NotNil(log.Flags)
    27  	assert.NotNil(log.Writable)
    28  	assert.NotNil(log.Scope)
    29  	assert.NotNil(log.Formatter)
    30  	assert.NotNil(log.Output)
    31  	assert.True(log.RecoverPanics)
    32  
    33  	for _, defaultFlag := range DefaultFlags {
    34  		assert.True(log.Flags.IsEnabled(defaultFlag))
    35  	}
    36  	assert.True(log.Writable.All())
    37  
    38  	log, err = New(OptAll(), OptFormatter(NewJSONOutputFormatter()))
    39  	assert.Nil(err)
    40  	assert.True(log.Flags.IsEnabled(uuid.V4().String()))
    41  	typed, ok := log.Formatter.(*JSONOutputFormatter)
    42  	assert.True(ok)
    43  	assert.NotNil(typed)
    44  }
    45  
    46  func TestLoggerFlagsWritten(t *testing.T) {
    47  	its := assert.New(t)
    48  
    49  	buf := new(bytes.Buffer)
    50  	log := Memory(buf)
    51  	defer log.Close()
    52  
    53  	log.Writable.Disable(Info)
    54  
    55  	eventTriggered := make(chan struct{})
    56  	log.Listen(Info, DefaultListenerName, func(_ context.Context, e Event) {
    57  		close(eventTriggered)
    58  	})
    59  
    60  	log.Dispatch(context.TODO(), NewMessageEvent(Info, "test"))
    61  	<-eventTriggered
    62  	its.Empty(buf.String())
    63  
    64  	log.Dispatch(context.TODO(), NewMessageEvent(Error, "this is just a test"))
    65  	its.Equal("[error] this is just a test\n", buf.String())
    66  }
    67  
    68  func TestLoggerScopes(t *testing.T) {
    69  	its := assert.New(t)
    70  
    71  	buf := new(bytes.Buffer)
    72  	log := Memory(buf)
    73  	defer log.Close()
    74  
    75  	log.Scopes.Disable("disabled/*")
    76  
    77  	eventTriggered := make(chan struct{})
    78  	log.Listen(Error, DefaultListenerName, func(_ context.Context, e Event) {
    79  		close(eventTriggered)
    80  	})
    81  
    82  	log.WithPath("disabled", "foo").TriggerContext(context.TODO(), NewMessageEvent(Info, "test"))
    83  	// should not trigger with scope disabled
    84  	its.Empty(buf.String())
    85  
    86  	log.WithPath("not-disabled", "foo").TriggerContext(context.TODO(), NewMessageEvent(Error, "this is just a test"))
    87  	<-eventTriggered
    88  	its.Equal("[not-disabled > foo] [error] this is just a test\n", buf.String())
    89  }
    90  
    91  func TestLoggerWritableScopes(t *testing.T) {
    92  	its := assert.New(t)
    93  
    94  	buf := new(bytes.Buffer)
    95  	log := Memory(buf)
    96  	defer log.Close()
    97  
    98  	log.WritableScopes.Disable("disabled/*")
    99  
   100  	eventTriggered := make(chan struct{})
   101  	log.Listen(Error, DefaultListenerName, func(_ context.Context, e Event) {
   102  		close(eventTriggered)
   103  	})
   104  
   105  	log.WithPath("disabled", "foo").TriggerContext(context.TODO(), NewMessageEvent(Error, "test"))
   106  	<-eventTriggered
   107  	its.Empty(buf.String())
   108  
   109  	eventTriggered = make(chan struct{})
   110  	log.WithPath("not-disabled", "foo").TriggerContext(context.TODO(), NewMessageEvent(Error, "this is just a test"))
   111  	<-eventTriggered
   112  	its.Equal("[not-disabled > foo] [error] this is just a test\n", buf.String())
   113  }
   114  
   115  func TestLoggerE2ESubContext(t *testing.T) {
   116  	assert := assert.New(t)
   117  
   118  	output := new(bytes.Buffer)
   119  	log, err := New(
   120  		OptOutput(output),
   121  		OptText(OptTextHideTimestamp(), OptTextNoColor()),
   122  	)
   123  	assert.Nil(err)
   124  	defer log.Close()
   125  
   126  	scID := uuid.V4().String()
   127  	sc := log.WithPath(scID)
   128  
   129  	sc.Infof("this is infof")
   130  	sc.Errorf("this is errorf")
   131  	sc.Fatalf("this is fatalf")
   132  
   133  	sc.Trigger(NewMessageEvent(Info, "this is a triggered message"))
   134  
   135  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
   136  	defer cancel()
   137  	log.DrainContext(ctx)
   138  
   139  	assert.Contains(output.String(), fmt.Sprintf("[%s] [info] this is infof", scID))
   140  	assert.Contains(output.String(), fmt.Sprintf("[%s] [error] this is errorf", scID))
   141  	assert.Contains(output.String(), fmt.Sprintf("[%s] [fatal] this is fatalf", scID))
   142  	assert.Contains(output.String(), fmt.Sprintf("[%s] [info] this is a triggered message", scID))
   143  }
   144  
   145  func TestLoggerE2ESubContextFields(t *testing.T) {
   146  	assert := assert.New(t)
   147  
   148  	output := new(bytes.Buffer)
   149  	log, err := New(
   150  		OptOutput(output),
   151  		OptText(OptTextHideTimestamp(), OptTextNoColor()),
   152  	)
   153  	assert.Nil(err)
   154  
   155  	fieldKey := uuid.V4().String()
   156  	fieldValue := uuid.V4().String()
   157  	sc := log.WithLabels(Labels{fieldKey: fieldValue})
   158  
   159  	sc.Infof("this is infof")
   160  	sc.Errorf("this is errorf")
   161  	sc.Fatalf("this is fatalf")
   162  
   163  	sc.Trigger(NewMessageEvent(Info, "this is a triggered message"))
   164  	log.DrainContext(context.Background())
   165  
   166  	assert.Contains(output.String(), fmt.Sprintf("[info] this is infof\t%s=%s", fieldKey, fieldValue))
   167  	assert.Contains(output.String(), fmt.Sprintf("[error] this is errorf\t%s=%s", fieldKey, fieldValue))
   168  	assert.Contains(output.String(), fmt.Sprintf("[fatal] this is fatalf\t%s=%s", fieldKey, fieldValue))
   169  	assert.Contains(output.String(), fmt.Sprintf("[info] this is a triggered message\t%s=%s", fieldKey, fieldValue))
   170  }
   171  
   172  func TestLoggerAnnotations(t *testing.T) {
   173  	assert := assert.New(t)
   174  
   175  	output := new(bytes.Buffer)
   176  	log, err := New(
   177  		OptOutput(output),
   178  		OptText(OptTextHideTimestamp(), OptTextNoColor()),
   179  	)
   180  	assert.Nil(err)
   181  
   182  	fieldKey := uuid.V4().String()
   183  	fieldValue := uuid.V4().String()
   184  	sc := log.WithAnnotations(Annotations{fieldKey: fieldValue})
   185  
   186  	sc.Infof("this is infof")
   187  	sc.Errorf("this is errorf")
   188  	sc.Fatalf("this is fatalf")
   189  
   190  	sc.Trigger(NewMessageEvent(Info, "this is a triggered message"))
   191  	log.DrainContext(context.Background())
   192  
   193  	assert.Contains(output.String(), fmt.Sprintf("[info] this is infof\t%s=%s", fieldKey, fieldValue))
   194  	assert.Contains(output.String(), fmt.Sprintf("[error] this is errorf\t%s=%s", fieldKey, fieldValue))
   195  	assert.Contains(output.String(), fmt.Sprintf("[fatal] this is fatalf\t%s=%s", fieldKey, fieldValue))
   196  	assert.Contains(output.String(), fmt.Sprintf("[info] this is a triggered message\t%s=%s", fieldKey, fieldValue))
   197  }
   198  
   199  func TestLoggerSkipTrigger(t *testing.T) {
   200  	assert := assert.New(t)
   201  
   202  	output := new(bytes.Buffer)
   203  	log, err := New(
   204  		OptOutput(output),
   205  		OptText(OptTextHideTimestamp(), OptTextNoColor()),
   206  	)
   207  	assert.Nil(err)
   208  	defer log.Close()
   209  
   210  	var wasCalled bool
   211  	log.Listen(Info, "---", func(ctx context.Context, e Event) {
   212  		wasCalled = true
   213  	})
   214  
   215  	log.TriggerContext(WithSkipTrigger(context.Background(), true), NewMessageEvent(Info, "this is a triggered message"))
   216  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
   217  	defer cancel()
   218  	log.DrainContext(ctx)
   219  
   220  	assert.False(wasCalled)
   221  	assert.Contains(output.String(), "[info] this is a triggered message")
   222  }
   223  
   224  func TestLoggerSkipWrite(t *testing.T) {
   225  	assert := assert.New(t)
   226  
   227  	output := new(bytes.Buffer)
   228  	log, err := New(
   229  		OptOutput(output),
   230  		OptText(OptTextHideTimestamp(), OptTextNoColor()),
   231  	)
   232  	assert.Nil(err)
   233  	defer log.Close()
   234  
   235  	var wasCalled bool
   236  	log.Listen(Info, "---", func(ctx context.Context, e Event) {
   237  		wasCalled = true
   238  	})
   239  	assert.True(log.HasListener(Info, "---"))
   240  
   241  	log.TriggerContext(WithSkipWrite(context.Background(), true), NewMessageEvent(Info, "this is a triggered message"))
   242  
   243  	// at the very least this cannot cause a deadlock.
   244  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   245  	defer cancel()
   246  
   247  	log.DrainContext(ctx)
   248  	assert.True(wasCalled)
   249  	assert.Empty(output.String())
   250  }
   251  
   252  func TestLoggerListeners(t *testing.T) {
   253  	assert := assert.New(t)
   254  
   255  	log := MustNew()
   256  	defer log.Close()
   257  
   258  	assert.Empty(log.Listeners)
   259  	log.Listen(Info, "foo", NewMessageEventListener(func(_ context.Context, me MessageEvent) {}))
   260  	assert.NotEmpty(log.Listeners)
   261  	assert.True(log.HasListeners(Info))
   262  	assert.True(log.HasListener(Info, "foo"))
   263  	assert.False(log.HasListener(Info, "bar"))
   264  
   265  	log.Listen(Error, "foo", NewMessageEventListener(func(_ context.Context, me MessageEvent) {}))
   266  	assert.True(log.HasListeners(Error))
   267  	assert.True(log.HasListener(Error, "foo"))
   268  	assert.False(log.HasListener(Error, "bar"))
   269  
   270  	log.Listen(Info, "bar", NewMessageEventListener(func(_ context.Context, me MessageEvent) {}))
   271  	assert.True(log.HasListeners(Info))
   272  	assert.True(log.HasListener(Info, "foo"))
   273  	assert.True(log.HasListener(Info, "bar"))
   274  
   275  	log.Listen(Error, "bar", NewMessageEventListener(func(_ context.Context, me MessageEvent) {}))
   276  	assert.True(log.HasListeners(Error))
   277  	assert.True(log.HasListener(Error, "foo"))
   278  	assert.True(log.HasListener(Error, "bar"))
   279  
   280  	assert.Nil(log.RemoveListener(Info, "foo"))
   281  	assert.True(log.HasListeners(Info))
   282  	assert.False(log.HasListener(Info, "foo"))
   283  	assert.True(log.HasListener(Info, "bar"))
   284  
   285  	assert.Nil(log.RemoveListeners(Error))
   286  	assert.False(log.HasListeners(Error))
   287  	assert.False(log.HasListener(Error, "foo"))
   288  	assert.False(log.HasListener(Error, "bar"))
   289  }
   290  
   291  func TestLoggerFilters(t *testing.T) {
   292  	assert := assert.New(t)
   293  
   294  	log := MustNew()
   295  	defer log.Close()
   296  
   297  	noop := func(_ context.Context, e MessageEvent) (MessageEvent, bool) {
   298  		return e, false
   299  	}
   300  
   301  	assert.Empty(log.Filters)
   302  	log.Filter(Info, "foo", NewMessageEventFilter(noop))
   303  	assert.NotEmpty(log.Filters)
   304  	assert.True(log.HasFilters(Info))
   305  	assert.True(log.HasFilter(Info, "foo"))
   306  	assert.False(log.HasFilter(Info, "bar"))
   307  
   308  	log.Filter(Error, "foo", NewMessageEventFilter(noop))
   309  	assert.True(log.HasFilters(Error))
   310  	assert.True(log.HasFilter(Error, "foo"))
   311  	assert.False(log.HasFilter(Error, "bar"))
   312  
   313  	log.Filter(Info, "bar", NewMessageEventFilter(noop))
   314  	assert.True(log.HasFilters(Info))
   315  	assert.True(log.HasFilter(Info, "foo"))
   316  	assert.True(log.HasFilter(Info, "bar"))
   317  
   318  	log.Filter(Error, "bar", NewMessageEventFilter(noop))
   319  	assert.True(log.HasFilters(Error))
   320  	assert.True(log.HasFilter(Error, "foo"))
   321  	assert.True(log.HasFilter(Error, "bar"))
   322  
   323  	log.RemoveFilter(Info, "foo")
   324  	assert.True(log.HasFilters(Info))
   325  	assert.False(log.HasFilter(Info, "foo"))
   326  	assert.True(log.HasFilter(Info, "bar"))
   327  
   328  	log.RemoveFilters(Error)
   329  	assert.False(log.HasFilters(Error))
   330  	assert.False(log.HasFilter(Error, "foo"))
   331  	assert.False(log.HasFilter(Error, "bar"))
   332  }
   333  
   334  func TestLoggerDispatchFilterMutate(t *testing.T) {
   335  	it := assert.New(t)
   336  
   337  	output := new(bytes.Buffer)
   338  	log, err := New(
   339  		OptOutput(output),
   340  		OptText(OptTextHideTimestamp(), OptTextNoColor()),
   341  	)
   342  	it.Nil(err)
   343  	defer log.Close()
   344  
   345  	var wasCalled bool
   346  	var textWasModified bool
   347  
   348  	log.Listen(Info, "---", func(ctx context.Context, e Event) {
   349  		wasCalled = true
   350  		textWasModified = e.(MessageEvent).Text == "not_test_message"
   351  	})
   352  	var wasFiltered bool
   353  	log.Filter(Info, "---", func(ctx context.Context, e Event) (Event, bool) {
   354  		wasFiltered = true
   355  		copy := e.(MessageEvent)
   356  		copy.Text = "not_" + copy.Text
   357  		return copy, false
   358  	})
   359  
   360  	log.TriggerContext(context.Background(), NewMessageEvent(Info, "test_message"))
   361  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
   362  	defer cancel()
   363  	log.DrainContext(ctx)
   364  
   365  	it.True(wasFiltered)
   366  	it.True(wasCalled)
   367  	it.True(textWasModified)
   368  
   369  	it.Equal("[info] not_test_message\n", output.String())
   370  }
   371  
   372  func TestLoggerDispatchFilterDrop(t *testing.T) {
   373  	it := assert.New(t)
   374  
   375  	output := new(bytes.Buffer)
   376  	log, err := New(
   377  		OptOutput(output),
   378  		OptText(OptTextHideTimestamp(), OptTextNoColor()),
   379  	)
   380  	it.Nil(err)
   381  	defer log.Close()
   382  
   383  	var wasCalled bool
   384  	log.Listen(Info, "---", func(ctx context.Context, e Event) {
   385  		wasCalled = true
   386  	})
   387  	var wasFiltered bool
   388  	log.Filter(Info, "---", func(ctx context.Context, e Event) (Event, bool) {
   389  		wasFiltered = true
   390  		return nil, true
   391  	})
   392  
   393  	log.TriggerContext(context.Background(), NewMessageEvent(Info, "this is a triggered message"))
   394  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
   395  	defer cancel()
   396  	log.DrainContext(ctx)
   397  
   398  	it.True(wasFiltered)
   399  	it.False(wasCalled)
   400  	it.Empty(output.String())
   401  }
   402  
   403  func TestLoggerDrain(t *testing.T) {
   404  	assert := assert.New(t)
   405  
   406  	log := MustNew()
   407  	defer log.Close()
   408  	assert.Empty(log.Listeners)
   409  
   410  	eventsCounted := 0
   411  	log.Listen(Info, "foo", NewMessageEventListener(func(_ context.Context, me MessageEvent) {
   412  		eventsCounted++
   413  	}))
   414  
   415  	for i := 0; i < 5; i++ {
   416  		log.Info("event")
   417  	}
   418  	log.Drain()
   419  	assert.Equal(5, eventsCounted)
   420  
   421  	for i := 0; i < 4; i++ {
   422  		log.Info("event")
   423  	}
   424  	log.Drain()
   425  	assert.Equal(9, eventsCounted)
   426  }
   427  
   428  func TestLoggerProd(t *testing.T) {
   429  	assert := assert.New(t)
   430  
   431  	p := Prod(OptEnabled("example-string"))
   432  	defer p.Close()
   433  
   434  	assert.True(p.Flags.IsEnabled("example-string"))
   435  	assert.True(p.Formatter.(*TextOutputFormatter).NoColor)
   436  }
   437  
   438  func TestLoggerClose(t *testing.T) {
   439  	assert := assert.New(t)
   440  
   441  	buf0 := new(bytes.Buffer)
   442  	l0 := Memory(buf0)
   443  	buf1 := new(bytes.Buffer)
   444  	l1 := Memory(buf1)
   445  
   446  	l0.Close()
   447  	l1.Infof("this is a test")
   448  	assert.Empty(buf0.String())
   449  	assert.NotEmpty(buf1.String())
   450  }