github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/errors_multi.go (about) 1 /* 2 Error Catcher 3 4 The MutiCatcher type makes it possible to collect from a group of 5 operations and then aggregate them as a single error. 6 */ 7 package grip 8 9 import ( 10 "fmt" 11 "strings" 12 "sync" 13 14 "github.com/pkg/errors" 15 ) 16 17 // TODO: make a new catcher package, leave constructors in this 18 // package, use this Catcher interface, and write implementations that 19 // don't translate errors into string internally. 20 // 21 22 // CheckFunction are functions which take no arguments and return an 23 // error. 24 type CheckFunction func() error 25 26 // Catcher is an interface for an error collector for use when 27 // implementing continue-on-error semantics in concurrent 28 // operations. There are three different Catcher implementations 29 // provided by this package that differ *only* in terms of the 30 // string format returned by String() (and also the format of the 31 // error returned by Resolve().) 32 // 33 // If you do not use github.com/pkg/errors to attach 34 // errors, the implementations are usually functionally 35 // equivalent. The Extended variant formats the errors using the "%+v" 36 // (which returns a full stack trace with pkg/errors,) the Simple 37 // variant uses %s (which includes all the wrapped context,) and the 38 // basic catcher calls error.Error() (which should be equvalent to %s 39 // for most error implementations.) 40 type Catcher interface { 41 Add(error) 42 AddWhen(bool, error) 43 Extend([]error) 44 ExtendWhen(bool, []error) 45 Len() int 46 HasErrors() bool 47 String() string 48 Resolve() error 49 Errors() []error 50 51 New(string) 52 NewWhen(bool, string) 53 Errorf(string, ...interface{}) 54 ErrorfWhen(bool, string, ...interface{}) 55 56 Wrap(error, string) 57 Wrapf(error, string, ...interface{}) 58 59 Check(CheckFunction) 60 CheckExtend([]CheckFunction) 61 CheckWhen(bool, CheckFunction) 62 } 63 64 // multiCatcher provides an interface to collect and coalesse error 65 // messages within a function or other sequence of operations. Used to 66 // implement a kind of "continue on error"-style operations. The 67 // methods on MultiCatatcher are thread-safe. 68 type baseCatcher struct { 69 errs []error 70 mutex sync.RWMutex 71 fmt.Stringer 72 } 73 74 // NewCatcher returns a Catcher instance that you can use to capture 75 // error messages and aggregate the errors. For consistency with 76 // earlier versions NewCatcher is the same as NewExtendedCatcher() 77 // 78 // DEPRECATED: use one of the other catcher implementations. See the 79 // documentation for the Catcher interface for most implementations. 80 func NewCatcher() Catcher { return NewExtendedCatcher() } 81 82 // NewBasicCatcher collects error messages and formats them using a 83 // new-line separated string of the output of error.Error() 84 func NewBasicCatcher() Catcher { 85 c := &baseCatcher{} 86 c.Stringer = &basicCatcher{c} 87 return c 88 } 89 90 // NewSimpleCatcher collects error messages and formats them using a 91 // new-line separated string of the string format of the error message 92 // (e.g. %s). 93 func NewSimpleCatcher() Catcher { 94 c := &baseCatcher{} 95 c.Stringer = &simpleCatcher{c} 96 return c 97 } 98 99 // NewExtendedCatcher collects error messages and formats them using a 100 // new-line separated string of the extended string format of the 101 // error message (e.g. %+v). 102 func NewExtendedCatcher() Catcher { 103 c := &baseCatcher{} 104 c.Stringer = &extendedCatcher{c} 105 return c 106 } 107 108 // Add takes an error object and, if it's non-nil, adds it to the 109 // internal collection of errors. 110 func (c *baseCatcher) Add(err error) { 111 if err == nil { 112 return 113 } 114 115 c.mutex.Lock() 116 defer c.mutex.Unlock() 117 118 c.errs = append(c.errs, err) 119 } 120 121 // Len returns the number of errors stored in the collector. 122 func (c *baseCatcher) Len() int { 123 c.mutex.RLock() 124 defer c.mutex.RUnlock() 125 126 return len(c.errs) 127 } 128 129 // HasErrors returns true if the collector has ingested errors, and 130 // false otherwise. 131 func (c *baseCatcher) HasErrors() bool { 132 c.mutex.RLock() 133 defer c.mutex.RUnlock() 134 135 return len(c.errs) > 0 136 } 137 138 // Extend adds all non-nil errors, passed as arguments to the catcher. 139 func (c *baseCatcher) Extend(errs []error) { 140 if len(errs) == 0 { 141 return 142 } 143 144 c.mutex.Lock() 145 defer c.mutex.Unlock() 146 147 for _, err := range errs { 148 if err == nil { 149 continue 150 } 151 152 c.errs = append(c.errs, err) 153 } 154 } 155 156 func (c *baseCatcher) Errorf(form string, args ...interface{}) { 157 if form == "" { 158 return 159 } else if len(args) == 0 { 160 c.New(form) 161 return 162 } 163 c.Add(errors.Errorf(form, args...)) 164 } 165 166 func (c *baseCatcher) New(e string) { 167 if e == "" { 168 return 169 } 170 c.Add(errors.New(e)) 171 } 172 173 func (c *baseCatcher) Wrap(err error, m string) { c.Add(errors.Wrap(err, m)) } 174 175 func (c *baseCatcher) Wrapf(err error, f string, args ...interface{}) { 176 c.Add(errors.Wrapf(err, f, args...)) 177 } 178 179 func (c *baseCatcher) AddWhen(cond bool, err error) { 180 if !cond { 181 return 182 } 183 184 c.Add(err) 185 } 186 187 func (c *baseCatcher) ExtendWhen(cond bool, errs []error) { 188 if !cond { 189 return 190 } 191 192 c.Extend(errs) 193 } 194 195 func (c *baseCatcher) ErrorfWhen(cond bool, form string, args ...interface{}) { 196 if !cond { 197 return 198 } 199 200 c.Errorf(form, args...) 201 } 202 203 func (c *baseCatcher) NewWhen(cond bool, e string) { 204 if !cond { 205 return 206 } 207 208 c.New(e) 209 } 210 211 func (c *baseCatcher) Check(fn CheckFunction) { c.Add(fn()) } 212 213 func (c *baseCatcher) CheckWhen(cond bool, fn CheckFunction) { 214 if !cond { 215 return 216 } 217 218 c.Add(fn()) 219 } 220 221 func (c *baseCatcher) CheckExtend(fns []CheckFunction) { 222 for _, fn := range fns { 223 c.Add(fn()) 224 } 225 } 226 227 func (c *baseCatcher) Errors() []error { 228 c.mutex.RLock() 229 defer c.mutex.RUnlock() 230 231 out := make([]error, len(c.errs)) 232 233 copy(out, c.errs) 234 235 return out 236 } 237 238 // Resolve returns a final error object for the Catcher. If there are 239 // no errors, it returns nil, and returns an error object with the 240 // string form of all error objects in the collector. 241 func (c *baseCatcher) Resolve() error { 242 if !c.HasErrors() { 243 return nil 244 } 245 246 return errors.New(c.String()) 247 } 248 249 //////////////////////////////////////////////////////////////////////// 250 // 251 // separate implementations of grip.Catcher with different string formatting options. 252 253 type extendedCatcher struct{ *baseCatcher } 254 255 func (c *extendedCatcher) String() string { 256 c.mutex.RLock() 257 defer c.mutex.RUnlock() 258 259 output := make([]string, len(c.errs)) 260 261 for idx, err := range c.errs { 262 output[idx] = fmt.Sprintf("%+v", err) 263 } 264 265 return strings.Join(output, "\n") 266 } 267 268 type simpleCatcher struct{ *baseCatcher } 269 270 func (c *simpleCatcher) String() string { 271 c.mutex.RLock() 272 defer c.mutex.RUnlock() 273 274 output := make([]string, len(c.errs)) 275 276 for idx, err := range c.errs { 277 output[idx] = fmt.Sprintf("%s", err) 278 } 279 280 return strings.Join(output, "\n") 281 } 282 283 type basicCatcher struct{ *baseCatcher } 284 285 func (c *basicCatcher) String() string { 286 c.mutex.RLock() 287 defer c.mutex.RUnlock() 288 289 output := make([]string, len(c.errs)) 290 291 for idx, err := range c.errs { 292 output[idx] = err.Error() 293 } 294 295 return strings.Join(output, "\n") 296 } 297 298 //////////////////////////////////////////////////////////////////////// 299 // 300 // an implementation to annotate errors with timestamps 301 302 type timeAnnotatingCatcher struct { 303 errs []*timestampError 304 extended bool 305 mu sync.RWMutex 306 } 307 308 // NewTimestampCatcher produces a Catcher instance that reports the 309 // short form of all constituent errors and annotates those errors 310 // with a timestamp to reflect when the error was collected. 311 func NewTimestampCatcher() Catcher { return &timeAnnotatingCatcher{} } 312 313 // NewExtendedTimestampCatcher adds long-form annotation to the 314 // aggregated error message (e.g. including stacks, when possible.) 315 func NewExtendedTimestampCatcher() Catcher { return &timeAnnotatingCatcher{extended: true} } 316 317 func (c *timeAnnotatingCatcher) Add(err error) { 318 if err == nil { 319 return 320 } 321 322 c.mu.Lock() 323 defer c.mu.Unlock() 324 325 tserr := newTimeStampError(err) 326 327 if tserr == nil { 328 return 329 } 330 331 c.errs = append(c.errs, tserr.setExtended(c.extended)) 332 } 333 334 func (c *timeAnnotatingCatcher) Extend(errs []error) { 335 if len(errs) == 0 { 336 return 337 } 338 339 c.mu.Lock() 340 defer c.mu.Unlock() 341 342 for _, err := range errs { 343 if err == nil { 344 continue 345 } 346 347 c.errs = append(c.errs, newTimeStampError(err).setExtended(c.extended)) 348 } 349 } 350 351 func (c *timeAnnotatingCatcher) AddWhen(cond bool, err error) { 352 if !cond { 353 return 354 } 355 356 c.Add(err) 357 } 358 359 func (c *timeAnnotatingCatcher) ExtendWhen(cond bool, errs []error) { 360 if !cond { 361 return 362 } 363 364 c.Extend(errs) 365 } 366 367 func (c *timeAnnotatingCatcher) New(e string) { 368 if e == "" { 369 return 370 } 371 372 c.Add(errors.New(e)) 373 } 374 375 func (c *timeAnnotatingCatcher) NewWhen(cond bool, e string) { 376 if !cond { 377 return 378 } 379 380 c.New(e) 381 } 382 383 func (c *timeAnnotatingCatcher) Errorf(f string, args ...interface{}) { 384 if f == "" { 385 return 386 } else if len(args) == 0 { 387 c.New(f) 388 return 389 } 390 391 c.Add(errors.Errorf(f, args...)) 392 } 393 394 func (c *timeAnnotatingCatcher) ErrorfWhen(cond bool, f string, args ...interface{}) { 395 if !cond { 396 return 397 } 398 399 c.Errorf(f, args...) 400 } 401 402 func (c *timeAnnotatingCatcher) Wrap(err error, m string) { 403 c.Add(WrapErrorTimeMessage(err, m)) 404 } 405 406 func (c *timeAnnotatingCatcher) Wrapf(err error, f string, args ...interface{}) { 407 c.Add(WrapErrorTimeMessagef(err, f, args...)) 408 } 409 410 func (c *timeAnnotatingCatcher) Check(fn CheckFunction) { 411 c.Add(fn()) 412 } 413 414 func (c *timeAnnotatingCatcher) CheckWhen(cond bool, fn CheckFunction) { 415 if !cond { 416 return 417 } 418 419 c.Add(fn()) 420 } 421 422 func (c *timeAnnotatingCatcher) CheckExtend(fns []CheckFunction) { 423 for _, fn := range fns { 424 c.Add(fn()) 425 } 426 } 427 428 func (c *timeAnnotatingCatcher) Len() int { 429 c.mu.RLock() 430 defer c.mu.RUnlock() 431 432 return len(c.errs) 433 } 434 435 func (c *timeAnnotatingCatcher) HasErrors() bool { 436 c.mu.RLock() 437 defer c.mu.RUnlock() 438 439 return len(c.errs) > 0 440 } 441 442 func (c *timeAnnotatingCatcher) Errors() []error { 443 c.mu.RLock() 444 defer c.mu.RUnlock() 445 446 out := make([]error, len(c.errs)) 447 for idx, err := range c.errs { 448 out[idx] = err 449 } 450 451 return out 452 } 453 454 func (c *timeAnnotatingCatcher) String() string { 455 c.mu.RLock() 456 defer c.mu.RUnlock() 457 458 output := make([]string, len(c.errs)) 459 460 for idx, err := range c.errs { 461 if err.extended { 462 output[idx] = err.String() 463 } else { 464 output[idx] = err.String() 465 } 466 } 467 468 return strings.Join(output, "\n") 469 } 470 471 func (c *timeAnnotatingCatcher) Resolve() error { 472 if !c.HasErrors() { 473 return nil 474 } 475 476 return errors.New(c.String()) 477 }