github.com/nats-io/nats-server/v2@v2.11.0-preview.2/logger/log.go (about) 1 // Copyright 2012-2019 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 provides logging facilities for the NATS server 15 package logger 16 17 import ( 18 "fmt" 19 "log" 20 "os" 21 "path/filepath" 22 "strings" 23 "sync" 24 "sync/atomic" 25 "time" 26 ) 27 28 // Logger is the server logger 29 type Logger struct { 30 sync.Mutex 31 logger *log.Logger 32 debug bool 33 trace bool 34 infoLabel string 35 warnLabel string 36 errorLabel string 37 fatalLabel string 38 debugLabel string 39 traceLabel string 40 fl *fileLogger 41 } 42 43 type LogOption interface { 44 isLoggerOption() 45 } 46 47 // LogUTC controls whether timestamps in the log output should be UTC or local time. 48 type LogUTC bool 49 50 func (l LogUTC) isLoggerOption() {} 51 52 func logFlags(time bool, opts ...LogOption) int { 53 flags := 0 54 if time { 55 flags = log.LstdFlags | log.Lmicroseconds 56 } 57 58 for _, opt := range opts { 59 switch v := opt.(type) { 60 case LogUTC: 61 if time && bool(v) { 62 flags |= log.LUTC 63 } 64 } 65 } 66 67 return flags 68 } 69 70 // NewStdLogger creates a logger with output directed to Stderr 71 func NewStdLogger(time, debug, trace, colors, pid bool, opts ...LogOption) *Logger { 72 flags := logFlags(time, opts...) 73 74 pre := "" 75 if pid { 76 pre = pidPrefix() 77 } 78 79 l := &Logger{ 80 logger: log.New(os.Stderr, pre, flags), 81 debug: debug, 82 trace: trace, 83 } 84 85 if colors { 86 setColoredLabelFormats(l) 87 } else { 88 setPlainLabelFormats(l) 89 } 90 91 return l 92 } 93 94 // NewFileLogger creates a logger with output directed to a file 95 func NewFileLogger(filename string, time, debug, trace, pid bool, opts ...LogOption) *Logger { 96 flags := logFlags(time, opts...) 97 98 pre := "" 99 if pid { 100 pre = pidPrefix() 101 } 102 103 fl, err := newFileLogger(filename, pre, time) 104 if err != nil { 105 log.Fatalf("error opening file: %v", err) 106 return nil 107 } 108 109 l := &Logger{ 110 logger: log.New(fl, pre, flags), 111 debug: debug, 112 trace: trace, 113 fl: fl, 114 } 115 fl.Lock() 116 fl.l = l 117 fl.Unlock() 118 119 setPlainLabelFormats(l) 120 return l 121 } 122 123 type writerAndCloser interface { 124 Write(b []byte) (int, error) 125 Close() error 126 Name() string 127 } 128 129 type fileLogger struct { 130 out int64 131 canRotate int32 132 sync.Mutex 133 l *Logger 134 f writerAndCloser 135 limit int64 136 olimit int64 137 pid string 138 time bool 139 closed bool 140 maxNumFiles int 141 } 142 143 func newFileLogger(filename, pidPrefix string, time bool) (*fileLogger, error) { 144 fileflags := os.O_WRONLY | os.O_APPEND | os.O_CREATE 145 f, err := os.OpenFile(filename, fileflags, 0660) 146 if err != nil { 147 return nil, err 148 } 149 stats, err := f.Stat() 150 if err != nil { 151 f.Close() 152 return nil, err 153 } 154 fl := &fileLogger{ 155 canRotate: 0, 156 f: f, 157 out: stats.Size(), 158 pid: pidPrefix, 159 time: time, 160 } 161 return fl, nil 162 } 163 164 func (l *fileLogger) setLimit(limit int64) { 165 l.Lock() 166 l.olimit, l.limit = limit, limit 167 atomic.StoreInt32(&l.canRotate, 1) 168 rotateNow := l.out > l.limit 169 l.Unlock() 170 if rotateNow { 171 l.l.Noticef("Rotating logfile...") 172 } 173 } 174 175 func (l *fileLogger) setMaxNumFiles(max int) { 176 l.Lock() 177 l.maxNumFiles = max 178 l.Unlock() 179 } 180 181 func (l *fileLogger) logDirect(label, format string, v ...any) int { 182 var entrya = [256]byte{} 183 var entry = entrya[:0] 184 if l.pid != "" { 185 entry = append(entry, l.pid...) 186 } 187 if l.time { 188 now := time.Now() 189 year, month, day := now.Date() 190 hour, min, sec := now.Clock() 191 microsec := now.Nanosecond() / 1000 192 entry = append(entry, fmt.Sprintf("%04d/%02d/%02d %02d:%02d:%02d.%06d ", 193 year, month, day, hour, min, sec, microsec)...) 194 } 195 entry = append(entry, label...) 196 entry = append(entry, fmt.Sprintf(format, v...)...) 197 entry = append(entry, '\r', '\n') 198 l.f.Write(entry) 199 return len(entry) 200 } 201 202 func (l *fileLogger) logPurge(fname string) { 203 var backups []string 204 lDir := filepath.Dir(fname) 205 lBase := filepath.Base(fname) 206 entries, err := os.ReadDir(lDir) 207 if err != nil { 208 l.logDirect(l.l.errorLabel, "Unable to read directory %q for log purge (%v), will attempt next rotation", lDir, err) 209 return 210 } 211 for _, entry := range entries { 212 if entry.IsDir() || entry.Name() == lBase || !strings.HasPrefix(entry.Name(), lBase) { 213 continue 214 } 215 if stamp, found := strings.CutPrefix(entry.Name(), fmt.Sprintf("%s%s", lBase, ".")); found { 216 _, err := time.Parse("2006:01:02:15:04:05.999999999", strings.Replace(stamp, ".", ":", 5)) 217 if err == nil { 218 backups = append(backups, entry.Name()) 219 } 220 } 221 } 222 currBackups := len(backups) 223 maxBackups := l.maxNumFiles - 1 224 if currBackups > maxBackups { 225 // backups sorted oldest to latest based on timestamped lexical filename (ReadDir) 226 for i := 0; i < currBackups-maxBackups; i++ { 227 if err := os.Remove(filepath.Join(lDir, string(os.PathSeparator), backups[i])); err != nil { 228 l.logDirect(l.l.errorLabel, "Unable to remove backup log file %q (%v), will attempt next rotation", backups[i], err) 229 // Bail fast, we'll try again next rotation 230 return 231 } 232 l.logDirect(l.l.infoLabel, "Purged log file %q", backups[i]) 233 } 234 } 235 } 236 237 func (l *fileLogger) Write(b []byte) (int, error) { 238 if atomic.LoadInt32(&l.canRotate) == 0 { 239 n, err := l.f.Write(b) 240 if err == nil { 241 atomic.AddInt64(&l.out, int64(n)) 242 } 243 return n, err 244 } 245 l.Lock() 246 n, err := l.f.Write(b) 247 if err == nil { 248 l.out += int64(n) 249 if l.out > l.limit { 250 if err := l.f.Close(); err != nil { 251 l.limit *= 2 252 l.logDirect(l.l.errorLabel, "Unable to close logfile for rotation (%v), will attempt next rotation at size %v", err, l.limit) 253 l.Unlock() 254 return n, err 255 } 256 fname := l.f.Name() 257 now := time.Now() 258 bak := fmt.Sprintf("%s.%04d.%02d.%02d.%02d.%02d.%02d.%09d", fname, 259 now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), 260 now.Second(), now.Nanosecond()) 261 os.Rename(fname, bak) 262 fileflags := os.O_WRONLY | os.O_APPEND | os.O_CREATE 263 f, err := os.OpenFile(fname, fileflags, 0660) 264 if err != nil { 265 l.Unlock() 266 panic(fmt.Sprintf("Unable to re-open the logfile %q after rotation: %v", fname, err)) 267 } 268 l.f = f 269 n := l.logDirect(l.l.infoLabel, "Rotated log, backup saved as %q", bak) 270 l.out = int64(n) 271 l.limit = l.olimit 272 if l.maxNumFiles > 0 { 273 l.logPurge(fname) 274 } 275 } 276 } 277 l.Unlock() 278 return n, err 279 } 280 281 func (l *fileLogger) close() error { 282 l.Lock() 283 if l.closed { 284 l.Unlock() 285 return nil 286 } 287 l.closed = true 288 l.Unlock() 289 return l.f.Close() 290 } 291 292 // SetSizeLimit sets the size of a logfile after which a backup 293 // is created with the file name + "year.month.day.hour.min.sec.nanosec" 294 // and the current log is truncated. 295 func (l *Logger) SetSizeLimit(limit int64) error { 296 l.Lock() 297 if l.fl == nil { 298 l.Unlock() 299 return fmt.Errorf("can set log size limit only for file logger") 300 } 301 fl := l.fl 302 l.Unlock() 303 fl.setLimit(limit) 304 return nil 305 } 306 307 // SetMaxNumFiles sets the number of archived log files that will be retained 308 func (l *Logger) SetMaxNumFiles(max int) error { 309 l.Lock() 310 if l.fl == nil { 311 l.Unlock() 312 return fmt.Errorf("can set log max number of files only for file logger") 313 } 314 fl := l.fl 315 l.Unlock() 316 fl.setMaxNumFiles(max) 317 return nil 318 } 319 320 // NewTestLogger creates a logger with output directed to Stderr with a prefix. 321 // Useful for tracing in tests when multiple servers are in the same pid 322 func NewTestLogger(prefix string, time bool) *Logger { 323 flags := 0 324 if time { 325 flags = log.LstdFlags | log.Lmicroseconds 326 } 327 l := &Logger{ 328 logger: log.New(os.Stderr, prefix, flags), 329 debug: true, 330 trace: true, 331 } 332 setColoredLabelFormats(l) 333 return l 334 } 335 336 // Close implements the io.Closer interface to clean up 337 // resources in the server's logger implementation. 338 // Caller must ensure threadsafety. 339 func (l *Logger) Close() error { 340 if l.fl != nil { 341 return l.fl.close() 342 } 343 return nil 344 } 345 346 // Generate the pid prefix string 347 func pidPrefix() string { 348 return fmt.Sprintf("[%d] ", os.Getpid()) 349 } 350 351 func setPlainLabelFormats(l *Logger) { 352 l.infoLabel = "[INF] " 353 l.debugLabel = "[DBG] " 354 l.warnLabel = "[WRN] " 355 l.errorLabel = "[ERR] " 356 l.fatalLabel = "[FTL] " 357 l.traceLabel = "[TRC] " 358 } 359 360 func setColoredLabelFormats(l *Logger) { 361 colorFormat := "[\x1b[%sm%s\x1b[0m] " 362 l.infoLabel = fmt.Sprintf(colorFormat, "32", "INF") 363 l.debugLabel = fmt.Sprintf(colorFormat, "36", "DBG") 364 l.warnLabel = fmt.Sprintf(colorFormat, "0;93", "WRN") 365 l.errorLabel = fmt.Sprintf(colorFormat, "31", "ERR") 366 l.fatalLabel = fmt.Sprintf(colorFormat, "31", "FTL") 367 l.traceLabel = fmt.Sprintf(colorFormat, "33", "TRC") 368 } 369 370 // Noticef logs a notice statement 371 func (l *Logger) Noticef(format string, v ...any) { 372 l.logger.Printf(l.infoLabel+format, v...) 373 } 374 375 // Warnf logs a notice statement 376 func (l *Logger) Warnf(format string, v ...any) { 377 l.logger.Printf(l.warnLabel+format, v...) 378 } 379 380 // Errorf logs an error statement 381 func (l *Logger) Errorf(format string, v ...any) { 382 l.logger.Printf(l.errorLabel+format, v...) 383 } 384 385 // Fatalf logs a fatal error 386 func (l *Logger) Fatalf(format string, v ...any) { 387 l.logger.Fatalf(l.fatalLabel+format, v...) 388 } 389 390 // Debugf logs a debug statement 391 func (l *Logger) Debugf(format string, v ...any) { 392 if l.debug { 393 l.logger.Printf(l.debugLabel+format, v...) 394 } 395 } 396 397 // Tracef logs a trace statement 398 func (l *Logger) Tracef(format string, v ...any) { 399 if l.trace { 400 l.logger.Printf(l.traceLabel+format, v...) 401 } 402 }