Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Бережная обработка ошибок в микросервисах

Бережная обработка ошибок в микросервисах

by Сергей Шамбир

Iskander (Alex) Sharipov

June 29, 2019
Tweet

More Decks by Iskander (Alex) Sharipov

Other Decks in Programming

Transcript

  1. Уровень domain использует только возможности Go Правила зависимостей 3 Уровень

    app использует только domain и возможности Go Пакет main всё создаёт, связывает и запускает Уровень infrastructure использует app и domain, а также технологии SQL, Redis, HTTP, GRPC и т.д. ◦ Максимум одна технология на один пакет
  2. Принципы обработки ошибок Предполагаем, что: • Логи нужны для анализа

    инцидентов • Ошибки и panic возникают при обработке запросов к API 4 Поэтому: • Все неожиданные ошибки попадают в лог • Любая ошибка или panic влияет только на один запрос
  3. Создание и передача ошибок Я не настоящий программист. Я сооружаю

    что-то, пока оно не заработает, и иду дальше. Настоящий программист скажет: “да, оно работает, но память течёт”. А я просто буду перезапускать Apache на каждые 10 запросов. Расмус Лердорф, автор языка PHP
  4. Можно заменить var на const package app type Error string

    func (e Error) Error() string { return string(e) } const ErrNoCake = Error("no cake found") 7 Источник: Constant errors | Dave Cheney
  5. Добавляйте информацию к сторонней ошибке package mysql import "github.com/pkg/errors" func

    (r *repository) FindOne(...) { row := r.client.QueryRow(sql, params...) switch err := row.Scan(...) { case sql.ErrNoRows: return nil, errors.WithStack(app.ErrNoCake) } } 8
  6. Используйте errors.Cause func (s *service) SaveCake(...) error { state, err

    := s.repo.FindOne(...) if errors.Cause(err) == ErrNoCake { err = nil // No cake is OK, create a new one // ... } else if err != nil { // ... } } 9
  7. Обрабатывайте ошибки в defer type UnitOfWork interface { Repository() Repository

    Complete(err *error) } 11 unit, err := factory.New() if err != nil { return err } defer unit.Complete(&err) repo := unit.Repository()
  8. Слияние ошибок при завершении операции func (u *unitOfWork) Complete(err *error)

    { if *err == nil { txErr := u.tx.Commit() *err = errors.Wrap(txErr, "cannot complete transaction ") } else { txErr := return u.tx.Rollback() *err = mergeErrors(*err, errors.Wrap(txErr, "cannot rollback transaction ")) } } 12
  9. Создайте функцию слияния ошибок package errors func mergeErrors(err error, nextErr

    error) error { if err == nil { err = nextErr } else if nextErr != nil { err = errors.Wrap(err, nextErr.Error()) } return err } 13
  10. Обработка ошибок Я определяю паттерн как идею, которая оказалась полезной

    в одном практическом контексте и, скорее всего, будет полезна в других Мартин Фаулер
  11. Как мы используем GRPC syntax = "proto3"; service Cooking {

    rpc ListCakes(ListCakesRequest) returns (ListCakesResponse) { option (google.api.http) = { get: "/api/v1/cakes" }; } } message ListCakesRequest { repeated string cookIDs = 3; } message ListCakesResponse { message Cake { float weight = 1; float price = 2; } 15
  12. Middleware для обработки ошибок 16 func NewMiddleware(n api.Server, l log.Logger)

    api.Server { server := &errorHandlingMiddleware{ next: n, logger: l, } return server }
  13. Реализация Middleware func (m *...) ListCakes( ctx context.Context, req *api.ListCakesRequest)

    (*api.ListCakesResponse, error) { start := time.Now() res, err := m.next.ListCakes(ctx, req) m.logCall(start, err, "ListCakes", log.Fields{ "cookIDs": req.CookIDs, }) return res, translateError(err) } 17
  14. Трансляция ошибки func translateError(err error) error { switch errors.Cause(err) {

    case app.ErrNoCake: err = status.Errorf(codes.NotFound, err.Error()) default: err = status.Errorf(codes.Internal, err.Error()) } return err } 18
  15. Трансляция ошибки type statusError interface { GRPCStatus() *status.Status } func

    translateError(err error) error { _, ok := err.(statusError) if ok { return err } switch errors.Cause(err) { case app.ErrNoCake: err = status.Errorf(codes.NotFound, err.Error()) 19
  16. Логирование вызова func (m *...) logCall(start time.Time, err error, method

    string, fields log.Fields) { fields["duration"] = fmt.Sprintf("%v", time.Since(start)) fields["method"] = method logger := m.logger.WithFields(fields) if err != nil { logger.Error(err, "call failed") } else { logger.Info("call finished") } 20
  17. Объявите свой интерфейс Logger package log type Logger interface {

    WithField(string, interface{}) Logger WithFields(Fields) Logger Debug(...interface{}) Info(...interface{}) Error(error, ...interface{}) } 21 Источник: Давайте поговорим о ведении логов (habr.com)
  18. 1. Установите формат: logrus.JSONFormatter 2. Установите log level 3. Добавьте

    получение stacktrace Нормализация полей logrus для ELK: Создайте свой пакет логирования 24 time @timestamp msg message
  19. Инициализируйте logger в main func initLogger(config Config) (log.MainLogger, error) {

    logLevel, err := jsonlog.ParseLevel(config.LogLevel) if err != nil { return nil, rrors.Wrap(err, "failed to parse log level" ) } return jsonlog.NewLogger(&jsonlog.Config{ Level: logLevel, AppName: "resumestate" }), nil } 25
  20. Подытожим 1. Используйте github.com/pkg/errors, читайте блог Dave Chaney ◦ Напишите

    в дополнение свою библиотеку errors 2. Напишите свои пакеты log и jsonlog 3. Используйте чистую архитектуру, читайте книги Robert Martin 4. Используйте паттерн Middleware, чтобы обрабатывать ошибки в едином месте 26