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 }