get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/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 ...interface{}) 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 ...interface{}) {
   372  	l.logger.Printf(l.infoLabel+format, v...)
   373  }
   374  
   375  // Warnf logs a notice statement
   376  func (l *Logger) Warnf(format string, v ...interface{}) {
   377  	l.logger.Printf(l.warnLabel+format, v...)
   378  }
   379  
   380  // Errorf logs an error statement
   381  func (l *Logger) Errorf(format string, v ...interface{}) {
   382  	l.logger.Printf(l.errorLabel+format, v...)
   383  }
   384  
   385  // Fatalf logs a fatal error
   386  func (l *Logger) Fatalf(format string, v ...interface{}) {
   387  	l.logger.Fatalf(l.fatalLabel+format, v...)
   388  }
   389  
   390  // Debugf logs a debug statement
   391  func (l *Logger) Debugf(format string, v ...interface{}) {
   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 ...interface{}) {
   399  	if l.trace {
   400  		l.logger.Printf(l.traceLabel+format, v...)
   401  	}
   402  }