An overview of the
Many readers in the background to ask me for Gin framework actual combat series Demo source, here again to explain, source code I have updated to GitHub, address: github.com/xinliangnot…
To start today’s article, why custom error handling? What is the default error handling?
Okay, so let’s talk about default error handling.
The default error handling is errors.New(” error message “), which is returned as a return value of type error.
Here’s a simple example:
func hello(name string) (str string, err error) {
if name == "" {
err = errors.New("Name cannot be empty")
return
}
str = fmt.Sprintf("hello: %s", name)
return
}
Copy the code
When this method is called:
var name = ""
str, err := hello(name)
iferr ! = nil { fmt.Println(err.Error())return
}
Copy the code
This is the default error handling, as illustrated in the following example.
This default error handler just gets a string of error messages.
However…
I also want the time when the error occurred, the filename, method name, line number, and so on.
I also want to get alarms when errors occur, such as SMS alarms, email alarms, wechat alarms, etc.
I also want the calls to be less complex and similar to the default error handling, such as:
alarm.WeChat("Error message")
return
Copy the code
In this way, we get the information we want (time, file name, method name, line number) and notify us of the alarm through wechat.
Similarly, alarm.Email(” error message “) and alarm.sms (” error message “) give us the same message, but in different ways.
We also want to make sure that when we get an error in our business logic, we only get an error message.
So this is what we’re going to implement today, custom error handling, and before we do that, let’s talk about error handling for Go.
Error handling
package main
import (
"errors"
"fmt"
)
func hello(name string) (str string, err error) {
if name == "" {
err = errors.New("Name cannot be empty")
return
}
str = fmt.Sprintf("hello: %s", name)
return
}
func main() {
var name = ""
fmt.Println("param:", name)
str, err := hello(name)
iferr ! = nil { fmt.Println(err.Error())return
}
fmt.Println(str)
}
Copy the code
Output:
param: Tom
hello: Tom
Copy the code
When name = “”, output:
Param: Name cannot be emptyCopy the code
It is recommended that every function should have error handling, and error should be the last return value.
Let’s take a look at official errors.go
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package errors implements functions to manipulate errors.
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
Copy the code
The above code, not complex, refer to the above, let’s write a custom error handling.
Custom error handling
Let’s define an alarm.go to handle alarms.
Without further ado, just look at the code.
package alarm
import (
"encoding/json"
"fmt"
"ginDemo/common/function"
"path/filepath"
"runtime"
"strings"
)
type errorString struct {
s string
}
type errorInfo struct {
Time string `json:"time"`
Alarm string `json:"alarm"`
Message string `json:"message"`
Filename string `json:"filename"`
Line int `json:"line"`
Funcname string `json:"funcname"`
}
func (e *errorString) Error() string {
return e.s
}
func New (text string) error {
alarm("INFO", text)
return&errorString{text}} // send Email func Email (text string) error {alarm("EMAIL", text)
return&errorString{text}} // send Sms func Sms (text string) error {alarm("SMS", text)
return&errorString{text}} func WeChat (text string) error {alarm("WX", text)
return&errorString{text}} // Alarm method func alarm(level string, STR string) {function.GetTimeStr();functionName := "?", 0,"?"
pc, fileName, line, ok := runtime.Caller(2)
if ok {
functionName = runtime.FuncForPC(pc).Name()
functionName = filepath.Ext(functionName)
functionName = strings.TrimPrefix(functionName, ".")
}
var msg = errorInfo {
Time : currentTime,
Alarm : level,
Message : str,
Filename : fileName,
Line : line,
Funcname : functionName,
}
jsons, errs := json.Marshal(msg)
iferrs ! = nil { fmt.Println("json marshal error:", errs)
}
errorJsonInfo := string(jsons)
fmt.Println(errorJsonInfo)
if level == "EMAIL"{// Perform emailing}else if level == "SMS"{// Execute SMS}else if level == "WX"{// Execute sending wechat}else if level == "INFO"{// execute log}}Copy the code
See how to call:
package v1
import (
"fmt"
"ginDemo/common/alarm"
"ginDemo/entity"
"github.com/gin-gonic/gin"
"net/http") func AddProduct(c *gin.Context) {// Get the Get parameter name := c.query ("name")
var res = entity.Result{}
str, err := hello(name)
iferr ! = nil { res.SetCode(entity.CODE_ERROR) res.SetMessage(err.Error()) c.JSON(http.StatusOK, res) c.Abort()return
}
res.SetCode(entity.CODE_SUCCESS)
res.SetMessage(str)
c.JSON(http.StatusOK, res)
}
func hello(name string) (str string, err error) {
if name == "" {
err = alarm.WeChat("Name cannot be empty")
return
}
str = fmt.Sprintf("hello: %s", name)
return
}
Copy the code
Visit: http://localhost:8080/v1/product/add? name=a
{
"code": 1,
"msg": "hello: a"."data": null
}
Copy the code
No errors are thrown and no information is output.
Visit: http://localhost:8080/v1/product/add
{
"code": 1,"msg": "Name cannot be empty"."data": null
}
Copy the code
An error was thrown, with the following output:
{"time":"The 2019-07-23 22:19:17"."alarm":"WX"."message":"Name cannot be empty"."filename":"Absolute path/ginDemo/router/v1 / product. Go." "."line": 33,"funcname":"hello"}
Copy the code
You might say, “As in the data binding and validation shared in the previous article, binding the passed parameters to “required” would also work.”
All I could say was, “Boy, you don’t understand. This is just an example of how you can use custom error handling in some complex business logic judgment scenarios.”
At this point, we receive the time, error message, file name, line number, and method name when reporting an error.
It’s also relatively easy to call.
Although the alarm mode is marked, there is still no alarm notification.
I mean, if you store data in a queue and then do an asynchronous task and consume it, that’s out of the question, and you can fix that.
Caller(); Caller(); runtime.caller ();
We also know that Go has panic and Recover. What do they do? Let’s talk about them.
Panic and recover
Panic should only be used to throw errors when the program cannot continue.
In the event of panic, recover can be called within defer(the delay function) for control, but only within the same Go coroutine.
Panic is divided into two, one is intentionally thrown, one is caused by careless programming, let’s say one by one.
Intentional panic:
package main
import (
"fmt"
)
func main() {
fmt.Println("-- 1 --")
defer func() {
ifr := recover(); r ! = nil { fmt.Printf("panic: %s\n", r)
}
fmt.Println("-- 2 --")
}()
panic("i am panic")}Copy the code
Output:
-- 1 --
panic: i am panic
-- 2 --
Copy the code
Panic:
package main
import (
"fmt"
)
func main() {
fmt.Println("-- 1 --")
defer func() {
ifr := recover(); r ! = nil { fmt.Printf("panic: %s\n", r)
}
fmt.Println("-- 2 --")
}()
var slice = [] int {1, 2, 3, 4, 5}
slice[6] = 6
}
Copy the code
Output:
-- 1 --
panic: runtime error: index out of range
-- 2 --
Copy the code
We captured both of the above through RECOVER, so how do we use them in Gin framework? What can I do if I also want to send an alarm when I receive panic?
If you want to implement an alarm, you should first define a Panic() method in ararm.go and call this method when a Panic exception occurs in your project.
Func Panic (text string) error {alarm("PANIC", text)
return &errorString{text}
}
Copy the code
So how do we capture it?
Use middleware to capture, write an Recover middleware.
package recover
import (
"fmt"
"ginDemo/common/alarm"
"github.com/gin-gonic/gin"
)
func Recover() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
ifr := recover(); r ! = nil { alarm.Panic(fmt.Sprintf("%s", r))
}
}()
c.Next()
}
}
Copy the code
Routing call middleware:
R.u. (LoggerToFile(), recover.recover ()) //Use can pass multiple middleware.Copy the code
So let’s check, let’s throw two exceptions and see if we can catch them, okay?
Instead, modify the product.go file.
Intentional panic:
package v1
import (
"fmt"
"ginDemo/entity"
"github.com/gin-gonic/gin"
"net/http") func AddProduct(c *gin.Context) {// Get the Get parameter name := c.query ("name")
var res = entity.Result{}
str, err := hello(name)
iferr ! = nil { res.SetCode(entity.CODE_ERROR) res.SetMessage(err.Error()) c.JSON(http.StatusOK, res) c.Abort()return
}
res.SetCode(entity.CODE_SUCCESS)
res.SetMessage(str)
c.JSON(http.StatusOK, res)
}
func hello(name string) (str string, err error) {
if name == ""{// Intentionally throw panic panic("i am panic")
return
}
str = fmt.Sprintf("hello: %s", name)
return
}
Copy the code
Visit: http://localhost:8080/v1/product/add
The interface is blank.
An exception is thrown with the following output:
{"time":"The 2019-07-23 22:42:37"."alarm":"PANIC"."message":"i am panic"."filename":"Absolute path/ginDemo/middleware/recover/recover. Go." "."line": 13."funcname":"1"}
Copy the code
Obviously, the file name, method name, and line number of the location are not what we want.
Caller(2) needs to be adjusted. This code is in the alarm method of alarm.go.
Change 2 to 4 and look at the output:
{"time":"The 2019-07-23 22:45:24"."alarm":"PANIC"."message":"i am panic"."filename":"Absolute path/ginDemo/router/v1 / product. Go." "."line": 33,"funcname":"hello"}
Copy the code
That’s right.
Panic:
Func hello(name string) (STR string, err error) {if name == ""Panic var slice = [] int {1, 2, 3, 4, 5} slice[6] = 6return
}
str = fmt.Sprintf("hello: %s", name)
return
}
Copy the code
Visit: http://localhost:8080/v1/product/add
The interface is blank.
An exception is thrown with the following output:
{"time":"The 2019-07-23 22:50:06"."alarm":"PANIC"."message":"runtime error: index out of range"."filename":"Absolute path/Runtime /panic. Go"."line": 44,"funcname":"panicindex"}
Copy the code
Obviously, the file name, method name, and line number of the location are not what we want.
Change 4 to 5 and look at the output:
{"time":"The 2019-07-23 22:55:27"."alarm":"PANIC"."message":"runtime error: index out of range"."filename":"Absolute path/ginDemo/router/v1 / product. Go." "."line": 34."funcname":"hello"}
Copy the code
That’s right.
That’s weird. Why is that?
Caller(skip) Caller(skip)
Skip refers to the depth of the call.
Is 0, prints the current calling file and the number of lines.
If the value is 1, the file and line number of the superior call are printed.
And so on…
In this case, be careful when you call it, I don’t have a good solution yet.
I’m going to skip when a parameter is passed in.
Such as:
Func WeChat (text string) error {alarm("WX", text, 2)
return&errorString{text}} // Panic error func Panic (text string) error {alarm("PANIC", text, 5)
return &errorString{text}
}
Copy the code
The specific code will not be posted.
However, if a Panic is thrown intentionally and unintentionally, the depth of the call is different.
1. Try to change intentional Panic to an error.
2. Find another way to do it.
I’ll stop there.
I will update the code involved to GitHub.
Recommended reading
Gin framework
- Gin Framework – Data binding and validation
- Gin Framework – Using Logrus logging
- Gin Framework – Installation and routing configuration
Based on article
- Go – function
- Go – cycle
- Go-map collection
- Go-struct structure
- Go-slice
- Go – array
- Go – Variable declaration
- Go – Environment installation
This article is welcome to forward, forward please indicate the author and source, thank you!