github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/logger/logger_test.go (about) 1 package logger 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "testing" 8 "time" 9 10 "github.com/redis/go-redis/v9" 11 "github.com/sirupsen/logrus" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/mock" 14 "github.com/stretchr/testify/require" 15 ) 16 17 type hookMock struct { 18 mock.Mock 19 } 20 21 func newHookMock(t *testing.T) *hookMock { 22 m := new(hookMock) 23 m.Test(t) 24 25 t.Cleanup(func() { m.AssertExpectations(t) }) 26 27 return m 28 } 29 30 func (m *hookMock) Levels() []logrus.Level { 31 return logrus.AllLevels 32 } 33 34 func (m *hookMock) Fire(entry *logrus.Entry) error { 35 return m.Called(entry.String()).Error(0) 36 } 37 38 func TestImplementation(t *testing.T) { 39 assert.Implements(t, (*Logger)(nil), new(Entry)) 40 } 41 42 func TestLogger(t *testing.T) { 43 t.Run("DebugDomain", func(t *testing.T) { 44 buf := new(bytes.Buffer) 45 err := Init(Options{ 46 Level: "info", 47 Output: buf, 48 }) 49 assert.NoError(t, err) 50 51 debugLogger.SetFormatter(&logrus.TextFormatter{ 52 DisableColors: true, 53 DisableTimestamp: true, 54 }) 55 56 err = AddDebugDomain("foo.bar", 24*time.Hour) 57 assert.NoError(t, err) 58 59 WithDomain("foo.bar").Debug("debug1") 60 61 debugTimeout := DebugExpiration("foo.bar") 62 assert.WithinDuration(t, time.Now().Add(24*time.Hour), *debugTimeout, time.Second) 63 64 err = RemoveDebugDomain("foo.bar") 65 assert.NoError(t, err) 66 67 WithDomain("foo.bar").Debug("debug2") 68 69 assert.Equal(t, "level=debug msg=debug1 domain=foo.bar\n", buf.String()) 70 }) 71 72 t.Run("DebugDomain with expired debug", func(t *testing.T) { 73 buf := new(bytes.Buffer) 74 75 err := Init(Options{ 76 Level: "info", 77 Output: buf, 78 }) 79 assert.NoError(t, err) 80 81 debugLogger.SetFormatter(&logrus.TextFormatter{ 82 DisableColors: true, 83 DisableTimestamp: true, 84 }) 85 86 err = AddDebugDomain("foo.bar", 5*time.Millisecond) 87 assert.NoError(t, err) 88 89 WithDomain("foo.bar").Debug("debug1") 90 91 // Let the rule about the domain expire. 92 time.Sleep(6 * time.Millisecond) 93 94 // Should not be logged as the debug mode has expired 95 WithDomain("foo.bar").Debug("debug2") 96 97 debugTimeout := DebugExpiration("foo.bar") 98 assert.Nil(t, debugTimeout) 99 100 assert.Equal(t, "level=debug msg=debug1 domain=foo.bar\n", buf.String()) 101 }) 102 103 t.Run("DebugDomainWithRedis", func(t *testing.T) { 104 if testing.Short() { 105 t.Skip("an instance is required for this test: test skipped due to the use of --short flag") 106 } 107 108 opt, err := redis.ParseURL("redis://localhost:6379/0") 109 assert.NoError(t, err) 110 err = Init(Options{ 111 Level: "info", 112 Redis: redis.NewClient(opt), 113 }) 114 assert.NoError(t, err) 115 116 buf := new(bytes.Buffer) 117 debugLogger.SetFormatter(&logrus.TextFormatter{ 118 DisableColors: true, 119 DisableTimestamp: true, 120 }) 121 debugLogger.SetOutput(buf) 122 123 time.Sleep(100 * time.Millisecond) 124 125 err = AddDebugDomain("foo.bar.redis", 24*time.Hour) 126 assert.NoError(t, err) 127 err = AddDebugDomain("foo.bar.redis", 24*time.Hour) 128 assert.NoError(t, err) 129 130 time.Sleep(100 * time.Millisecond) 131 132 WithDomain("foo.bar.redis").Debug("debug1") 133 134 err = RemoveDebugDomain("foo.bar.redis") 135 assert.NoError(t, err) 136 137 time.Sleep(100 * time.Millisecond) 138 139 WithDomain("foo.bar.redis").Debug("debug2") 140 141 assert.Equal(t, "level=debug msg=debug1 domain=foo.bar.redis\n", buf.String()) 142 }) 143 144 t.Run("Hooks are triggered", func(t *testing.T) { 145 hook := newHookMock(t) 146 147 err := Init(Options{ 148 Level: "info", 149 Hooks: []logrus.Hook{hook}, 150 Output: io.Discard, 151 }) 152 require.NoError(t, err) 153 154 logrus.StandardLogger().SetFormatter(&logrus.TextFormatter{ 155 DisableColors: true, 156 DisableTimestamp: true, 157 }) 158 159 hook.On("Fire", "level=warning msg=warn1 domain=foo.bar\n", nil).Once().Return(nil) 160 161 WithDomain("foo.bar").Warnf("warn%d", 1) 162 }) 163 164 t.Run("Fallback to Info if level is not set", func(t *testing.T) { 165 err := Init(Options{}) 166 require.NoError(t, err) 167 168 assert.Equal(t, logrus.InfoLevel, logrus.StandardLogger().Level) 169 }) 170 171 t.Run("Init fail in case of an invalid level", func(t *testing.T) { 172 err := Init(Options{Level: "invalid level"}) 173 174 require.EqualError(t, err, "not a valid logrus Level: \"invalid level\"") 175 }) 176 177 t.Run("Truncate log line if too long", func(t *testing.T) { 178 buf := new(bytes.Buffer) 179 180 err := Init(Options{ 181 Level: "info", 182 Output: buf, 183 }) 184 require.NoError(t, err) 185 186 debugLogger.SetFormatter(&logrus.TextFormatter{ 187 DisableColors: true, 188 DisableTimestamp: true, 189 }) 190 191 // input == "foo" + ' ' * 3000 + "bar" 192 WithDomain("test").Error(fmt.Sprintf("%-3000sbar", "foo")) 193 194 assert.Equal(t, fmt.Sprintf("level=error msg=\"%-1988s [TRUNCATED]\" domain=test\n", "foo"), buf.String()) 195 }) 196 }