[series] – go gin API routing middleware capture exception (4)

Time:2020-6-29

summary

First of all, synchronize the following project overview:

[series] - go gin API routing middleware capture exception (4)

Last article shared, routing Middleware – logging, this article we share: routing Middleware – catch exceptions. When the system is abnormal, prompt “system exception, please contact the administrator!” At the same time, send a panic alarm email.

[series] - go gin API routing middleware capture exception (4)

What are anomalies?

The exception in go is panic, which is thrown when the program is running. After the panic is thrown, if no protection measures are added to the program, the console will print out the details of the panic and then terminate the operation.

We can divide panics into two types

One is intentionally thrown, for example,

Panic ("customized panic information")

Output:

2019 / 09 / 10 20:25:27 http: panic serving [:: 1]: 61547: customized panic information
goroutine 8 [running]:
...

One is unintentionally thrown out and carelessly written. For example,

var slice = [] int {1, 2, 3, 4, 5}

slice[6] = 6

Output:

2019/09/10 15:27:05 http: panic serving [::1]:61616: runtime error: index out of range
goroutine 6 [running]:
...

Imagine that if there is panic in the online environment, and the command line output, because we can’t capture it, we can’t locate it. It’s terrible to think about it. How can we capture the exception?

How to catch exceptions?

When a panic occurs in a program, recover can be called inside the defer function to capture it.

Not to say much, code directly:

defer func() {
    if err := recover(); err != nil {
        fmt.Println(err)
    }
}()

Run “unintentionally thrown panic” and output:

runtime error: index out of range

OK, the error has been caught. Now we can do the article.

We should all know what to do

  • Get the call stack of the runtime( debug.Stack ())
  • Get the current request data
  • Assemble data for email

So, how can go email? Is there an open source package?

Sure. Please look down.

Encapsulate email method

Use package:gopkg.in/gomail.v2

Direct code:

func SendMail(mailTo string, subject string, body string) error {
    
    if config.ErrorNotifyOpen != 1 {
        return nil
    }

    m := gomail.NewMessage()

    //Set sender
    m.SetHeader("From", config.SystemEmailUser)

    //Settings sent to multiple users
    mailArrTo := strings.Split(mailTo, ",")
    m.SetHeader("To", mailArrTo...)

    //Set message subject
    m.SetHeader("Subject", subject)

    //Set message body
    m.SetBody("text/html", body)

    d := gomail.NewDialer(config.SystemEmailHost, config.SystemEmailPort, config.SystemEmailUser, config.SystemEmailPass)

    err := d.DialAndSend(m)
    if err != nil {
        fmt.Println(err)
    }
    return err
}

I’ve added a switch here. You can turn it on or off.

Now the email will be sent, and the whole email template will be perfect.

Custom mail template

As shown in the figure:

[series] - go gin API routing middleware capture exception (4)

This is the template of alarm email. It’s not bad. What else do you want to record? You can customize it.

Encapsulating a middleware

Finally, wrap it up.

Direct code:

func SetUp() gin.HandlerFunc {

    return func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {

                DebugStack := ""
                for _, v := range strings.Split(string(debug.Stack()), "\n") {
                    DebugStack += v + "<br>"
                }

                subject :=  fmt.Sprintf ("[important error]% s item is wrong! " config.AppName )

                body := strings.ReplaceAll(MailTemplate, "{ErrorMsg}", fmt.Sprintf("%s", err))
                body  = strings.ReplaceAll(body, "{RequestTime}", util.GetCurrentDate())
                body  = strings.ReplaceAll(body, "{RequestURL}", c.Request.Method + "  " + c.Request.Host + c.Request.RequestURI)
                body  = strings.ReplaceAll(body, "{RequestUA}", c.Request.UserAgent())
                body  = strings.ReplaceAll(body, "{RequestIP}", c.ClientIP())
                body  = strings.ReplaceAll(body, "{DebugStack}", DebugStack)

                _ = util.SendMail(config.ErrorNotifyUser, subject, body)

                utilGin := util.Gin{Ctx: c}
                utilGin.Response (500, "system exception, please contact administrator! ", nil)
            }
        }()
        c.Next()
    }
}

When a panic exception occurs, the output:

{
    "code": 500,
    "MSG": "the system is abnormal, please contact the administrator! "
    "data": null
}

At the same time, you will receive a panic alert email.

[series] - go gin API routing middleware capture exception (4)

For the sake of screenshots, debugstack cuts out some information.

It’s over here.

[series] - go gin API routing middleware capture exception (4)

remarks

  • The place where the mail is sent can be adjusted to asynchronous sending.
  • Only part of the code is posted in the article. Please refer to GitHub for relevant code.
  • When testing outgoing mail, be sure to configure mailbox information.

Source code address

https://github.com/xinliangno…

Go in API series

  • 1. Use go modules to initialize the project
  • 2. Planning project directory and parameter verification
  • 3. Routing middleware logging

[series] - go gin API routing middleware capture exception (4)