Обработка ошибок в Go | Дмитрий Лукиянчук | Golang Meetup 2022| СберМаркет Tech

Поділитися
Вставка
  • Опубліковано 28 січ 2025

КОМЕНТАРІ • 5

  • @Freeline95
    @Freeline95 Рік тому

    Отличное видео, спасибо.

  • @evgen_sr
    @evgen_sr Рік тому

    у меня два вопроса про ошибке
    1. пишите ли вы в лог Caller? если да, что будет в логе? самый верхний уровень? или весь stack trace? Не засоряет это логи? да и в проде весь стек выводить странная затея.
    2. как я понял из доклада, вы заменяете ошибки, например БД своими, и сразу пробрасываете ее наверх?
    Например, запрос в БД
    var(
    ErrSQL = errors.New("...")
    )
    rows, err := s.db.Query(
    sqlStatement,
    )
    if err != nil {
    // что тут возврощать?
    // так мы получаем точную ошибку sql и понимаем что именно произошло, но привязываемся к реализации (PG одна ошибка, в mysql другая) и не можем проверить через Is без точного понимания типа ошибки
    return nil, err
    // или мы возврощаем свою ошибку, но создаем список ошибок, о чем вы говорили, но тут теряем именно само сообщение, что именно произошло, но может отвязаться от реализации, например storage может быть на memory map
    return nil, storage.ErrSQL
    // или делаем как в других проектах, например gitea
    return nil, fmt.Errorf("err: %s", err)
    }

    • @Дмитрий-щ8т3т
      @Дмитрий-щ8т3т Рік тому +1

      1.а. пишите ли вы в лог Caller?
      В том приложении, про которое рассказываю - не пишем
      1.б. если да, что будет в логе? самый верхний уровень? или весь stack trace?
      Задача, которую обычно хотят решить выводом caller или всего stack trace - найти точное место, где возникла ошибка и её причины. Этого можно достичь по-разному.
      - Мы, в основном, достигаем этого тем, что делаем wrap ошибки при каждом пробросе. Тогда по полной ошибке можно понять где она возникла. Пример:
      ```
      err := DBExec();
      if err != nil {
      return fmt.Errorf("DBExec: %w", err)
      }
      ```
      - Можно добавлять логи где это возможно. Тогда в логе вы захотите выводить файл и строку, там где логгер был вызван.
      Мы используем под капотом pkg.go.dev/go.uber.org/zap, и он умеет выводить caller из коробки.
      - Ещё один способ - задавать логгеру namespace в разных слоях кода свой - В zap это pkg.go.dev/go.uber.org/zap#Namespace .
      - Stacktrace мы выводим только в случае паники - в golang, как вы знаете, паника должна быть исключительной ситуацией.
      1. в. Не засоряет это логи?
      - Чтобы не засорять логи, нужно управлять уровнем логирования - в большинстве логгеров есть уровни FATAL, ERROR, WARNING, INFO, DEBUG.
      В обычном режиме выставляете уровень INFO+/WARNING+, а DEBUG включать только для диагностики.
      1. г. да и в проде весь стек выводить странная затея.
      - В проде лучше отправлять стек в Sentry с множеством дополнительных полей. sentry.io/
      Caller

    • @evgen_sr
      @evgen_sr Рік тому

      @@Дмитрий-щ8т3т спасибо за ответ, после ознакомления с fmt.Errorf("DBExec: %w", err), понял как он работает, что %w это врапер, интересно то, что оно работает только если %w в начале строки или в конце, такое впечатление что идет сравнение строк.

    • @Дмитрий-щ8т3т
      @Дмитрий-щ8т3т Рік тому +1

      2. как я понял из доклада, вы заменяете ошибки, например БД своими, и сразу пробрасываете ее наверх? ...
      Идея доклада в том, что слой бизнес логики, обращающийся к БД дожен принять решение, как обработать ошибку. Если он не смог с ней справиться - сделать ретрай или выбрать другой способ решения своей задачи, то любой слой выше тем более не сможет. В этом случае слой выше может разве что проигнорировать ошибку или упасть, распечатав ошибку в логи, чтобы вы могли её изучить и починить проблему.
      Если вопрос в том, как в этом случае сохранить информацию для диагностики, то ответ - лучше всего сделать wrap ошибки (обратите внимание на `%w`):
      ```
      err := DBExec();
      if err != nil {
      return fmt.Errorf("DBExec: %w", err)
      }
      ```
      Тогда на верхнем уровне вы сможете распечатать всю цепочку через fmt.Print или err.Error().
      Также wrapping помогает дотянуться до любой из вложенных ошибок через errors.Is или errors.As - подробнее читайте в go блоге: go.dev/blog/go1.13-errors