github.com/mailgun/holster/v4@v4.20.0/errors/README.md (about)

     1  # Errors
     2  Package is a fork of [https://github.com/pkg/errors](https://github.com/pkg/errors) with additional
     3   functions for improving the relationship between structured logging and error handling in go.
     4   
     5  ## Adding structured context to an error
     6  Wraps the original error while providing structured context data
     7  ```go
     8  _, err := ioutil.ReadFile(fileName)
     9  if err != nil {
    10          return errors.WithContext{"file": fileName}.Wrap(err, "read failed")
    11  }
    12  ```
    13  
    14  ## Retrieving the structured context
    15  Using `errors.WithContext{}` stores the provided context for later retrieval by upstream code or structured logging
    16  systems
    17  ```go
    18  // Pass to logrus as structured logging
    19  logrus.WithFields(errors.ToLogrus(err)).Error("open file error")
    20  ```
    21  Stack information on the source of the error is also included
    22  ```go
    23  context := errors.ToMap(err)
    24  context == map[string]interface{}{
    25        "file": "my-file.txt",
    26        "go-func": "loadFile()",
    27        "go-line": 146,
    28        "go-file": "with_context_example.go"
    29  }
    30  ```
    31  
    32  ## Conforms to the `Causer` interface
    33  Errors wrapped with `errors.WithContext{}` are compatible with errors wrapped by `github.com/pkg/errors`
    34  ```go
    35  switch err := errors.Cause(err).(type) {
    36  case *MyError:
    37          // handle specifically
    38  default:
    39          // unknown error
    40  }
    41  ```
    42  
    43  ## Proper Usage
    44  The context wrapped by `errors.WithContext{}` is not intended to be used to by code to decide how an error should be 
    45  handled. It is a convenience where the failure is well known, but the context is dynamic. In other words, you know the
    46  database returned an unrecoverable query error, but creating a new error type with the details of each query
    47  error is overkill **ErrorFetchPage{}, ErrorFetchAll{}, ErrorFetchAuthor{}, etc...**
    48  
    49  As an example
    50  ```go
    51  func (r *Repository) FetchAuthor(isbn string) (Author, error) {
    52      // Returns ErrorNotFound{} if not exist
    53      book, err := r.fetchBook(isbn)
    54      if err != nil {
    55          return nil, errors.WithContext{"isbn": isbn}.Wrap(err, "while fetching book")
    56      }
    57      // Returns ErrorNotFound{} if not exist
    58      author, err := r.fetchAuthorByBook(book)
    59      if err != nil {
    60          return nil, errors.WithContext{"book": book}.Wrap(err, "while fetching author")
    61      }
    62      return author, nil
    63  }
    64  ```
    65  
    66  You should continue to create and inspect error types
    67  ```go
    68  type ErrorAuthorNotFound struct {}
    69  
    70  func isNotFound(err error) {
    71      _, ok := err.(*ErrorAuthorNotFound)
    72      return ok
    73  }
    74  
    75  func main() {
    76      r := Repository{}
    77      author, err := r.FetchAuthor("isbn-213f-23422f52356")
    78      if err != nil {
    79          // Fetch the original Cause() and determine if the error is recoverable
    80          if isNotFound(error.Cause(err)) {
    81                  author, err := r.AddBook("isbn-213f-23422f52356", "charles", "darwin")
    82          }
    83          if err != nil {
    84                  logrus.WithFields(errors.ToLogrus(err)).Errorf("while fetching author - %s", err)
    85                  os.Exit(1)
    86          }
    87      }
    88      fmt.Printf("Author %+v\n", author)
    89  }
    90  ```
    91  
    92  ## Context for concrete error types
    93  If the error implements the `errors.HasContext` interface the context can be retrieved
    94  ```go
    95  context, ok := err.(errors.HasContext)
    96  if ok {
    97      fmt.Println(context.Context())
    98  }
    99  ```
   100  
   101  This makes it easy for error types to provide their context information.
   102   ```go
   103  type ErrorBookNotFound struct {
   104      ISBN string
   105  }
   106  // Implements the `HasContext` interface
   107  func (e *ErrorBookNotFound) func Context() map[string]interface{} {
   108      return map[string]interface{}{
   109          "isbn": e.ISBN,
   110      }
   111   }
   112  ```
   113  Now we can create the error and logrus knows how to retrieve the context
   114   
   115  ```go
   116  func (* Repository) FetchBook(isbn string) (*Book, error) {
   117      var book Book
   118      err := r.db.Query("SELECT * FROM books WHERE isbn = ?").One(&book)
   119      if err != nil {
   120          return nil, ErrorBookNotFound{ISBN: isbn}
   121      }
   122  }
   123  
   124  func main() {
   125      r := Repository{}
   126      book, err := r.FetchBook("isbn-213f-23422f52356")
   127      if err != nil {
   128          logrus.WithFields(errors.ToLogrus(err)).Errorf("while fetching book - %s", err)
   129          os.Exit(1)
   130      }
   131      fmt.Printf("Book %+v\n", book)
   132  }
   133  ```
   134  
   135  
   136  ## A Complete example
   137  The following is a complete example using
   138  http://github.com/mailgun/logrus-hooks/kafkahook to marshal the context into ES
   139  fields.
   140  
   141  ```go
   142  package main
   143  
   144  import (
   145      "log"
   146      "io/ioutil"
   147  
   148      "github.com/mailgun/holster/v4/errors"
   149      "github.com/mailgun/logrus-hooks/kafkahook"
   150      "github.com/sirupsen/logrus"
   151  )
   152  
   153  func OpenWithError(fileName string) error {
   154      _, err := ioutil.ReadFile(fileName)
   155      if err != nil {
   156              // pass the filename up via the error context
   157              return errors.WithContext{
   158                  "file": fileName,
   159              }.Wrap(err, "read failed")
   160      }
   161      return nil
   162  }
   163  
   164  func main() {
   165      // Init the kafka hook logger
   166      hook, err := kafkahook.New(kafkahook.Config{
   167          Endpoints: []string{"kafka-n01", "kafka-n02"},
   168          Topic:     "udplog",
   169      })
   170      if err != nil {
   171          log.Fatal(err)
   172      }
   173  
   174      // Add the hook to logrus
   175      logrus.AddHook(hook)
   176  
   177      // Create an error and log it
   178      if err := OpenWithError("/tmp/non-existant.file"); err != nil {
   179          // This log line will show up in ES with the additional fields
   180          //
   181          // excText: "read failed"
   182          // excValue: "read failed: open /tmp/non-existant.file: no such file or directory"
   183          // excType: "*errors.WithContext"
   184          // filename: "/src/to/main.go"
   185          // funcName: "main()"
   186          // lineno: 25
   187          // context.file: "/tmp/non-existant.file"
   188          // context.domain.id: "some-id"
   189          // context.foo: "bar"
   190          logrus.WithFields(logrus.Fields{
   191              "domain.id": "some-id",
   192              "foo": "bar",
   193              "err": err,
   194          }).Error("log messge")
   195      }
   196  }
   197  ```