мы в одном из проектов "грубо говоря" использовали три кода так:
200 success
400 - известная ошибка, например не прошла валидация или что-то подобное body: {error_code: 12345, error: message, some_additional_info ... }
500 - не известная ошибка
400 и 500 обрабатывались в ApplicationController-е, все известные ошибки наследовались от AppBaseError, по этому можно было просто сделать rescue_from AppBaseError -> 400; StandardError -> 500
другие коды старались не использовать, что бы "быстрее" отделять ошибки уровня rails от ошибок уровня nginx
логирование действительно отдельная тема, в выше упомянутом проекте было помимо info/debug/error было у логов еще что-то типа ttl, потому что некоторые логи занимали по 2mb (ответы external api) - такие логи жили час по дефолту, и их можно было "сохранить", если что-то пошло не так (всплеск ошибок). Логировалось буквально все.
вот такое мне уже больше нравится