github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekalog/logger_private.go (about)

     1  // Copyright © 2018-2021. All rights reserved.
     2  // Author: Ilya Stroy.
     3  // Contacts: iyuryevich@pm.me, https://github.com/qioalice
     4  // License: https://opensource.org/licenses/MIT
     5  
     6  package ekalog
     7  
     8  import (
     9  	"fmt"
    10  	"time"
    11  	"unsafe"
    12  
    13  	"github.com/qioalice/ekago/v3/ekadeath"
    14  	"github.com/qioalice/ekago/v3/ekaerr"
    15  	"github.com/qioalice/ekago/v3/internal/ekaclike"
    16  	"github.com/qioalice/ekago/v3/internal/ekaletter"
    17  
    18  	"github.com/modern-go/reflect2"
    19  )
    20  
    21  var (
    22  	// baseLogger is default package-level Logger, that used by all package-level
    23  	// logger functions.
    24  	baseLogger *Logger
    25  
    26  	// nopLogger is a special Logger that is returned to indicate,
    27  	// that next methods must do nothing.
    28  	nopLogger *Logger
    29  )
    30  
    31  func (l *Logger) assert() {
    32  	if !l.IsValid() {
    33  		panic("Failed to do something with Logger. Logger is malformed.")
    34  	}
    35  }
    36  
    37  // levelEnabled reports whether Entry with provided Level should be handled.
    38  func (l *Logger) levelEnabled(lvl Level) bool {
    39  	return lvl <= l.integrator.MinLevelEnabled()
    40  }
    41  
    42  // derive returns a new Logger with cloned Entry based on current Logger.
    43  func (l *Logger) derive() (newLogger *Logger) {
    44  	return new(Logger).setIntegrator(l.integrator).setEntry(l.entry.clone())
    45  }
    46  
    47  // setIntegrator changes the Logger's Integrator to the passed.
    48  // It's just assignment nothing more. Useful at the method chaining.
    49  func (l *Logger) setIntegrator(newIntegrator Integrator) (this *Logger) {
    50  	l.integrator = newIntegrator
    51  	return l
    52  }
    53  
    54  // setEntry changes the Logger's Entry to the passed. Also changes parent ptr
    55  // in newEntry to being pointed to the l. Useful at the method chaining.
    56  func (l *Logger) setEntry(newEntry *Entry) (this *Logger) {
    57  	l.entry = newEntry
    58  	l.entry.l = l
    59  	return l
    60  }
    61  
    62  // addField checks whether Logger is valid, not nop Logger, makes a copy
    63  // if it's requested and adds an ekaletter.LetterField to current Logger's Entry,
    64  // if field is addable.
    65  // Returns modified current Logger or its modified copy.
    66  func (l *Logger) addField(f ekaletter.LetterField) *Logger {
    67  	l.assert()
    68  	if l == nopLogger || f.IsInvalid() || f.RemoveVary() && f.IsZero() {
    69  		return l
    70  	}
    71  	ekaletter.LAddField(l.entry.LogLetter, f)
    72  	return l
    73  }
    74  
    75  // addFields is the same as addField() but works with an array of ekaletter.LetterField,
    76  // making a copy only once if it's requested.
    77  func (l *Logger) addFields(fs []ekaletter.LetterField) *Logger {
    78  	l.assert()
    79  	if l == nopLogger || len(fs) == 0 {
    80  		return l
    81  	}
    82  	for i, n := 0, len(fs); i < n; i++ {
    83  		ekaletter.LAddFieldWithCheck(l.entry.LogLetter, fs[i])
    84  	}
    85  	return l
    86  }
    87  
    88  // addFieldsParse creates a ekaletter.LetterField objects based on passed values,
    89  // try to treating them as a key-value pairs of that fields.
    90  // It also makes a copy if it's requested and adds generated ekaletter.LetterField
    91  // to the destination Logger's Entry only if those fields are addable.
    92  // Returns modified current Logger or its modified copy.
    93  func (l *Logger) addFieldsParse(fs []any) *Logger {
    94  	l.assert()
    95  	if l == nopLogger || len(fs) == 0 {
    96  		return l
    97  	}
    98  	ekaletter.LParseTo(l.entry.LogLetter, fs, true)
    99  	return l
   100  }
   101  
   102  // log starts and manages all processes that should be passed between
   103  // forming log message and writing it.
   104  //
   105  // There are 4 big things this method shall done:
   106  //
   107  //  1. Figure out what will be used as log message's body and construct it
   108  //     if necessary (wasn't done before):
   109  //
   110  //     - Uses 'format' as just string if len(args) == 0;
   111  //     - Uses 'format' as printf-like format string if it's contains printf verbs
   112  //     and len(args) > 0, and then uses args[:N] as printf values;
   113  //     - Uses err's message as log's string if 'format' == "" and len(args) == 0;
   114  //     - Tries to extract string (or something string-like, e.g. fmt.Sprinter)
   115  //     or Golang error from 'args[0]' and then use it in an one of four cases
   116  //     described above. In that case only 'args[1:]' are allowed to be processed then.
   117  //
   118  //  2. Figure out what fields should be attached to the log message,
   119  //     Convert implicit fields (both of named/unnamed) to the explicit
   120  //     (using Entry.parseLogArgs method);
   121  //
   122  //     It guarantees that only one of 'args' or 'explicitFields' provided
   123  //     at the same time.
   124  //     If there's printf message, only 'args[N:]' or 'args[N+1:]' uses as
   125  //     explicit/implicit args, where N - printf verbs.
   126  //     N or N+1 depends by whether 'format' or 'err' was provided
   127  //     as printf-like string, or it was extracted from 'args[0]'.
   128  //
   129  //  3. Adds caller and stacktrace (if it necessary and if it wasn't provided
   130  //     by gext.errors.Error) (using Entry.addStacktrace method);
   131  //
   132  //     Uses gext.errors.Error.Stacktrace as stacktrace avoiding another one
   133  //     stacktrace generation procedure.
   134  //
   135  // 4. Finally write a message and call then death.Die() if it's fatal level.
   136  func (l *Logger) log(
   137  
   138  	lvl Level,
   139  	format string,
   140  	err *ekaerr.Error,
   141  	args []any,
   142  	fields []ekaletter.LetterField,
   143  
   144  ) *Logger {
   145  
   146  	l.assert()
   147  	if l == nopLogger || !l.levelEnabled(lvl) {
   148  		return l
   149  	}
   150  
   151  	// empty messages are skipped by default, but who knows?
   152  	if err.IsNil() && format == "" && len(args) == 0 && len(fields) == 0 {
   153  		return l
   154  	}
   155  
   156  	// At this code point Entry should be copied anyway even if it's empty message,
   157  	// even w/o body, even w/o fields, because of at least timestamp.
   158  	//
   159  	// But we don't need to copy an internal Logger's entry:
   160  	// There is no necessary to keep (and then return) Logger with modified entry.
   161  	// Because otherwise I guess it will lead to very unpredictable behaviour.
   162  	workTempEntry := l.entry.clone()
   163  
   164  	workTempEntry.Level = lvl
   165  	workTempEntry.Time = time.Now()
   166  
   167  	var (
   168  		onlyFields = false
   169  		errLetter  = ekaletter.BridgeErrorGetLetter(unsafe.Pointer(err))
   170  	)
   171  
   172  	// Try to use ekaerr.Error's last message or first arg from args
   173  	// if format is not presented.
   174  
   175  	if format == "" {
   176  		if errLetter != nil {
   177  			format = ekaletter.LPopLastMessage(errLetter)
   178  		} else if len(args) > 0 {
   179  			var (
   180  				typ1stArg   = reflect2.TypeOf(args[0])
   181  				rtype1stArg = uintptr(0)
   182  			)
   183  
   184  			if args[0] != nil {
   185  				rtype1stArg = typ1stArg.RType()
   186  			}
   187  
   188  			if rtype1stArg == ekaclike.RTypeString {
   189  				var str string
   190  				typ1stArg.UnsafeSet(unsafe.Pointer(&str), reflect2.PtrOf(args[0]))
   191  				args = args[1:]
   192  				if format = str; format == "" {
   193  					// if user pass empty string as argument,
   194  					// it means he don't want log message, only fields
   195  					onlyFields = true
   196  				}
   197  
   198  			} else if typ1stArg.Implements(ekaletter.TypeFmtStringer) {
   199  				stringer := args[0].(fmt.Stringer)
   200  				if stringer != nil {
   201  					format = stringer.String()
   202  					args = args[1:]
   203  					if format == "" {
   204  						onlyFields = true
   205  					}
   206  				}
   207  			}
   208  		}
   209  	}
   210  
   211  	ekaletter.LSetMessage(workTempEntry.LogLetter, format, false)
   212  	workTempEntry.ErrLetter = errLetter
   213  
   214  	if lvl <= l.integrator.MinLevelForStackTrace() {
   215  		workTempEntry.addStacktraceIfNotPresented()
   216  	}
   217  
   218  	// Try to extract message from 'args' if 'errLetter' == nil ('onlyFields' == false),
   219  	// but if 'errLetter' is set, it's OK to log w/o message.
   220  	switch {
   221  	case len(args) > 0:
   222  		ekaletter.LParseTo(workTempEntry.LogLetter, args, onlyFields)
   223  	case len(fields) > 0:
   224  		workTempEntry.LogLetter.Fields = fields
   225  	}
   226  
   227  	l.integrator.EncodeAndWrite(workTempEntry)
   228  
   229  	ekaerr.ReleaseError(err)
   230  	releaseEntry(workTempEntry)
   231  
   232  	switch workTempEntry.Level {
   233  	case LEVEL_EMERGENCY:
   234  		ekadeath.Die()
   235  	}
   236  
   237  	return l
   238  }