Как заставить рубин распечатать полную трассировку вместо усеченной?

170

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

tmp.rb:7:in `t': undefined method `bar' for nil:NilClass (NoMethodError)
        from tmp.rb:10:in `s'
        from tmp.rb:13:in `r'
        from tmp.rb:16:in `q'
        from tmp.rb:19:in `p'
        from tmp.rb:22:in `o'
        from tmp.rb:25:in `n'
        from tmp.rb:28:in `m'
        from tmp.rb:31:in `l'
         ... 8 levels...
        from tmp.rb:58:in `c'
        from tmp.rb:61:in `b'
        from tmp.rb:64:in `a'
        from tmp.rb:67

Это усечение "... 8 уровней ..." доставляет мне много хлопот. У меня нет особого успеха в поиске в Google: как мне сказать ruby, что я хочу, чтобы дампы включали полный стек?

Sniggerfardimungus
источник
2
Есть ли способ сделать это из командной строки вместо этого?
Эндрю Гримм

Ответы:

241

Исключение # backtrace содержит весь стек:

def do_division_by_zero; 5 / 0; end
begin
  do_division_by_zero
rescue => exception
  puts exception.backtrace
  raise # always reraise
end

(Вдохновлено блогом Питера Купера Ruby Inside )

Gareth
источник
15
Я бы повторил исключение, по крайней мере, для полноты примеров.
до
13
Для ререйза нужно просто сказать raise. Нет необходимости явно указывать исключение, которое вы хотите поднять.
Тимо
Хорошо, я всегда думал, что ты должен был пройти предыдущее исключение для повышения. Я не осознавал, что по умолчанию спасено последнее исключение.
открывает
Что если ваш код не выдает исключение, вы просто хотите увидеть трассировку стека того, куда он пошел?
Алекс Левин
170

Вы также можете сделать это, если вы хотите простой однострочный:

puts caller
анонимный трус
источник
2
Удивительный трюк. Большое спасибо. Я не знал, что raiseможно использовать без аргументов. Также я не знал, что rescueбудет правильно трактоваться как однострочник. Я также полностью игнорирую эти глобальные переменные как $!.
Дмитрий Нагирняк
11
не нужно поднимать / спасать, вы можете просто использовать Kernel # caller, например так:puts "this line was reached by #{caller.join("\n")}"
Стивен С.
Ах, я узнал об этом вскоре после публикации этого ответа и забыл его обновить. Спасибо
анонимный трус
Я использую, y callerчтобы напечатать вывод, как трассировка стека Java.
so_mv
caller(0,2)вернет две последние записи в трассировке стека. Отлично подходит для вывода сокращенных стековых трасс.
Магне
100

Это дает описание ошибки и красивую чистую трассировку с отступом:

begin               
 # Some exception throwing code
rescue => e
  puts "Error during processing: #{$!}"
  puts "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
end
Бен
источник
49

IRB имеет настройку для этой ужасной «функции», которую вы можете настроить.

Создайте файл с именем ~/.irbrc, включающим следующую строку:

IRB.conf[:BACK_TRACE_LIMIT] = 100

Это позволит вам увидеть irbкак минимум 100 кадров стека . Мне не удалось найти эквивалентную настройку для неинтерактивной среды выполнения.

Подробную информацию о настройке IRB можно найти в книге «Кирка» .

robinluckey
источник
3
Это должен быть принятый ответ, потому что он рассматривает вопрос о том, как показать больше обратной трассировки вместо "... X уровней ...".
nickh
13

Один лайнер для callstack:

begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace; end

Один лайнер для callstack без всех драгоценных камней:

begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace.grep_v(/\/gems\//); end

Один лайнер для callstack без всех драгоценных камней и относительно текущего каталога

begin; Whatever.you.want; rescue => e; puts e.message; puts; puts e.backtrace.grep_v(/\/gems\//).map { |l| l.gsub(`pwd`.strip + '/', '') }; end
Дориан
источник
2
на самом деле плохая вещь, когда у вас есть несколько утверждений.
nurettin
3
@nurettin это для быстрой отладки, так что, сделав его одной строкой, можно легко скопировать и вставить его, в основном в интерактивных оболочках
Дориан
@Dorian Вы напоминаете мне вопрос, который у меня возник: «Почему полезны интерактивные оболочки? (Исключая Shell-скрипт)».
Sapphire_Brick
9

Это имитирует официальную трассировку Ruby, если это важно для вас.

begin
  0/0  # or some other nonsense
rescue => e
  puts e.backtrace.join("\n\t")
       .sub("\n\t", ": #{e}#{e.class ? " (#{e.class})" : ''}\n\t")
end

Забавно, что он не обрабатывает «необработанное исключение» должным образом, сообщая о нем как «RuntimeError», но местоположение является правильным.

android.weasel
источник
Я сожалею, что у меня есть только один голос за ваш ответ. Я добавляю это везде
Dbz
4

Я получал эти ошибки, когда пытался загрузить свою тестовую среду (с помощью rake test или autotest), и предложения IRB не помогли. В итоге я завернул весь файл test / test_helper.rb в блок begin / rescue, и это все исправило.

begin
  class ActiveSupport::TestCase
    #awesome stuff
  end
rescue => e
  puts e.backtrace
end
Райан Энгилли
источник
0

[исследуйте все обратные пути потоков, чтобы найти виновника]
Даже полностью расширенный стек вызовов по-прежнему может скрыть от вас фактическую строку кода, когда вы используете более одного потока!

Пример: один поток выполняет итерацию ruby ​​Hash, другой поток пытается его изменить. БУМ! Исключение! И проблема с трассировкой стека, которую вы получаете при попытке изменить «занятый» хеш, состоит в том, что она показывает цепочку функций вплоть до того места, где вы пытаетесь изменить хеш, но она НЕ показывает, кто в данный момент выполняет ее параллельно ( кому это принадлежит)! Вот способ выяснить это путем печати трассировки стека для ВСЕХ запущенных потоков. Вот как вы это делаете:

# This solution was found in comment by @thedarkone on https://github.com/rails/rails/issues/24627
rescue Object => boom

    thread_count = 0
    Thread.list.each do |t|
      thread_count += 1
      err_msg += "--- thread #{thread_count} of total #{Thread.list.size} #{t.object_id} backtrace begin \n"
      # Lets see if we are able to pin down the culprit
      # by collecting backtrace for all existing threads:
      err_msg += t.backtrace.join("\n")
      err_msg += "\n---thread #{thread_count} of total #{Thread.list.size} #{t.object_id} backtrace end \n"
    end

    # and just print it somewhere you like:
    $stderr.puts(err_msg)

    raise # always reraise
end

Приведенный выше фрагмент кода полезен даже для образовательных целей, поскольку он может показать вам (например, рентген), сколько потоков у вас на самом деле (по сравнению с тем, сколько, как вы думаете, у вас есть - довольно часто эти два числа разные);

Дмитрий Шевкопляс
источник
0

Вы также можете использовать обратную трассировку Ruby gem (я автор):

require 'backtrace'
begin
  # do something dangerous
rescue StandardError => e
  puts Backtrace.new(e)
end
yegor256
источник
4
Вы можете хотя бы объяснить, почему мы хотим использовать ваш драгоценный камень? Можете ли вы показать пример вывода?
ioquatix