pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/log/log.go (about)

     1  // Package log provides an improved logger
     2  package log
     3  
     4  // ////////////////////////////////////////////////////////////////////////////////// //
     5  //                                                                                    //
     6  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     7  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     8  //                                                                                    //
     9  // ////////////////////////////////////////////////////////////////////////////////// //
    10  
    11  import (
    12  	"bufio"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"os"
    17  	"strings"
    18  	"sync"
    19  	"time"
    20  
    21  	"pkg.re/essentialkaos/ek.v12/fmtc"
    22  )
    23  
    24  // ////////////////////////////////////////////////////////////////////////////////// //
    25  
    26  const (
    27  	DEBUG uint8 = 0  // DEBUG debug messages
    28  	INFO        = 1  // INFO info messages
    29  	WARN        = 2  // WARN warning messages
    30  	ERROR       = 3  // ERROR error messages
    31  	CRIT        = 4  // CRIT critical error messages
    32  	AUX         = 99 // AUX unskipable messages (separators, headers, etc...)
    33  )
    34  
    35  // ////////////////////////////////////////////////////////////////////////////////// //
    36  
    37  // Logger is a basic logger struct
    38  type Logger struct {
    39  	PrefixDebug bool // Prefix for debug messages
    40  	PrefixInfo  bool // Prefix for info messages
    41  	PrefixWarn  bool // Prefix for warning messages
    42  	PrefixError bool // Prefix for error messages
    43  	PrefixCrit  bool // Prefix for critical error messages
    44  
    45  	UseColors bool // Enable ANSI escape codes for colors in output
    46  
    47  	file     string
    48  	fd       *os.File
    49  	w        *bufio.Writer
    50  	mu       *sync.Mutex
    51  	minLevel uint8
    52  	perms    os.FileMode
    53  	useBufIO bool
    54  }
    55  
    56  // ////////////////////////////////////////////////////////////////////////////////// //
    57  
    58  // Global is global logger struct
    59  var Global = &Logger{
    60  	PrefixWarn:  true,
    61  	PrefixError: true,
    62  	PrefixCrit:  true,
    63  
    64  	minLevel: INFO,
    65  	mu:       &sync.Mutex{},
    66  }
    67  
    68  // ////////////////////////////////////////////////////////////////////////////////// //
    69  
    70  // PrefixMap is map with messages prefixes
    71  var PrefixMap = map[uint8]string{
    72  	DEBUG: "[DEBUG]",
    73  	INFO:  "[INFO]",
    74  	WARN:  "[WARNING]",
    75  	ERROR: "[ERROR]",
    76  	CRIT:  "[CRITICAL]",
    77  }
    78  
    79  // Colors colors is map with fmtc color tags for every level
    80  var Colors = map[uint8]string{
    81  	DEBUG: "{s-}",
    82  	INFO:  "",
    83  	WARN:  "{y}",
    84  	ERROR: "{r}",
    85  	CRIT:  "{m}",
    86  }
    87  
    88  // TimeFormat contains format string for time in logs
    89  var TimeFormat = "2006/01/02 15:04:05.000"
    90  
    91  // ////////////////////////////////////////////////////////////////////////////////// //
    92  
    93  // Errors
    94  var (
    95  	// ErrLoggerIsNil is returned by Logger struct methods if struct is nil
    96  	ErrLoggerIsNil = errors.New("Logger is nil or not created properly")
    97  
    98  	// ErrUnexpectedLevel is returned by the MinLevel method if given level is unknown
    99  	ErrUnexpectedLevel = errors.New("Unexpected level type")
   100  
   101  	// ErrOutputNotSet is returned by the Reopen method if output file is not set
   102  	ErrOutputNotSet = errors.New("Output file is not set")
   103  )
   104  
   105  // ////////////////////////////////////////////////////////////////////////////////// //
   106  
   107  var logLevelsNames = map[string]uint8{
   108  	"debug":    0,
   109  	"info":     1,
   110  	"warn":     2,
   111  	"warning":  2,
   112  	"error":    3,
   113  	"crit":     4,
   114  	"critical": 4,
   115  }
   116  
   117  // ////////////////////////////////////////////////////////////////////////////////// //
   118  
   119  // New creates new logger struct
   120  func New(file string, perms os.FileMode) (*Logger, error) {
   121  	logger := &Logger{
   122  		PrefixWarn:  true,
   123  		PrefixCrit:  true,
   124  		PrefixError: true,
   125  
   126  		minLevel: INFO,
   127  		mu:       &sync.Mutex{},
   128  	}
   129  
   130  	err := logger.Set(file, perms)
   131  
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	return logger, nil
   137  }
   138  
   139  // Reopen close file descriptor for global logger and open it again
   140  // Useful for log rotation
   141  func Reopen() error {
   142  	return Global.Reopen()
   143  }
   144  
   145  // MinLevel defines minimal logging level
   146  func MinLevel(level interface{}) error {
   147  	return Global.MinLevel(level)
   148  }
   149  
   150  // Set change global logger output target
   151  func Set(file string, perms os.FileMode) error {
   152  	return Global.Set(file, perms)
   153  }
   154  
   155  // EnableBufIO enable buffered I/O
   156  func EnableBufIO(interval time.Duration) {
   157  	Global.EnableBufIO(interval)
   158  }
   159  
   160  // Flush write buffered data to file
   161  func Flush() error {
   162  	return Global.Flush()
   163  }
   164  
   165  // Print write message to global logger output
   166  func Print(level uint8, f string, a ...interface{}) error {
   167  	return Global.Print(level, f, a...)
   168  }
   169  
   170  // Debug write debug message to global logger output
   171  func Debug(f string, a ...interface{}) error {
   172  	return Global.Debug(f, a...)
   173  }
   174  
   175  // Info write info message to global logger output
   176  func Info(f string, a ...interface{}) error {
   177  	return Global.Info(f, a...)
   178  }
   179  
   180  // Warn write warning message to global logger output
   181  func Warn(f string, a ...interface{}) error {
   182  	return Global.Warn(f, a...)
   183  }
   184  
   185  // Error write error message to global logger output
   186  func Error(f string, a ...interface{}) error {
   187  	return Global.Error(f, a...)
   188  }
   189  
   190  // Crit write critical message to global logger output
   191  func Crit(f string, a ...interface{}) error {
   192  	return Global.Crit(f, a...)
   193  }
   194  
   195  // Aux write unskippable message (for separators/headers)
   196  func Aux(f string, a ...interface{}) error {
   197  	return Global.Aux(f, a...)
   198  }
   199  
   200  // ////////////////////////////////////////////////////////////////////////////////// //
   201  
   202  // Reopen close file descriptor and open again
   203  // Useful for log rotation
   204  func (l *Logger) Reopen() error {
   205  	if l == nil || l.mu == nil {
   206  		return ErrLoggerIsNil
   207  	}
   208  
   209  	l.mu.Lock()
   210  
   211  	if l.fd == nil {
   212  		l.mu.Unlock()
   213  		return ErrOutputNotSet
   214  	}
   215  
   216  	if l.w != nil {
   217  		l.w.Flush()
   218  	}
   219  
   220  	l.fd.Close()
   221  	l.mu.Unlock()
   222  
   223  	return l.Set(l.file, l.perms)
   224  }
   225  
   226  // MinLevel defines minimal logging level
   227  func (l *Logger) MinLevel(level interface{}) error {
   228  	if l == nil || l.mu == nil {
   229  		return ErrLoggerIsNil
   230  	}
   231  
   232  	l.mu.Lock()
   233  	defer l.mu.Unlock()
   234  
   235  	levelCode, err := convertMinLevelValue(level)
   236  
   237  	if err != nil {
   238  		return err
   239  	}
   240  
   241  	if levelCode > CRIT {
   242  		levelCode = CRIT
   243  	}
   244  
   245  	l.minLevel = levelCode
   246  
   247  	return nil
   248  }
   249  
   250  // EnableBufIO enable buffered I/O support
   251  func (l *Logger) EnableBufIO(interval time.Duration) {
   252  	if l == nil || l.mu == nil {
   253  		return
   254  	}
   255  
   256  	l.mu.Lock()
   257  	defer l.mu.Unlock()
   258  
   259  	l.useBufIO = true
   260  
   261  	if l.fd != nil {
   262  		l.w = bufio.NewWriter(l.fd)
   263  	}
   264  
   265  	go l.flushDaemon(interval)
   266  }
   267  
   268  // Set change logger output target
   269  func (l *Logger) Set(file string, perms os.FileMode) error {
   270  	if l == nil || l.mu == nil {
   271  		return ErrLoggerIsNil
   272  	}
   273  
   274  	l.mu.Lock()
   275  	defer l.mu.Unlock()
   276  
   277  	fd, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, perms)
   278  
   279  	if err != nil {
   280  		return err
   281  	}
   282  
   283  	// Flush data if writer exist
   284  	if l.w != nil {
   285  		l.w.Flush()
   286  		l.w = nil
   287  	}
   288  
   289  	if l.fd != nil {
   290  		l.fd.Close()
   291  		l.fd = nil
   292  	}
   293  
   294  	l.fd, l.file, l.perms = fd, file, perms
   295  
   296  	if l.useBufIO {
   297  		l.w = bufio.NewWriter(l.fd)
   298  	}
   299  
   300  	return nil
   301  }
   302  
   303  // Print write message to logger output
   304  func (l *Logger) Print(level uint8, f string, a ...interface{}) error {
   305  	if l == nil || l.mu == nil {
   306  		return ErrLoggerIsNil
   307  	}
   308  
   309  	l.mu.Lock()
   310  	defer l.mu.Unlock()
   311  
   312  	if l.minLevel > level {
   313  		return nil
   314  	}
   315  
   316  	w := l.getWritter(level)
   317  	showPrefix := l.showPrefix(level)
   318  
   319  	if f == "" || f[len(f)-1:] != "\n" {
   320  		f += "\n"
   321  	}
   322  
   323  	var err error
   324  
   325  	switch {
   326  	case l.UseColors && showPrefix:
   327  		_, err = fmtc.Fprintf(w, "{s-}%s{!} "+Colors[level]+"%s %s{!}", getTime(), PrefixMap[level], fmt.Sprintf(f, a...))
   328  	case l.UseColors && !showPrefix:
   329  		_, err = fmtc.Fprintf(w, "{s-}%s{!} "+Colors[level]+"%s{!}", getTime(), fmt.Sprintf(f, a...))
   330  	case !l.UseColors && showPrefix:
   331  		_, err = fmt.Fprintf(w, "%s %s %s", getTime(), PrefixMap[level], fmt.Sprintf(f, a...))
   332  	case !l.UseColors && !showPrefix:
   333  		_, err = fmt.Fprintf(w, "%s %s", getTime(), fmt.Sprintf(f, a...))
   334  	}
   335  
   336  	return err
   337  }
   338  
   339  // Flush write buffered data to file
   340  func (l *Logger) Flush() error {
   341  	if l == nil || l.mu == nil {
   342  		return ErrLoggerIsNil
   343  	}
   344  
   345  	l.mu.Lock()
   346  
   347  	if l.w == nil {
   348  		l.mu.Unlock()
   349  		return nil
   350  	}
   351  
   352  	err := l.w.Flush()
   353  
   354  	l.mu.Unlock()
   355  
   356  	return err
   357  }
   358  
   359  // Debug write debug message to logger output
   360  func (l *Logger) Debug(f string, a ...interface{}) error {
   361  	if l == nil || l.mu == nil {
   362  		return ErrLoggerIsNil
   363  	}
   364  
   365  	return l.Print(DEBUG, f, a...)
   366  }
   367  
   368  // Info write info message to logger output
   369  func (l *Logger) Info(f string, a ...interface{}) error {
   370  	if l == nil || l.mu == nil {
   371  		return ErrLoggerIsNil
   372  	}
   373  
   374  	return l.Print(INFO, f, a...)
   375  }
   376  
   377  // Warn write warning message to logger output
   378  func (l *Logger) Warn(f string, a ...interface{}) error {
   379  	if l == nil || l.mu == nil {
   380  		return ErrLoggerIsNil
   381  	}
   382  
   383  	return l.Print(WARN, f, a...)
   384  }
   385  
   386  // Error write error message to logger output
   387  func (l *Logger) Error(f string, a ...interface{}) error {
   388  	if l == nil || l.mu == nil {
   389  		return ErrLoggerIsNil
   390  	}
   391  
   392  	return l.Print(ERROR, f, a...)
   393  }
   394  
   395  // Crit write critical message to logger output
   396  func (l *Logger) Crit(f string, a ...interface{}) error {
   397  	if l == nil || l.mu == nil {
   398  		return ErrLoggerIsNil
   399  	}
   400  
   401  	return l.Print(CRIT, f, a...)
   402  }
   403  
   404  // Aux write unfiltered message (for separators/headers) to logger output
   405  func (l *Logger) Aux(f string, a ...interface{}) error {
   406  	if l == nil || l.mu == nil {
   407  		return ErrLoggerIsNil
   408  	}
   409  
   410  	return l.Print(AUX, f, a...)
   411  }
   412  
   413  // ////////////////////////////////////////////////////////////////////////////////// //
   414  
   415  func (l *Logger) getWritter(level uint8) io.Writer {
   416  	var w io.Writer
   417  
   418  	if l.fd == nil {
   419  		switch level {
   420  		case ERROR, CRIT:
   421  			w = os.Stderr
   422  		default:
   423  			w = os.Stdout
   424  		}
   425  	} else {
   426  		if l.w != nil {
   427  			w = l.w
   428  		} else {
   429  			w = l.fd
   430  		}
   431  	}
   432  
   433  	return w
   434  }
   435  
   436  func (l *Logger) showPrefix(level uint8) bool {
   437  	switch {
   438  	case level == DEBUG && l.PrefixDebug,
   439  		level == INFO && l.PrefixInfo,
   440  		level == WARN && l.PrefixWarn,
   441  		level == ERROR && l.PrefixError,
   442  		level == CRIT && l.PrefixCrit:
   443  		return true
   444  	}
   445  
   446  	return false
   447  }
   448  
   449  func (l *Logger) flushDaemon(interval time.Duration) {
   450  	for range time.NewTicker(interval).C {
   451  		l.Flush()
   452  	}
   453  }
   454  
   455  // ////////////////////////////////////////////////////////////////////////////////// //
   456  
   457  func getTime() string {
   458  	return "[ " + time.Now().Format(TimeFormat) + " ]"
   459  }
   460  
   461  func convertMinLevelValue(level interface{}) (uint8, error) {
   462  	switch u := level.(type) {
   463  
   464  	case int:
   465  		return uint8(u), nil
   466  
   467  	case int8:
   468  		return uint8(u), nil
   469  
   470  	case int16:
   471  		return uint8(u), nil
   472  
   473  	case int32:
   474  		return uint8(u), nil
   475  
   476  	case int64:
   477  		return uint8(u), nil
   478  
   479  	case uint:
   480  		return uint8(u), nil
   481  
   482  	case uint8:
   483  		return uint8(u), nil
   484  
   485  	case uint16:
   486  		return uint8(u), nil
   487  
   488  	case uint32:
   489  		return uint8(u), nil
   490  
   491  	case uint64:
   492  		return uint8(u), nil
   493  
   494  	case float32:
   495  		return uint8(u), nil
   496  
   497  	case float64:
   498  		return uint8(u), nil
   499  
   500  	case string:
   501  		code, ok := logLevelsNames[strings.ToLower(level.(string))]
   502  
   503  		if !ok {
   504  			return 255, errors.New("Unknown level " + level.(string))
   505  		}
   506  
   507  		return code, nil
   508  	}
   509  
   510  	return 255, ErrUnexpectedLevel
   511  }