github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekaerr/error.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 ekaerr 7 8 import ( 9 "fmt" 10 "strings" 11 "time" 12 13 "github.com/qioalice/ekago/v3/internal/ekaletter" 14 ) 15 16 type ( 17 // Error is an object representation of your abstract error. 18 // Error accumulates and stores your data that will help you to log it later. 19 // 20 // In your code you must now use *Error as error indicating. Like 21 // func foo() *Error 22 // You still may return nil if no error has occurred, or create an Error 23 // object and return it (by reference). 24 // 25 // TLDR: 26 // - DO NOT CREATE ERROR OBJECTS MANUALLY, USE Class's CONSTRUCTORS INSTEAD. 27 // - DO NOT FORGET TO USE Throw(). 28 // - IF YOU WANT TO DO SOMETHING WITH YOUR ERROR, DO IT BEFORE LOGGING. 29 // - ALL ERROR OBJECTS ARE THREAD-UNSAFE. AVOID POTENTIAL DATA RACES! 30 // - NEVER USE ERROR OBJECT AS VALUE, ALWAYS USE BY REFERENCE. 31 // 32 // ----- 33 // 34 // ERROR OBJECTS CREATED MANUALLY CONSIDERED NOT INITIALIZED AND WILL NOT 35 // WORK PROPERLY, WILL NOT CONTAIN ANY DATA AND WILL NOT WORK AT ALL! 36 // 37 // Use Class.New(), Class.Wrap(), Class.LightNew(), Class.LightWrap() methods 38 // to create an *Error object. 39 // 40 // BECAUSE OF THE MAIN IDEA OF THIS PACKAGE AND EKALOG PACKAGE, 41 // ERROR AND ITS LOGGING ARE STRONGLY LINKED AND ERROR EXISTENCE WITHOUT LOGGING 42 // IS MEANINGLESS. 43 // 44 // In accordance with the above, and in pursuit of better performance 45 // (decreasing unnecessary allocations / freeing => RAM reusing) 46 // once allocated *Error objects may be reused in the future. 47 // Because of that, follow these rules: 48 // 49 // IF YOU LOG AN ERROR, YOU MUST NOT USE AN ERROR OBJECT THEN! 50 // DO WHATEVER YOU WANT WITH AN ERROR OBJECT BEFORE YOU WILL LOG IT USING EKALOG. 51 // AFTER YOU LOG YOUR ERROR THAT ERROR OBJECT WILL BE BROKEN AND CANNOT BE USED. 52 // 53 // This is because after calling error's log finisher 54 // ( from https://github.com/qioalice/ekago/ekaerr/error_ekalog.go ) 55 // an internal part of *Error object is returned to the pool for being reused 56 // (RAM optimisation). 57 // 58 // If you do not want to log an error but want to return it manually to the pool 59 // (I don't know the case when you needed it, but whatever) you can just call 60 // ReleaseError(err). 61 // If you don't do it, an allocated Error will be returned to the pool automatically 62 // when it goes out from the scope. 63 // 64 // ----- 65 // 66 // Lightweight errors. 67 // 68 // Lightweight errors is just the same Error but w/o stacktrace generating. 69 // You can also add fields or messages, but they won't be linked to some stacktrace. 70 // Thus, Throw() call in that case is meaningless and will do nothing. 71 // 72 // Often it's useful when you don't want to log your error but do something instead. 73 // 74 // If you log lightweight Error, make sure your encoder supports lightweight errors. 75 // Both of ekalog.CI_ConsoleEncoder, ekalog.CI_JSONEncoder provides that. 76 // 77 // ----- 78 // 79 // Error's ID. 80 // 81 // Each Error object (even lightweight) has its own ID. 82 // You can use that ID to find and determine Error by its ID. 83 // 84 // Earlier an UUIDv4 was used to generate ID. 85 // Since 3.0 ver, an ULID is used instead. 86 // 87 // Read more about ULID here: https://github.com/ulid/spec 88 // and here https://github.com/oklog/ulid . 89 Error struct { 90 91 // letter is the main internal part of Error object. 92 // It stores a stacktrace, fields, messages, public message, unique error's ID. 93 // 94 // If it's nil, the Error object is considered broken and not valid. 95 // Breaks after Error's logging. Under pool reusing (RAM optimisation). 96 // 97 // See https://github.com/qioalice/ekago/internal/letter/letter.go . 98 letter *ekaletter.Letter 99 100 // classID is just an ID of Class what has been used to create this object. 101 classID ClassID 102 103 // namespaceID is just an ID of Namespace 104 // the Class that has been used to create this object, belongs to. 105 namespaceID NamespaceID 106 107 needSetFinalizer bool 108 } 109 ) 110 111 // IsValid reports whether Error is valid Error object or not. 112 // 113 // It returns false if either Error is nil, or Error has not been initialized properly 114 // (instantiated manually instead of Class's constructors calling). 115 // 116 // You can also use IsNil() and IsNotNil() to make your code more clean. 117 func (e *Error) IsValid() bool { 118 return e != nil && e.letter != nil 119 } 120 121 // IsNotNil is IsValid() alias. Does absolutely the same thing. 122 // Introduced to increase your code's cleaning. 123 func (e *Error) IsNotNil() bool { 124 return e.IsValid() 125 } 126 127 // IsNil is `!Error.IsValid()` code alias. Does exactly that thing, nothing more. 128 // Introduced to increase your code's cleaning (and easy chaining typing). 129 func (e *Error) IsNil() bool { 130 return !e.IsValid() 131 } 132 133 // Throw is an OOP style of raising an error up. 134 // 135 // YOU MUST CALL THIS METHOD EACH TIME YOU RETURNING AN ERROR OBJECT FROM THE FUNC. 136 // THIS CALL MUST BE THE LAST ONE AT THE RETURN STATEMENT. 137 // 138 // Nil safe. Returns this. 139 // 140 // Typically, you have two cases you must follow. 141 // 1. An Error instantiating: 142 // func foo() *Error { 143 // return ekaerr.IllegalState.New("something happen").Throw() 144 // } 145 // 2. Already existed error raising up: 146 // (an example with adding custom stack frame's message and field) 147 // if err := foo(); err != nil { 148 // return err.S("foo failed").W("id", 42).Throw() 149 // } 150 // 151 // Does nothing for lightweight errors. 152 // Nil safe. 153 func (e *Error) Throw() *Error { 154 if e.IsValid() { 155 ekaletter.LIncStackIdx(e.letter) 156 } 157 return e 158 } 159 160 // AddMessage adds a message to your current Error's stack frame. 161 // Nil safe. Returns this. 162 func (e *Error) AddMessage(message string) *Error { 163 if e.IsValid() { 164 if message = strings.TrimSpace(message); message != "" { 165 ekaletter.LSetMessage(e.letter, message, true) 166 } 167 } 168 return e 169 } 170 171 // With adds provided ekaletter.LetterField to your current Error stack frame. 172 // Nil safe. Returns this. 173 func (e *Error) With(f ekaletter.LetterField) *Error { return e.addField(f) } 174 175 // Methods below are code-generated. 176 177 func (e *Error) WithBool(key string, value bool) *Error { 178 return e.addField(ekaletter.FBool(key, value)) 179 } 180 func (e *Error) WithInt(key string, value int) *Error { 181 return e.addField(ekaletter.FInt(key, value)) 182 } 183 func (e *Error) WithInt8(key string, value int8) *Error { 184 return e.addField(ekaletter.FInt8(key, value)) 185 } 186 func (e *Error) WithInt16(key string, value int16) *Error { 187 return e.addField(ekaletter.FInt16(key, value)) 188 } 189 func (e *Error) WithInt32(key string, value int32) *Error { 190 return e.addField(ekaletter.FInt32(key, value)) 191 } 192 func (e *Error) WithInt64(key string, value int64) *Error { 193 return e.addField(ekaletter.FInt64(key, value)) 194 } 195 func (e *Error) WithUint(key string, value uint) *Error { 196 return e.addField(ekaletter.FUint(key, value)) 197 } 198 func (e *Error) WithUint8(key string, value uint8) *Error { 199 return e.addField(ekaletter.FUint8(key, value)) 200 } 201 func (e *Error) WithUint16(key string, value uint16) *Error { 202 return e.addField(ekaletter.FUint16(key, value)) 203 } 204 func (e *Error) WithUint32(key string, value uint32) *Error { 205 return e.addField(ekaletter.FUint32(key, value)) 206 } 207 func (e *Error) WithUint64(key string, value uint64) *Error { 208 return e.addField(ekaletter.FUint64(key, value)) 209 } 210 func (e *Error) WithUintptr(key string, value uintptr) *Error { 211 return e.addField(ekaletter.FUintptr(key, value)) 212 } 213 func (e *Error) WithFloat32(key string, value float32) *Error { 214 return e.addField(ekaletter.FFloat32(key, value)) 215 } 216 func (e *Error) WithFloat64(key string, value float64) *Error { 217 return e.addField(ekaletter.FFloat64(key, value)) 218 } 219 func (e *Error) WithComplex64(key string, value complex64) *Error { 220 return e.addField(ekaletter.FComplex64(key, value)) 221 } 222 func (e *Error) WithComplex128(key string, value complex128) *Error { 223 return e.addField(ekaletter.FComplex128(key, value)) 224 } 225 func (e *Error) WithString(key string, value string) *Error { 226 return e.addField(ekaletter.FString(key, value)) 227 } 228 func (e *Error) WithStringFromBytes(key string, value []byte) *Error { 229 return e.addField(ekaletter.FStringFromBytes(key, value)) 230 } 231 func (e *Error) WithBoolp(key string, value *bool) *Error { 232 return e.addField(ekaletter.FBoolp(key, value)) 233 } 234 func (e *Error) WithIntp(key string, value *int) *Error { 235 return e.addField(ekaletter.FIntp(key, value)) 236 } 237 func (e *Error) WithInt8p(key string, value *int8) *Error { 238 return e.addField(ekaletter.FInt8p(key, value)) 239 } 240 func (e *Error) WithInt16p(key string, value *int16) *Error { 241 return e.addField(ekaletter.FInt16p(key, value)) 242 } 243 func (e *Error) WithInt32p(key string, value *int32) *Error { 244 return e.addField(ekaletter.FInt32p(key, value)) 245 } 246 func (e *Error) WithInt64p(key string, value *int64) *Error { 247 return e.addField(ekaletter.FInt64p(key, value)) 248 } 249 func (e *Error) WithUintp(key string, value *uint) *Error { 250 return e.addField(ekaletter.FUintp(key, value)) 251 } 252 func (e *Error) WithUint8p(key string, value *uint8) *Error { 253 return e.addField(ekaletter.FUint8p(key, value)) 254 } 255 func (e *Error) WithUint16p(key string, value *uint16) *Error { 256 return e.addField(ekaletter.FUint16p(key, value)) 257 } 258 func (e *Error) WithUint32p(key string, value *uint32) *Error { 259 return e.addField(ekaletter.FUint32p(key, value)) 260 } 261 func (e *Error) WithUint64p(key string, value *uint64) *Error { 262 return e.addField(ekaletter.FUint64p(key, value)) 263 } 264 func (e *Error) WithFloat32p(key string, value *float32) *Error { 265 return e.addField(ekaletter.FFloat32p(key, value)) 266 } 267 func (e *Error) WithFloat64p(key string, value *float64) *Error { 268 return e.addField(ekaletter.FFloat64p(key, value)) 269 } 270 func (e *Error) WithStringp(key string, value *string) *Error { 271 return e.addField(ekaletter.FStringp(key, value)) 272 } 273 func (e *Error) WithType(key string, value any) *Error { 274 return e.addField(ekaletter.FType(key, value)) 275 } 276 func (e *Error) WithStringer(key string, value fmt.Stringer) *Error { 277 return e.addField(ekaletter.FStringer(key, value)) 278 } 279 func (e *Error) WithAddr(key string, value any) *Error { 280 return e.addField(ekaletter.FAddr(key, value)) 281 } 282 func (e *Error) WithUnixFromStd(key string, value time.Time) *Error { 283 return e.addField(ekaletter.FUnixFromStd(key, value)) 284 } 285 func (e *Error) WithUnixNanoFromStd(key string, value time.Time) *Error { 286 return e.addField(ekaletter.FUnixNanoFromStd(key, value)) 287 } 288 func (e *Error) WithUnix(key string, value int64) *Error { 289 return e.addField(ekaletter.FUnix(key, value)) 290 } 291 func (e *Error) WithUnixNano(key string, value int64) *Error { 292 return e.addField(ekaletter.FUnixNano(key, value)) 293 } 294 func (e *Error) WithDuration(key string, value time.Duration) *Error { 295 return e.addField(ekaletter.FDuration(key, value)) 296 } 297 func (e *Error) WithArray(key string, value any) *Error { 298 return e.addField(ekaletter.FArray(key, value)) 299 } 300 func (e *Error) WithObject(key string, value any) *Error { 301 return e.addField(ekaletter.FObject(key, value)) 302 } 303 func (e *Error) WithMap(key string, value any) *Error { 304 return e.addField(ekaletter.FMap(key, value)) 305 } 306 func (e *Error) WithExtractedMap(key string, value map[string]any) *Error { 307 return e.addField(ekaletter.FExtractedMap(key, value)) 308 } 309 func (e *Error) WithAny(key string, value any) *Error { 310 return e.addField(ekaletter.FAny(key, value)) 311 } 312 313 func (e *Error) WithMany(fields ...ekaletter.LetterField) *Error { 314 return e.addFields(fields) 315 } 316 317 func (e *Error) WithManyAny(fields ...any) *Error { 318 return e.addFieldsParse(fields, true) 319 } 320 321 func (e *Error) WithDescription(description string) *Error { 322 return e.WithString("description", description) 323 } 324 325 // Apply calls f callback passing the current Error object into and returning 326 // the Error object, callback is return what. 327 // Nil safe. 328 // 329 // Does nothing if f is nil. 330 // 331 // Why? 332 // You can write your own fields appender like: 333 // 334 // func (this *YourType) errAddIdentifiers(err *ekaerr.Error) *ekaerr.Error { 335 // return err.WithManyAny("id", this.ID, "date", this.Date) 336 // } 337 // 338 // And then use it like: 339 // 340 // yt := new(YourType) 341 // return ekaerr.IllegalState.New("Unexpected state of world"). 342 // Apply(yt.errAddIdentifiers). 343 // Throw() 344 // 345 // Brilliant, isn't? And most importantly it's so clean. 346 func (e *Error) Apply(f func(err *Error) *Error) *Error { 347 if e.IsValid() && f != nil { 348 return f(e) 349 } 350 return e 351 } 352 353 // Is reports whether Error has been instantiated by cls Class's constructors. 354 // Returns false if either Error is not valid or Class is invalid. 355 // Nil safe. 356 func (e *Error) Is(cls Class) bool { 357 return e.IsValid() && isValidClassID(cls.id) && e.classID == cls.id 358 } 359 360 // IsAny reports whether Error belongs to at least one of passed cls Class 361 // (has been instantiated using one of them). 362 // Returns false if Error is not valid or no one class has been passed. 363 // Nil-safe. 364 func (e *Error) IsAny(cls ...Class) bool { 365 return e.is(cls, false) 366 } 367 368 // Of reports whether Error has been instantiated by some Class that belongs to 369 // ns Namespace. Returns false if either Error is not valid or Namespace is invalid. 370 // Nil safe. 371 func (e *Error) Of(ns Namespace) bool { 372 return e.IsValid() && isValidNamespaceID(ns.id) && e.namespaceID == ns.id 373 } 374 375 // OfAny reports whether Error belongs to at least one of passed nss Namespace 376 // (has been instantiated by the Class that belongs to one of nss Namespace). 377 // Returns false if either Error is not valid or no one namespace has been passed. 378 // Nil safe. 379 func (e *Error) OfAny(nss ...Namespace) bool { 380 return e.of(nss) 381 } 382 383 // IsAnyDeep reports whether Error belongs to at least one of passed cls Class 384 // or any of its parent (base) Class is the same as one of passed cls. 385 // Returns false if Error is not valid or no one class has been passed. 386 // Nil-safe. 387 // 388 // IsAnyDeep has increased algorithmic complexity (uses recursive algorithm) 389 // and MUCH SLOWER than IsAny() if you pass subclasses. So, make sure it's what you need. 390 func (e *Error) IsAnyDeep(cls ...Class) bool { 391 return e.is(cls, true) 392 } 393 394 // Class returns Error's Class. A special invalidClass is returned if Error is nil 395 // or has been manually instantiated instead of constructor using. 396 // Nil safe. 397 func (e *Error) Class() Class { 398 if !e.IsValid() { 399 return invalidClass 400 } 401 return classByID(e.classID, true) 402 } 403 404 // ReplaceClass replaces both of Error's Class and Namespace to the provided Class 405 // and its Namespace. 406 // Does nothing if any of Error or new Class is invalid. 407 // Nil safe. 408 func (e *Error) ReplaceClass(newClass Class) *Error { 409 if e.IsValid() && newClass.IsValid() { 410 e.classID = newClass.id 411 e.namespaceID = newClass.namespaceID 412 } 413 return e 414 } 415 416 // ID returns an unique Error's ID as ULID. You can tell this ID to the user and 417 // log this error. Then it will be easy to find an associated error. 418 // Returns "" if Error is not valid. 419 // Nil safe. 420 func (e *Error) ID() string { 421 if !e.IsValid() { 422 return "" 423 } 424 return e.letter.SystemFields[_ERR_SYS_FIELD_IDX_ERROR_ID].SValue 425 } 426 427 // ReleaseError prepares Error for being reused in the future and releases 428 // its internal parts (returning them to the pool). 429 // 430 // YOU MUST NOT USE ERROR OBJECT AFTER PASSING THEM INTO THIS FUNCTION. 431 // YOU DO NOT NEED TO CALL THIS FUNCTION IF YOUR ERROR WILL BE LOGGED USING EKALOG. 432 func ReleaseError(err *Error) { 433 if err.IsValid() { 434 releaseError(err) 435 } 436 }