[Серия] Gin Framework — Пользовательская обработка ошибок

Go

Обзор

Многие читатели просили меня предоставить исходный код демо-версии реальной боевой серии Gin framework в фоновом режиме. Позвольте мне еще раз объяснить здесь. Я обновил исходный код на GitHub по адресу:GitHub.com/Бессердечный не…

Начните сегодняшнюю статью Зачем настраивать обработку ошибок? Какова обработка ошибок по умолчанию?

Что ж, давайте сначала поговорим об обработке ошибок по умолчанию.

Обработка ошибок по умолчаниюerrors.New("错误信息"), эта информация возвращается через возвращаемое значение типа error.

Возьмем простой пример:

func hello(name string) (str string, err error) {
	if name == "" {
		err = errors.New("name 不能为空")
		return
	}
	str = fmt.Sprintf("hello: %s", name)
	return
}

При вызове этого метода:

var name = ""
str, err :=  hello(name)
if err != nil {
	fmt.Println(err.Error())
	return
}

Это обработка ошибок по умолчанию, и этот пример будет использоваться ниже.

Этот обработчик ошибок по умолчанию просто получает строку сообщений об ошибках.

Однако...

Я также хочу получить, когда возникает ошибка时间,文件名,方法名,行号и другая информация.

Я также хочу получать оповещения об ошибках, например短信告警,邮件告警,微信告警Ждать.

Когда я все же хочу позвонить, это не так сложно, это похоже на обработку ошибок по умолчанию, например:

alarm.WeChat("错误信息")
return

Таким образом, мы получаем нужную нам информацию (时间,文件名,方法名,行号), и через微信способ предупредить нас.

Так же,alarm.Email("错误信息"),alarm.Sms("错误信息")Информация, которую мы получаем, одинакова, но метод оповещения отличается.

Также убедитесь, что в нашей бизнес-логике, когда мы получаем ошибки, мы получаем только информацию об ошибках.

Приведенные выше идеи - это то, что мы хотим реализовать сегодня. Пользовательская обработка ошибок. Прежде чем мы это реализуем, давайте поговорим об обработке ошибок Go.

обработка ошибок

package main

import (
	"errors"
	"fmt"
)

func hello(name string) (str string, err error) {
	if name == "" {
		err = errors.New("name 不能为空")
		return
	}
	str = fmt.Sprintf("hello: %s", name)
	return
}

func main() {
	var name = ""
	fmt.Println("param:", name)

	str, err := hello(name)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	fmt.Println(str)
}

вывод:

param: Tom
hello: Tom

Когда имя = "", вывод:

param:
name 不能为空

Рекомендуется, чтобы каждая функция имела обработку ошибок, и ошибка должна быть последним возвращаемым значением.

Давайте посмотрим на официальные ошибки.

// 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
}

Вышеприведенный код не сложен.Ссылаясь на вышеизложенное, давайте напишем собственную обработку ошибок.

пользовательская обработка ошибок

Мы определяем файл alarm.go для обработки сигналов тревоги.

Без лишних слов, просто посмотрите на код.

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}
}

// 发邮件
func Email (text string) error {
	alarm("EMAIL", text)
	return &errorString{text}
}

// 发短信
func Sms (text string) error {
	alarm("SMS", text)
	return &errorString{text}
}

// 发微信
func WeChat (text string) error {
	alarm("WX", text)
	return &errorString{text}
}

// 告警方法
func  alarm(level string, str string) {
	// 当前时间
	currentTime := function.GetTimeStr()

	// 定义 文件名、行号、方法名
	fileName, line, 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)

	if errs != nil {
		fmt.Println("json marshal error:", errs)
	}

	errorJsonInfo := string(jsons)

	fmt.Println(errorJsonInfo)

	if level == "EMAIL" {
		// 执行发邮件

	} else if level == "SMS" {
		// 执行发短信

	} else if level == "WX" {
		// 执行发微信

	} else if level == "INFO" {
		// 执行记日志
	}
}

Посмотрите, как звонить:

package v1

import (
	"fmt"
	"ginDemo/common/alarm"
	"ginDemo/entity"
	"github.com/gin-gonic/gin"
	"net/http"
)

func AddProduct(c *gin.Context)  {
	// 获取 Get 参数
	name := c.Query("name")

	var res = entity.Result{}

	str, err := hello(name)
	if err != 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 不能为空")
		return
	}
	str = fmt.Sprintf("hello: %s", name)
	return
}

доступ:http://localhost:8080/v1/product/add?name=a

{
    "code": 1,
    "msg": "hello: a",
    "data": null
}

Ошибок не выдает, информация не выводится.

доступ:http://localhost:8080/v1/product/add

{
    "code": -1,
    "msg": "name 不能为空",
    "data": null
}

Была выброшена ошибка, и вывод выглядит следующим образом:

{"time":"2019-07-23 22:19:17","alarm":"WX","message":"name 不能为空","filename":"绝对路径/ginDemo/router/v1/product.go","line":33,"funcname":"hello"}

Некоторые учащиеся могут сказать: «Используя привязку данных и проверку, описанную в предыдущей статье, можно также привязать входящие параметры: «требуется»».

Я могу только сказать: «Студенты, вы не понимаете моих добрых намерений, это всего лишь пример, вы можете использовать собственную обработку ошибок в некоторых сложных сценариях оценки бизнес-логики».

На данный момент мы получили ошибку при сообщении时间,错误信息,文件名,行号,方法名.

Это также относительно просто позвонить.

Несмотря на то, что режим тревоги отмечен, уведомления о тревоге по-прежнему нет.

Я хочу сказать, что хранение данных в очереди здесь, а затем выполнение асинхронных задач для их потребления конкретно, эта часть не будет реализована, и каждый может ее улучшить.

читать文件名,方法名,行号используетruntime.Caller().

Мы также знаем, что в Go естьpanicиrecover, что они делают, поговорим об этом далее.

паникуй и выздоравливай

Когда программа не может продолжать работать, вы должны использовать панику для выдачи ошибок.

Когда программа паникует, recovery можно вызвать внутри defer (функция задержки) для управления, но есть предварительное условие, которое можно сделать только в той же сопрограмме Go.

Есть два типа паники, один вызывается преднамеренно, другой вызван непреднамеренным программированием, давайте поговорим о них по порядку.

Намеренно брошенная паника:

package main

import (
	"fmt"
)

func main() {

	fmt.Println("-- 1 --")

	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("panic: %s\n", r)
		}
		fmt.Println("-- 2 --")
	}()
	
	panic("i am panic")
}

вывод:

-- 1 --
panic: i am panic
-- 2 --

Непреднамеренно брошенная паника:

package main

import (
	"fmt"
)

func main() {

	fmt.Println("-- 1 --")

	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("panic: %s\n", r)
		}
		fmt.Println("-- 2 --")
	}()


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

	slice[6] = 6
}

вывод:

-- 1 --
panic: runtime error: index out of range
-- 2 --

Оба вышеперечисленных мы проходимrecoverЗахвачено, так как же мы можем использовать его во фреймворке Gin? если полученоpanicКогда я хочу поставить будильник, как мне этого добиться?

Поскольку вы хотите реализовать будильник, сначала определите будильник в aarm.go.Panic()метод, когда элемент происходитpanicКогда возникает исключение, этот метод вызывается для получения сигнала тревоги.

// Panic 异常
func Panic (text string) error {
	alarm("PANIC", text)
	return &errorString{text}
}

Так как же нам его поймать?

Используйте промежуточное ПО для захвата, напишитеrecoverпромежуточное ПО.

package recover

import (
	"fmt"
	"ginDemo/common/alarm"
	"github.com/gin-gonic/gin"
)

func Recover()  gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if r := recover(); r != nil {
				alarm.Panic(fmt.Sprintf("%s", r))
			}
		}()
		c.Next()
	}
}

Промежуточное ПО маршрутизации вызовов:

r.Use(logger.LoggerToFile(), recover.Recover())

//Use 可以传递多个中间件。

Давайте проверим, давайте сначала выкинем два исключения и посмотрим, сможем ли мы их поймать?

Или измените файл product.go.

Намеренно бросать панику:

package v1

import (
	"fmt"
	"ginDemo/entity"
	"github.com/gin-gonic/gin"
	"net/http"
)

func AddProduct(c *gin.Context)  {
	// 获取 Get 参数
	name := c.Query("name")

	var res = entity.Result{}

	str, err := hello(name)
	if err != 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 == "" {
		// 有意抛出 panic
		panic("i am panic")
		return
	}
	str = fmt.Sprintf("hello: %s", name)
	return
}

доступ:http://localhost:8080/v1/product/add

Интерфейс пустой.

Возникло исключение, и выходные данные выглядят следующим образом:

{"time":"2019-07-23 22:42:37","alarm":"PANIC","message":"i am panic","filename":"绝对路径/ginDemo/middleware/recover/recover.go","line":13,"funcname":"1"}

Очевидно, что найденное имя файла, имя метода и номер строки — это не то, что нам нужно.

нужно настроитьruntime.Caller(2), этот код находится вalarm.go 的 alarmметод.

Отрегулируйте 2 до 4 и посмотрите на результат:

{"time":"2019-07-23 22:45:24","alarm":"PANIC","message":"i am panic","filename":"绝对路径/ginDemo/router/v1/product.go","line":33,"funcname":"hello"}

теперь это правильно.

Непреднамеренно вызывает панику:

// 上面代码不变

func hello(name string) (str string, err error) {
	if name == "" {
		// 无意抛出 panic
		var slice = [] int {1, 2, 3, 4, 5}
		slice[6] = 6
		return
	}
	str = fmt.Sprintf("hello: %s", name)
	return
}

доступ:http://localhost:8080/v1/product/add

Интерфейс пустой.

Возникло исключение, и выходные данные выглядят следующим образом:

{"time":"2019-07-23 22:50:06","alarm":"PANIC","message":"runtime error: index out of range","filename":"绝对路径/runtime/panic.go","line":44,"funcname":"panicindex"}

Очевидно, что найденное имя файла, имя метода и номер строки — это не то, что нам нужно.

Отрегулируйте 4 до 5 и посмотрите на результат:

{"time":"2019-07-23 22:55:27","alarm":"PANIC","message":"runtime error: index out of range","filename":"绝对路径/ginDemo/router/v1/product.go","line":34,"funcname":"hello"}

теперь это правильно.

Странно, почему это?

Тут надо сказатьruntime.Caller(skip).

пропуск относится к глубине вызова.

При 0 вывести текущий вызывающий файл и количество строк.

Когда он равен 1, вывести номер файла и строки, вызванные вышестоящим.

И так далее...

На эту штуку нужно обратить внимание при звонке, хорошего решения у меня пока нет.

Я пропускаю (глубину вызова), когда передается параметр.

Например:

// 发微信
func WeChat (text string) error {
	alarm("WX", text, 2)
	return &errorString{text}
}

// Panic 异常
func Panic (text string) error {
	alarm("PANIC", text, 5)
	return &errorString{text}
}

Конкретный код не будет опубликован.

Тем не менее, глубина вызова преднамеренного запуска Паники и непреднамеренного запуска Паники различна, что мне делать?

1. Попробуйте изменить намеренно выброшенную Панику так, чтобы она выдавала ошибку.

2, подумайте о других способах его получения.

Просто здесь.

Я обновлю задействованный в нем код на GitHub.

Рекомендуемое чтение

Джин каркас

Основы

Эту статью можно переслать, пожалуйста, укажите автора и источник для пересылки, спасибо!