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


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, что я хочу, чтобы дампы включали полный стек?


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 )


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 можно найти в книге «Кирка» .


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», но местоположение является правильным.


Я сожалею, что у меня есть только один голос за ваш ответ. Я добавляю это везде
— 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

4
Вы можете хотя бы объяснить, почему мы хотим использовать ваш драгоценный камень? Можете ли вы показать пример вывода?
— ioquatix
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.