у меня два вопроса про ошибке 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т Жыл бұрын
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 Жыл бұрын
@@Дмитрий-щ8т3т спасибо за ответ, после ознакомления с fmt.Errorf("DBExec: %w", err), понял как он работает, что %w это врапер, интересно то, что оно работает только если %w в начале строки или в конце, такое впечатление что идет сравнение строк.
@Дмитрий-щ8т3т Жыл бұрын
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