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 }