github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/internal/ekaletter/letter.go (about)

     1  // Copyright © 2020-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 ekaletter
     7  
     8  import (
     9  	"fmt"
    10  	"unsafe"
    11  
    12  	"github.com/qioalice/ekago/v3/ekasys"
    13  	"github.com/qioalice/ekago/v3/internal/ekaclike"
    14  
    15  	"github.com/modern-go/reflect2"
    16  )
    17  
    18  type (
    19  	// Letter is a core for both of ekalog.Entry, ekaerr.Error objects.
    20  	//
    21  	// Both of them may (and should) contain message's body and some attached values.
    22  	// And that's where they are stored.
    23  	Letter struct {
    24  
    25  		// StackTrace is just stack trace, nothing more.
    26  		// It fills by ekasys.GetStackTrace() when Letter is under initializing.
    27  		//
    28  		// It can be nil if:
    29  		//
    30  		//  - This Letter belongs to ekalog.Entry, and it's linked
    31  		//    with some ekaerr.Error object to use it as a stacktrace's source.
    32  		//
    33  		//  - This Letter is initialized to be w/o stacktrace.
    34  		//    It's possible for both of ekalog.Entry, ekaerr.Error.
    35  		//
    36  		StackTrace ekasys.StackTrace
    37  
    38  		// Messages contains some messages for each stackframe from StackTrace.
    39  		//
    40  		// It's an array, each element of which has an index of stackframe from StackTrace,
    41  		// that element belongs to.
    42  		//
    43  		// It guarantees that each next element's LetterMessage.StackFrameIdx
    44  		// GTE than prev.
    45  		Messages []LetterMessage
    46  
    47  		// Fields contains some attached values (fields) for each stackframe from StackTrace.
    48  		//
    49  		// It's an array, each element of which has an index of stackframe from StackTrace,
    50  		// that element belongs to.
    51  		//
    52  		// It guarantees that each next element's LetterField.StackFrameIdx
    53  		// GTE than prev.
    54  		Fields []LetterField
    55  
    56  		// SystemFields contains only important system meta information,
    57  		// that could be generated by ekaerr.Error's or ekalog.Logger's methods.
    58  		//
    59  		// It guarantees, that all these field has set KIND_FLAG_SYSTEM bit
    60  		// at the their LetterField.Kind property.
    61  		SystemFields []LetterField
    62  
    63  		// ---------------------------- PRIVATE ---------------------------- //
    64  
    65  		// stackFrameIdx is a counter that generally uses only for ekaerr.Error object.
    66  		// It's a "pointer" to a current stackframe when stack unwinding is performing.
    67  		//
    68  		// Look:
    69  		// ekaerr.Error has a Throw() method, that does increase this counter
    70  		// (until it reach StackTrace's len -1).
    71  		//
    72  		stackFrameIdx int16
    73  	}
    74  )
    75  
    76  var (
    77  	// RTypesBeingIgnoredForParsing is an array of RTypes that will be ignored
    78  	// at the arguments parsing in LParseTo() function.
    79  	RTypesBeingIgnoredForParsing = []uintptr{RTypeLetterField, RTypeLetterFieldPtr}
    80  )
    81  
    82  // LAddField just adds passed LetterField at the end of provided Letter.
    83  func LAddField(l *Letter, f LetterField) {
    84  	f.StackFrameIdx = l.stackFrameIdx
    85  	l.Fields = append(l.Fields, f)
    86  }
    87  
    88  // LAddFieldWithCheck calls LAddField() but with some checks before.
    89  // LAddField() won't be called if provided LetterField is invalid or zero-vary.
    90  func LAddFieldWithCheck(l *Letter, f LetterField) {
    91  	if !(f.IsInvalid() || f.RemoveVary() && f.IsZero()) {
    92  		LAddField(l, f)
    93  	}
    94  }
    95  
    96  // LSetMessage adds (or overwrites if overwrite is true) message in provided Letter
    97  // that is relevant for the current stack frame idx.
    98  func LSetMessage(l *Letter, msg string, overwrite bool) {
    99  	switch lm := len(l.Messages); {
   100  
   101  	case lm > 0 && len(l.StackTrace) == 0 && overwrite:
   102  		// This isn't the first message, but an error is lightweight
   103  		// and overwrite is requested.
   104  		l.Messages[lm-1].Body += "; " + msg
   105  
   106  	case lm == 0:
   107  		// This is the first message.
   108  		fallthrough
   109  
   110  	case l.Messages[lm-1].StackFrameIdx < l.stackFrameIdx:
   111  		l.Messages = append(l.Messages, LetterMessage{
   112  			Body:          msg,
   113  			StackFrameIdx: l.stackFrameIdx,
   114  		})
   115  
   116  	case overwrite:
   117  		// Prev cond can be false only if l.Messages[lm-1].StackFrameIdx == l.stackFrameIdx
   118  		// meaning that message for the current stackframe is presented.
   119  		// So, we can overwrite it, if it's allowed.
   120  		l.Messages[lm-1].Body = msg
   121  	}
   122  }
   123  
   124  // LGetMessage returns a message that is relevant for the current stack frame idx
   125  // in provided Letter.
   126  func LGetMessage(l *Letter) string {
   127  	if lm := len(l.Messages); lm > 0 && l.Messages[lm-1].StackFrameIdx == l.stackFrameIdx {
   128  		return l.Messages[lm-1].Body
   129  	}
   130  	return ""
   131  }
   132  
   133  // LPopLastMessage returns last not empty message from provided Letter,
   134  // replacing it by empty string when found.
   135  func LPopLastMessage(l *Letter) string {
   136  	for i := len(l.Messages) - 1; i >= 0; i-- {
   137  		if l.Messages[i].Body != "" {
   138  			ret := l.Messages[i].Body
   139  			l.Messages[i].Body = ""
   140  			return ret
   141  		}
   142  	}
   143  	return ""
   144  }
   145  
   146  // LIncStackIdx increments Letter's stackIdx property if it's allowed.
   147  // Returns a new value.
   148  //
   149  // Increment won't happen (and current value is returned) if it's maximum
   150  // of allowed stack idx for the current len of stackframe.
   151  func LIncStackIdx(l *Letter) {
   152  	if n := len(l.StackTrace); n == 0 || l.stackFrameIdx+1 < int16(n) {
   153  		l.stackFrameIdx++
   154  	}
   155  }
   156  
   157  // LGetStackIdx returns Letter's stackIdx property.
   158  func LGetStackIdx(l *Letter) int16 {
   159  	return l.stackFrameIdx
   160  }
   161  
   162  // LSetStackIdx replaces a Letter's stackIdx to the provided one returning an old.
   163  // IT MUST BE USED CAREFULLY. YOU CAN GET A PANIC IF YOUR STACK INDEX
   164  // IS GREATER THAN A LENGTH OF EMBEDDED STACKFRAME.
   165  func LSetStackIdx(l *Letter, newStackIdx int16) (oldStackIdx int16) {
   166  	oldStackIdx = l.stackFrameIdx
   167  	l.stackFrameIdx = newStackIdx
   168  	return oldStackIdx
   169  }
   170  
   171  // LReset resets all internal fields, frees unnecessary RAM, preparing
   172  // it for being returned to the pool and being reused in the future.
   173  func LReset(l *Letter) *Letter {
   174  
   175  	for i, n := 0, len(l.Fields); i < n; i++ {
   176  		FieldReset(&l.Fields[i])
   177  	}
   178  
   179  	l.stackFrameIdx = 0
   180  	l.Fields = l.Fields[:0]
   181  	l.Messages = l.Messages[:0]
   182  
   183  	return l
   184  }
   185  
   186  // LParseTo is all-in-one function that parses 'args' to extract message
   187  // (if 'onlyFields' is false) and fields to the Letter.
   188  //
   189  //   - If 'onlyField' is true then only fields tries to be extracted from 'args'
   190  //     (explicit or implicit) and then saves to the Letter.
   191  //
   192  //   - If 'onlyField' is false then also a message tried to be extracted (or generated)
   193  //     from 'args' and use it as current stackframe's message
   194  //     and the rest of 'args' will be used to extract fields.
   195  //
   196  // If it's message extraction, then:
   197  //
   198  //	The first item in 'args' that can be used as message
   199  //	(string-like or something that can be stringifed) will be used to do it.
   200  //
   201  //	If it's a string and has a printf verbs, then next N values from 'args'
   202  //	will be used to generate a final string, N is a number of printf verbs in string.
   203  //
   204  //	The rest of 'args' will be parsed as a fields.
   205  //
   206  // Limitations:
   207  // If 'args' contain item that has one of the following type it will be SKIPPED:
   208  //
   209  //	*Error, Error, *Letter, Letter, *LetterItem, LetterItem.
   210  //	See InitRestrictedTypesBeingParsed() for more details.
   211  //	IT IS NOT POSSIBLE TO USE ANOTHER ERRORS OR THEIR PRIVATE TYPES AS FIELDS.
   212  //	BUILD ONE ERROR THAT WILL CONTAIN ALL YOU WANT INSTEAD OF ERROR SPAWNING.
   213  //
   214  // Requirements:
   215  // 'li' != nil. Otherwise UB (may panic).
   216  //
   217  // Used to:
   218  // - Add fields (explicit/implicit) into *Error,
   219  // - Add fields (explicit/implicit) or/and message to *Logger's *Entry.
   220  func LParseTo(l *Letter, args []any, onlyFields bool) {
   221  
   222  	var (
   223  		message          = LGetMessage(l)
   224  		messageArgs      []any
   225  		messageNeedsArgs int
   226  	)
   227  
   228  	if len(args) > 0 && !onlyFields {
   229  		messageNeedsArgs = PrintfVerbsCount(&message)
   230  		if messageNeedsArgs > 0 {
   231  			messageArgs = make([]any, 0, messageNeedsArgs)
   232  		}
   233  	}
   234  
   235  	var (
   236  		fieldKey   string // below loop's var
   237  		messageBak string
   238  	)
   239  
   240  	if onlyFields {
   241  		messageBak = message
   242  		message = "message variable must not be empty to avoid its generating"
   243  	}
   244  
   245  	// isRestrictedType is an auxiliary function that will be used in the loop above
   246  	// to figure out whether arg item must be ignored.
   247  	isRestrictedType := func(rtype uintptr, basedOn []uintptr) bool {
   248  		for i, n := 0, len(basedOn); i < n; i++ {
   249  			if rtype == basedOn[i] {
   250  				return true
   251  			}
   252  		}
   253  		return false
   254  	}
   255  
   256  	for i, n := 0, len(args); i < n; i++ {
   257  
   258  		var (
   259  			typeArg  = reflect2.TypeOf(args[i])
   260  			rtypeArg = uintptr(0)
   261  		)
   262  
   263  		if args[i] != nil {
   264  			rtypeArg = typeArg.RType()
   265  		}
   266  
   267  		// let's recognize what kind of arg we have
   268  		switch {
   269  
   270  		case rtypeArg == RTypeLetterField:
   271  			var f LetterField
   272  			typeArg.UnsafeSet(unsafe.Pointer(&f), reflect2.PtrOf(args[i]))
   273  			LAddFieldWithCheck(l, f)
   274  
   275  		case rtypeArg == RTypeLetterFieldPtr:
   276  			var f *LetterField
   277  			typeArg.UnsafeSet(unsafe.Pointer(&f), reflect2.PtrOf(args[i]))
   278  			if f != nil {
   279  				LAddFieldWithCheck(l, *f)
   280  			}
   281  
   282  		case isRestrictedType(rtypeArg, RTypesBeingIgnoredForParsing):
   283  			// DO NOTHING
   284  
   285  		case fieldKey != "":
   286  			// it guarantees that if fieldKey is not empty, message's body
   287  			// is already formed (if requested)
   288  			LAddFieldWithCheck(l, FAny(fieldKey, args[i]))
   289  			fieldKey = ""
   290  
   291  		case messageNeedsArgs > 0:
   292  			messageNeedsArgs--
   293  			fallthrough
   294  
   295  		case message == "" && len(messageArgs) == 0:
   296  			// there is no message's body still and we'll use current arg as message's body.
   297  			messageArgs = append(messageArgs, args[i])
   298  
   299  		case rtypeArg == ekaclike.RTypeString:
   300  			// at this code point arg could be only field's key or unnamed arg
   301  			// well, looks like it's a key.
   302  			typeArg.UnsafeSet(unsafe.Pointer(&fieldKey), reflect2.PtrOf(args[i]))
   303  
   304  			// it can be "" (empty string) then handle it as unnamed field
   305  			if fieldKey != "" {
   306  				break // break switch, go to next loop's iter
   307  			}
   308  			fallthrough // fallthrough can't be in 'if' statement
   309  
   310  		default:
   311  			LAddFieldWithCheck(l, FAny("", args[i]))
   312  		}
   313  	}
   314  
   315  	if onlyFields {
   316  		message = messageBak
   317  		return
   318  	}
   319  
   320  	// if after loop fieldKey != "", it's the last unnamed field
   321  	if fieldKey != "" {
   322  		LAddFieldWithCheck(l, FString("", fieldKey))
   323  	}
   324  
   325  	// TODO: What do we have to do if we had printf verbs < than required ones?
   326  	//  Now it's handled by fmt.Printf, but I guess we shall to handle it manually.
   327  
   328  	switch hasPrintArgs := len(messageArgs) > 0; {
   329  
   330  	case hasPrintArgs && message != "":
   331  		LSetMessage(l, fmt.Sprintf(message, messageArgs...), true)
   332  
   333  	case hasPrintArgs && message == "":
   334  		LSetMessage(l, fmt.Sprint(messageArgs...), true)
   335  
   336  	default:
   337  		// already formed
   338  	}
   339  
   340  	// TODO: Shall we do something else with empty messages?
   341  }