One place for hosting & domains

      Errors

      Creating Custom Errors in Go


      Introduction

      Go provides two methods to create errors in the standard library, errors.New and fmt.Errorf. When communicating more complicated error information to your users, or to your future self when debugging, sometimes these two mechanisms are not enough to adequately capture and report what has happened. To convey this more complex error information and attain more functionality, we can implement the standard library interface type, error.

      The syntax for this would be as follows:

      type error interface {
        Error() string
      }
      

      The builtin package defines error as an interface with a single Error() method that returns an error message as a string. By implementing this method, we can transform any type we define into an error of our own.

      Let’s try running the following example to see an implementation of the error interface:

      package main
      
      import (
          "fmt"
          "os"
      )
      
      type MyError struct{}
      
      func (m *MyError) Error() string {
          return "boom"
      }
      
      func sayHello() (string, error) {
          return "", &MyError{}
      }
      
      func main() {
          s, err := sayHello()
          if err != nil {
              fmt.Println("unexpected error: err:", err)
              os.Exit(1)
          }
          fmt.Println("The string:", s)
      }
      

      We’ll see the following output:

      Output

      unexpected error: err: boom exit status 1

      Here we’ve created a new empty struct type, MyError, and defined the Error() method on it. The Error() method returns the string "boom".

      Within main(), we call the function sayHello that returns an empty string and a new instance of MyError. Since sayHello will always return an error, the fmt.Println invocation within the body of the if statement in main() will always execute. We then use fmt.Println to print the short prefix string "unexpected error:" along with the instance of MyError held within the err variable.

      Notice that we don’t have to directly call Error(), since the fmt package is able to automatically detect that this is an implementation of error. It calls Error() transparently to get the string "boom" and concatenates it with the prefix string "unexpected error: err:".

      Collecting Detailed Information in a Custom Error

      Sometimes a custom error is the cleanest way to capture detailed error information. For example, let’s say we want to capture the status code for errors produced by an HTTP request; run the following program to see an implementation of error that allows us to cleanly capture that information:

      package main
      
      import (
          "errors"
          "fmt"
          "os"
      )
      
      type RequestError struct {
          StatusCode int
      
          Err error
      }
      
      func (r *RequestError) Error() string {
          return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err)
      }
      
      func doRequest() error {
          return &RequestError{
              StatusCode: 503,
              Err:        errors.New("unavailable"),
          }
      }
      
      func main() {
          err := doRequest()
          if err != nil {
              fmt.Println(err)
              os.Exit(1)
          }
          fmt.Println("success!")
      }
      

      We will see the following output:

      Output

      status 503: err unavailable exit status 1

      In this example, we create a new instance of RequestError and provide the status code and an error using the errors.New function from the standard library. We then print this using fmt.Println as in previous examples.

      Within the Error() method of RequestError, we use the fmt.Sprintf function to construct a string using the information provided when the error was created.

      Type Assertions and Custom Errors

      The error interface exposes only one method, but we may need to access the other methods of error implementations to handle an error properly. For example, we may have several custom implementations of error that are temporary and can be retried—denoted by the presence of a Temporary() method.

      Interfaces provide a narrow view into the wider set of methods provided by types, so we must use a type assertion to change the methods that view is displaying, or to remove it entirely.

      The following example augments the RequestError shown previously to have a Temporary() method which will indicate whether or not callers should retry the request:

      package main
      
      import (
          "errors"
          "fmt"
          "net/http"
          "os"
      )
      
      type RequestError struct {
          StatusCode int
      
          Err error
      }
      
      func (r *RequestError) Error() string {
          return r.Err.Error()
      }
      
      func (r *RequestError) Temporary() bool {
          return r.StatusCode == http.StatusServiceUnavailable // 503
      }
      
      func doRequest() error {
          return &RequestError{
              StatusCode: 503,
              Err:        errors.New("unavailable"),
          }
      }
      
      func main() {
          err := doRequest()
          if err != nil {
              fmt.Println(err)
              re, ok := err.(*RequestError)
              if ok {
                  if re.Temporary() {
                      fmt.Println("This request can be tried again")
                  } else {
                      fmt.Println("This request cannot be tried again")
                  }
              }
              os.Exit(1)
          }
      
          fmt.Println("success!")
      }
      

      We will see the following output:

      Output

      unavailable This request can be tried again exit status 1

      Within main(), we call doRequest() which returns an error interface to us. We first print the error message returned by the Error() method. Next, we attempt to expose all methods from RequestError by using the type assertion re, ok := err.(*RequestError). If the type assertion succeeded, we then use the Temporary() method to see if this error is a temporary error. Since the StatusCode set by doRequest() is 503, which matches http.StatusServiceUnavailable, this returns true and causes "This request can be tried again" to be printed. In practice, we would instead make another request rather than printing a message.

      Wrapping Errors

      Commonly, an error will be generated from something outside of your program such as: a database, a network connection, etc. The error messages provided from these errors don’t help anyone find the origin of the error. Wrapping errors with extra information at the beginning of an error message would provide some needed context for successful debugging.

      The following example demonstrates how we can attach some contextual information to an otherwise cryptic error returned from some other function:

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      type WrappedError struct {
          Context string
          Err     error
      }
      
      func (w *WrappedError) Error() string {
          return fmt.Sprintf("%s: %v", w.Context, w.Err)
      }
      
      func Wrap(err error, info string) *WrappedError {
          return &WrappedError{
              Context: info,
              Err:     err,
          }
      }
      
      func main() {
          err := errors.New("boom!")
          err = Wrap(err, "main")
      
          fmt.Println(err)
      }
      

      We will see the following output:

      Output

      main: boom!

      WrappedError is a struct with two fields: a context message as a string, and an error that this WrappedError is providing more information about. When the Error() method is invoked, we again use fmt.Sprintf to print the context message, then the error (fmt.Sprintf knows to implicitly call the Error() method as well).

      Within main(), we create an error using errors.New, and then we wrap that error using the Wrap function we defined. This allows us to indicate that this error was generated in "main". Also, since our WrappedError is also an error, we could wrap other WrappedErrors—this would allow us to see a chain to help us track down the source of the error. With a little help from the standard library, we can even embed complete stack traces in our errors.

      Conclusion

      Since the error interface is only a single method, we’ve seen that we have great flexibility in providing different types of errors for different situations. This can encompass everything from communicating multiple pieces of information as part of an error all the way to implementing exponential backoff. While the error handling mechanisms in Go might on the surface seem simplistic, we can achieve quite rich handling using these custom errors to handle both common and uncommon situations.

      Go has another mechanism to communicate unexpected behavior, panics. In our next article in the error handling series, we will examine panics—what they are and how to handle them.



      Source link

      Handling Errors in Go


      Robust code needs to react correctly to unexpected circumstances like bad user input, faulty network connections, and failing disks. Error handling is the process of identifying when your program is in an unexpected state, and taking steps to record diagnostic information for later debugging.

      Unlike other languages that require developers to handle errors with specialized syntax, errors in Go are values with the type error returned from functions like any other value. To handle errors in Go, we must examine these errors that functions could return, decide if an error has occurred, and take proper action to protect data and tell users or operators that the error occurred.

      Creating Errors

      Before we can handle errors, we need to create some first. The standard library provides two built-in functions to create errors: errors.New and fmt.Errorf. Both of these functions allow you to specify a custom error message that you can later present to your users.

      errors.New takes a single argument—an error message as a string that you can customize to alert your users what went wrong.

      Try running the following example to see an error created by errors.New printed to standard output:

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func main() {
          err := errors.New("barnacles")
          fmt.Println("Sammy says:", err)
      }
      

      Output

      Sammy says: barnacles

      We used the errors.New function from the standard library to create a new error message with the string "barnacles" as the error message. We’ve followed convention here by using lowercase for the error message as the Go Programming Language Style Guide suggests.

      Finally, we used the fmt.Println function to combine our error message with "Sammy says:".

      The fmt.Errorf function allows you to dynamically build an error message. Its first argument is a string containing your error message with placeholder values such as %s for a string and %d for an integer. fmt.Errorf interpolates the arguments that follow this formatting string into those placeholders in order:

      package main
      
      import (
          "fmt"
          "time"
      )
      
      func main() {
          err := fmt.Errorf("error occurred at: %v", time.Now())
          fmt.Println("An error happened:", err)
      }
      

      Output

      An error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103

      We used the fmt.Errorf function to build an error message that would include the current time. The formatting string we provided to fmt.Errorf contains the %v formatting directive that tells fmt.Errorf to use the default formatting for the first argument provided after the formatting string. That argument will be the current time, provided by the time.Now function from the standard library. Similarly to the earlier example, we combine our error message with a short prefix and print the result to standard output using the fmt.Println function.

      Handling Errors

      Typically you wouldn’t see an error created like this to be used immediately for no other purpose, as in the previous example. In practice, it’s far more common to create an error and return it from a function when something goes wrong. Callers of that function will then use an if statement to see if the error was present or nil—an uninitialized value.

      This next example includes a function that always returns an error. Notice when you run the program that it produces the same output as the previous example even though a function is returning the error this time. Declaring an error in a different location does not change the error’s message.

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func boom() error {
          return errors.New("barnacles")
      }
      
      func main() {
          err := boom()
      
          if err != nil {
              fmt.Println("An error occurred:", err)
              return
          }
          fmt.Println("Anchors away!")
      }
      

      Output

      An error occurred: barnacles

      Here we define a function called boom() that returns a single error that we construct using errors.New. We then call this function and capture the error with the line err := boom().
      Once we assign this error, we check to see if it was present with the if err != nil conditional. Here the conditional will always evaluate to true, since we are always returning an error from boom().

      This won’t always be the case, so it’s good practice to have logic handling cases where an error is not present (nil) and cases where the error is present. When the error is present, we use fmt.Println to print our error along with a prefix as we have done in earlier examples. Finally, we use a return statement to skip the execution of fmt.Println("Anchors away!"), since that should only execute when no error occurred.

      Note: The if err != nil construction shown in the last example is the workhorse of error handling in the Go programming language. Wherever a function could produce an error, it’s important to use an if statement to check whether one occurred. In this way, idiomatic Go code naturally has its “happy path” logic at the first indent level, and all the “sad path” logic at the second indent level.

      If statements have an optional assignment clause that can be used to help condense calling a function and handling its errors.

      Run the next program to see the same output as our earlier example, but this time using a compound if statement to reduce some boilerplate:

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func boom() error {
          return errors.New("barnacles")
      }
      
      func main() {
          if err := boom(); err != nil {
              fmt.Println("An error occurred:", err)
              return
          }
          fmt.Println("Anchors away!")
      }
      

      Output

      An error occurred: barnacles

      As before, we have a function, boom(), that always returns an error. We assign the error returned from boom() to err as the first part of the if statement. In the second part of the if statement, following the semicolon, that err variable is then available. We check to see if the error was present and print our error with a short prefix string as we’ve done previously.

      In this section, we learned how to handle functions that only return an error. These functions are common, but it’s also important to be able to handle errors from functions that can return multiple values.

      Returning Errors Alongside Values

      Functions that return a single error value are often those that effect some stateful change, like inserting rows to a database. It’s also common to write functions that return a value if they completed successfully along with a potential error if that function failed. Go permits functions to return more than one result, which can be used to simultaneously return a value and an error type.

      To create a function that returns more than one value, we list the types of each returned value inside parentheses in the signature for the function. For example, a capitalize function that returns a string and an error would be declared using func capitalize(name string) (string, error) {}. The (string, error) part tells the Go compiler that this function will return a string and an error, in that order.

      Run the following program to see the output from a function that returns both a string and an error:

      package main
      
      import (
          "errors"
          "fmt"
          "strings"
      )
      
      func capitalize(name string) (string, error) {
          if name == "" {
              return "", errors.New("no name provided")
          }
          return strings.ToTitle(name), nil
      }
      
      func main() {
          name, err := capitalize("sammy")
          if err != nil {
              fmt.Println("Could not capitalize:", err)
              return
          }
      
          fmt.Println("Capitalized name:", name)
      }
      

      Output

      Capitalized name: SAMMY

      We define capitalize() as a function that takes a string (the name to be capitalized) and returns a string and an error value. In main(), we call capitalize() and assign the two values returned from the function to the name and err variables by separating them with commas on the left-hand side of the := operator. After this, we perform our if err != nil check as in earlier examples, printing the error to standard output using fmt.Println if the error was present. If no error was present, we print Capitalized name: SAMMY.

      Try changing the string "sammy" in name, err := capitalize("sammy") to the empty string ("") and you’ll receive the error Could not capitalize: no name provided instead.

      The capitalize function will return an error when callers of the function provide an empty string for the name parameter. When the name parameter is not the empty string, capitalize() uses strings.ToTitle to capitalize the name parameter and returns nil for the error value.

      There are some subtle conventions that this example follows that is typical of Go code, yet not enforced by the Go compiler. When a function returns multiple values, including an error, convention requests that we return the error as the last item. When returning an error from a function with multiple return values, idiomatic Go code also will set each non-error value to a zero value. Zero values are, for example, an empty string for strings, 0 for integers, an empty struct for struct types, and nil for interface and pointer types, to name a few. We cover zero values in more detail in our tutorial on variables and constants.

      Reducing boilerplate

      Adhering to these conventions can become tedious in situations where there are many values to return from a function. We can use an anonymous function to help reduce the boilerplate. Anonymous functions are procedures assigned to variables. In contrast to the functions we have defined in earlier examples, they are only available within the functions where you declare them—this makes them perfect to act as short pieces of reusable helper logic.

      The following program modifies the last example to include the length of the name that we’re capitalizing. Since it has three values to return, handling errors could become cumbersome without an anonymous function to assist us:

      package main
      
      import (
          "errors"
          "fmt"
          "strings"
      )
      
      func capitalize(name string) (string, int, error) {
          handle := func(err error) (string, int, error) {
              return "", 0, err
          }
      
          if name == "" {
              return handle(errors.New("no name provided"))
          }
      
          return strings.ToTitle(name), len(name), nil
      }
      
      func main() {
          name, size, err := capitalize("sammy")
          if err != nil {
              fmt.Println("An error occurred:", err)
          }
      
          fmt.Printf("Capitalized name: %s, length: %d", name, size)
      }
      

      Output

      Capitalized name: SAMMY, length: 5

      Within main(), we now capture the three returned arguments from capitalize as name, size, and err, respectively. We then check to see if capitalize returned an error by checking if the err variable was not equal to nil. This is important to do before attempting to use any of the other values returned by capitalize, because the anonymous function, handle, could set those to zero values. Since no error occurred because we provided the string "sammy", we print out the capitalized name and its length.

      Once again, you can try changing "sammy" to the empty string ("") to see the error case printed (An error occurred: no name provided).

      Within capitalize, we define the handle variable as an anonymous function. It takes a single error and returns identical values in the same order as the return values of capitalize. handle sets those values to zero values and forwards the error passed as its argument as the final return value. Using this, we can then return any errors encountered in capitalize by using the return statement in front of the call to handle with the error as its parameter.

      Remember that capitalize must return three values all the time, since that’s how we defined the function. Sometimes we don’t want to deal with all the values that a function could return. Fortunately, we have some flexibility in how we can use these values on the assignment side.

      Handling Errors from Multi-Return Functions

      When a function returns many values, Go requires us to assign each to a variable. In the last example, we do this by providing names for the two values returned from the capitalize function. These names should be separated by commas and appear on the left-hand side of the := operator. The first value returned from capitalize will be assigned to the name variable, and the second value (the error) will be assigned to the variable err. Occasionally, we’re only interested in the error value. You can discard any unwanted values that functions return using the special _ variable name.

      In the following program, we’ve modified our first example involving the capitalize function to produce an error by passing in the empty string (""). Try running this program to see how we’re able to examine just the error by discarding the first returned value with the _ variable:

      package main
      
      import (
          "errors"
          "fmt"
          "strings"
      )
      
      func capitalize(name string) (string, error) {
          if name == "" {
              return "", errors.New("no name provided")
          }
          return strings.ToTitle(name), nil
      }
      
      func main() {
          _, err := capitalize("")
          if err != nil {
              fmt.Println("Could not capitalize:", err)
              return
          }
          fmt.Println("Success!")
      }
      

      Output

      Could not capitalize: no name provided

      Within the main() function this time, we assign the capitalized name (the string returned first) to the underscore variable (_). At the same time, we assign the error returned by capitalize to the err variable. We then check if the error was present in the if err != nil conditional. Since we have hard-coded an empty string as an argument to capitalize in the line _, err := capitalize(""), this conditional will always evaluate to true. This produces the output "Could not capitalize: no name provided" printed by the call to the fmt.Println function within the body of the if statement. The return after this will skip the fmt.Println("Success!").

      Conclusion

      We’ve seen many ways to create errors using the standard library and how to build functions that return errors in an idiomatic way. In this tutorial, we’ve managed to successfully create various errors using the standard library errors.New and fmt.Errorf functions. In future tutorials, we’ll look at how to create our own custom error types to convey richer information to users.



      Source link

      How To Troubleshoot Socket Errors in MySQL


      MySQL manages connections to the database server through the use of a socket file, a special kind of file that facilitates communications between different processes. The MySQL server’s socket file is named mysqld.sock and on Ubuntu systems it’s usually stored in the /var/run/mysqld/ directory. This file is created by the MySQL service automatically.

      Sometimes, changes to your system or your MySQL configuration can result in MySQL being unable to read the socket file, preventing you from gaining access to your databases. The most common socket error looks like this:

      Output

      ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

      There are a few reasons why this error may occur, and a few potential ways to resolve it.

      One common cause of this error is that the MySQL service is stopped or did not start to begin with, meaning that it was unable to create the socket file in the first place. To find out if this is the reason you’re seeing this error, try starting the service with systemctl:

      • sudo systemctl start mysql

      Then try accessing the MySQL prompt again. If you still receive the socket error, double check the location where your MySQL installation is looking for the socket file. This information can be found in the mysqld.cnf file:

      • sudo nano /etc/mysql/mysql.conf.d/mysql.cnf

      Look for the socket parameter in the [mysqld] section of this file. It will look like this:

      /etc/mysql/mysql.conf.d/mysqld.cnf

      . . .
      [mysqld]
      user            = mysql
      pid-file        = /var/run/mysqld/mysqld.pid
      socket          = /var/run/mysqld/mysqld.sock
      port            = 3306
      . . .
      

      Close this file, then ensure that the mysqld.sock file exists by running an ls command on the directory where MySQL expects to find it:

      If the socket file exists, you will see it in this command's output:

      Output

      . .. mysqld.pid mysqld.sock mysqld.sock.lock

      If the file does not exist, the reason may be that MySQL is trying to create it, but does not have adequate permissions to do so. You can ensure that the correct permissions are in place by changing the directory's ownership to the mysql user and group:

      • sudo chown mysql:mysql /var/run/mysqld/

      Then ensure that the mysql user has the appropriate permissions over the directory. Setting these to 775 will work in most cases:

      • sudo chmod -R 755 /var/run/mysqld/

      Finally, restart the MySQL service so it can attempt to create the socket file again:

      • sudo systemctl restart mysql

      Then try accessing the MySQL prompt once again. If you still encounter the socket error, there's likely a deeper issue with your MySQL instance, in which case you should review the error log to see if it can provide any clues.



      Source link