github.com/nats-io/nats-server/v2@v2.11.0-preview.2/logger/log_test.go (about) 1 // Copyright 2012-2018 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package logger 15 16 import ( 17 "bytes" 18 "fmt" 19 "io" 20 "log" 21 "os" 22 "path/filepath" 23 "regexp" 24 "strings" 25 "testing" 26 ) 27 28 func TestStdLogger(t *testing.T) { 29 logger := NewStdLogger(false, false, false, false, false) 30 31 flags := logger.logger.Flags() 32 if flags != 0 { 33 t.Fatalf("Expected %q, received %q\n", 0, flags) 34 } 35 36 if logger.debug { 37 t.Fatalf("Expected %t, received %t\n", false, logger.debug) 38 } 39 40 if logger.trace { 41 t.Fatalf("Expected %t, received %t\n", false, logger.trace) 42 } 43 } 44 45 func TestStdLoggerWithDebugTraceAndTime(t *testing.T) { 46 logger := NewStdLogger(true, true, true, false, false) 47 48 flags := logger.logger.Flags() 49 if flags != log.LstdFlags|log.Lmicroseconds { 50 t.Fatalf("Expected %d, received %d\n", log.LstdFlags, flags) 51 } 52 53 if !logger.debug { 54 t.Fatalf("Expected %t, received %t\n", true, logger.debug) 55 } 56 57 if !logger.trace { 58 t.Fatalf("Expected %t, received %t\n", true, logger.trace) 59 } 60 } 61 62 func TestStdLoggerNotice(t *testing.T) { 63 expectOutput(t, func() { 64 logger := NewStdLogger(false, false, false, false, false) 65 logger.Noticef("foo") 66 }, "[INF] foo\n") 67 } 68 69 func TestStdLoggerNoticeWithColor(t *testing.T) { 70 expectOutput(t, func() { 71 logger := NewStdLogger(false, false, false, true, false) 72 logger.Noticef("foo") 73 }, "[\x1b[32mINF\x1b[0m] foo\n") 74 } 75 76 func TestStdLoggerDebug(t *testing.T) { 77 expectOutput(t, func() { 78 logger := NewStdLogger(false, true, false, false, false) 79 logger.Debugf("foo %s", "bar") 80 }, "[DBG] foo bar\n") 81 } 82 83 func TestStdLoggerDebugWithOutDebug(t *testing.T) { 84 expectOutput(t, func() { 85 logger := NewStdLogger(false, false, false, false, false) 86 logger.Debugf("foo") 87 }, "") 88 } 89 90 func TestStdLoggerTrace(t *testing.T) { 91 expectOutput(t, func() { 92 logger := NewStdLogger(false, false, true, false, false) 93 logger.Tracef("foo") 94 }, "[TRC] foo\n") 95 } 96 97 func TestStdLoggerTraceWithOutDebug(t *testing.T) { 98 expectOutput(t, func() { 99 logger := NewStdLogger(false, false, false, false, false) 100 logger.Tracef("foo") 101 }, "") 102 } 103 104 func TestFileLogger(t *testing.T) { 105 tmpDir := t.TempDir() 106 file := createFileAtDir(t, tmpDir, "nats-server:log_") 107 file.Close() 108 109 logger := NewFileLogger(file.Name(), false, false, false, false) 110 defer logger.Close() 111 logger.Noticef("foo") 112 113 buf, err := os.ReadFile(file.Name()) 114 if err != nil { 115 t.Fatalf("Could not read logfile: %v", err) 116 } 117 if len(buf) <= 0 { 118 t.Fatal("Expected a non-zero length logfile") 119 } 120 121 if string(buf) != "[INF] foo\n" { 122 t.Fatalf("Expected '%s', received '%s'\n", "[INFO] foo", string(buf)) 123 } 124 125 file = createFileAtDir(t, tmpDir, "nats-server:log_") 126 file.Close() 127 128 logger = NewFileLogger(file.Name(), true, false, true, true) 129 defer logger.Close() 130 logger.Errorf("foo") 131 132 buf, err = os.ReadFile(file.Name()) 133 if err != nil { 134 t.Fatalf("Could not read logfile: %v", err) 135 } 136 if len(buf) <= 0 { 137 t.Fatal("Expected a non-zero length logfile") 138 } 139 str := string(buf) 140 errMsg := fmt.Sprintf("Expected '%s', received '%s'\n", "[pid] <date> [ERR] foo", str) 141 pidEnd := strings.Index(str, " ") 142 infoStart := strings.LastIndex(str, "[ERR]") 143 if pidEnd == -1 || infoStart == -1 { 144 t.Fatalf("%v", errMsg) 145 } 146 pid := str[0:pidEnd] 147 if pid[0] != '[' || pid[len(pid)-1] != ']' { 148 t.Fatalf("%v", errMsg) 149 } 150 151 date := str[pidEnd:infoStart] 152 dateRegExp := "[0-9]{4}/[0-9]{2}/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6}" 153 reg, err := regexp.Compile(dateRegExp) 154 if err != nil { 155 t.Fatalf("Compile date regexp error: %v", err) 156 } 157 if matched := reg.Match([]byte(date)); !matched { 158 t.Fatalf("Date string '%s' does not match '%s'", date, dateRegExp) 159 } 160 161 if !strings.HasSuffix(str, "[ERR] foo\n") { 162 t.Fatalf("%v", errMsg) 163 } 164 } 165 166 func TestFileLoggerSizeLimit(t *testing.T) { 167 // Create std logger 168 logger := NewStdLogger(true, false, false, false, true) 169 if err := logger.SetSizeLimit(1000); err == nil || 170 !strings.Contains(err.Error(), "only for file logger") { 171 t.Fatalf("Expected error about being able to use only for file logger, got %v", err) 172 } 173 logger.Close() 174 175 tmpDir := t.TempDir() 176 177 file := createFileAtDir(t, tmpDir, "log_") 178 file.Close() 179 180 logger = NewFileLogger(file.Name(), true, false, false, true) 181 defer logger.Close() 182 logger.SetSizeLimit(1000) 183 184 for i := 0; i < 50; i++ { 185 logger.Noticef("This is a line in the log file") 186 } 187 188 files, err := os.ReadDir(tmpDir) 189 if err != nil { 190 t.Fatalf("Error reading logs dir: %v", err) 191 } 192 if len(files) == 1 { 193 t.Fatalf("Expected file to have been rotated") 194 } 195 lastBackup := files[len(files)-1] 196 if err := logger.Close(); err != nil { 197 t.Fatalf("Error closing log: %v", err) 198 } 199 content, err := os.ReadFile(file.Name()) 200 if err != nil { 201 t.Fatalf("Error loading latest log: %v", err) 202 } 203 if !bytes.Contains(content, []byte("Rotated log")) || 204 !bytes.Contains(content, []byte(lastBackup.Name())) { 205 t.Fatalf("Should be statement about rotated log and backup name, got %s", content) 206 } 207 208 tmpDir = t.TempDir() 209 210 // Recreate logger and don't set a limit 211 file = createFileAtDir(t, tmpDir, "log_") 212 file.Close() 213 logger = NewFileLogger(file.Name(), true, false, false, true) 214 defer logger.Close() 215 for i := 0; i < 50; i++ { 216 logger.Noticef("This is line %d in the log file", i+1) 217 } 218 files, err = os.ReadDir(tmpDir) 219 if err != nil { 220 t.Fatalf("Error reading logs dir: %v", err) 221 } 222 if len(files) != 1 { 223 t.Fatalf("Expected file to not be rotated") 224 } 225 226 // Now set a limit that is below current size 227 logger.SetSizeLimit(1000) 228 // Should have triggered rotation 229 files, err = os.ReadDir(tmpDir) 230 if err != nil { 231 t.Fatalf("Error reading logs dir: %v", err) 232 } 233 if len(files) <= 1 { 234 t.Fatalf("Expected file to have been rotated") 235 } 236 if err := logger.Close(); err != nil { 237 t.Fatalf("Error closing log: %v", err) 238 } 239 lastBackup = files[len(files)-1] 240 content, err = os.ReadFile(file.Name()) 241 if err != nil { 242 t.Fatalf("Error loading latest log: %v", err) 243 } 244 if !bytes.Contains(content, []byte("Rotated log")) || 245 !bytes.Contains(content, []byte(lastBackup.Name())) { 246 t.Fatalf("Should be statement about rotated log and backup name, got %s", content) 247 } 248 249 logger = NewFileLogger(file.Name(), true, false, false, true) 250 defer logger.Close() 251 logger.SetSizeLimit(1000) 252 253 // Check error on rotate. 254 logger.Lock() 255 logger.fl.Lock() 256 failClose := &fileLogFailClose{logger.fl.f, true} 257 logger.fl.f = failClose 258 logger.fl.Unlock() 259 logger.Unlock() 260 // Write a big line that will force rotation. 261 // Since we fail to close the log file, we should have bumped the limit to 2000 262 logger.Noticef("This is a big line: %v", make([]byte, 1000)) 263 264 // Remove the failure 265 failClose.fail = false 266 // Write a big line that makes rotation happen 267 logger.Noticef("This is a big line: %v", make([]byte, 2000)) 268 // Close 269 logger.Close() 270 271 files, err = os.ReadDir(tmpDir) 272 if err != nil { 273 t.Fatalf("Error reading logs dir: %v", err) 274 } 275 lastBackup = files[len(files)-1] 276 content, err = os.ReadFile(filepath.Join(tmpDir, lastBackup.Name())) 277 if err != nil { 278 t.Fatalf("Error reading backup file: %v", err) 279 } 280 if !bytes.Contains(content, []byte("on purpose")) || !bytes.Contains(content, []byte("size 2000")) { 281 t.Fatalf("Expected error that file could not rotated and max size bumped to 2000, got %s", content) 282 } 283 } 284 285 type fileLogFailClose struct { 286 writerAndCloser 287 fail bool 288 } 289 290 func (l *fileLogFailClose) Close() error { 291 if l.fail { 292 return fmt.Errorf("on purpose") 293 } 294 return l.writerAndCloser.Close() 295 } 296 297 func expectOutput(t *testing.T, f func(), expected string) { 298 old := os.Stderr // keep backup of the real stderr 299 r, w, _ := os.Pipe() 300 os.Stderr = w 301 302 f() 303 304 outC := make(chan string) 305 // copy the output in a separate goroutine so printing can't block indefinitely 306 go func() { 307 var buf bytes.Buffer 308 io.Copy(&buf, r) 309 outC <- buf.String() 310 }() 311 312 os.Stderr.Close() 313 os.Stderr = old // restoring the real stdout 314 out := <-outC 315 if out != expected { 316 t.Fatalf("Expected '%s', received '%s'\n", expected, out) 317 } 318 } 319 320 func createFileAtDir(t *testing.T, dir, prefix string) *os.File { 321 t.Helper() 322 f, err := os.CreateTemp(dir, prefix) 323 if err != nil { 324 t.Fatal(err) 325 } 326 return f 327 }