Catching Line Numbers in Ruby Exceptions

Catching line numbers in ruby exceptions

p e.backtrace 

I ran it on an IRB session which has no source and it still gave relevant info.

=> ["(irb):11:in `foo'", 
"(irb):17:in `irb_binding'",
"/usr/lib64/ruby/1.8/irb/workspace.rb:52:in `irb_binding'",
"/usr/lib64/ruby/1.8/irb/workspace.rb:52"]

If you want a nicely parsed backtrace, the following regex might be handy:

p x.backtrace.map{ |x|   
x.match(/^(.+?):(\d+)(|:in `(.+)')$/);
[$1,$2,$4]
}

[
["(irb)", "11", "foo"],
["(irb)", "48", "irb_binding"],
["/usr/lib64/ruby/1.8/irb/workspace.rb", "52", "irb_binding"],
["/usr/lib64/ruby/1.8/irb/workspace.rb", "52", nil]
]

( Regex /should/ be safe against weird characters in function names or directories/filenames )
( If you're wondering where foo camefrom, i made a def to grab the exception out :

>>def foo
>> thisFunctionDoesNotExist
>> rescue Exception => e
>> return e
>>end
>>x = foo
>>x.backtrace

Is it possible to get the line number that threw an error?

Just take the backtrace:

begin
. . .
# error occurs here
. . .
rescue => error
puts "Error: " + error.message
puts error.backtrace
end

To get only the line number - just parse it out of the backtrace via a regex.

More information can be found here: Catching line numbers in ruby exceptions

Get the error line in a Ruby Opal code

Opal has source map support, to facilitate this kind of source level of debugging. I will not go into details about sourcemaps, but HTML5Rocks has a great article that covers the topic in depth.

Here is the minimal boilerplate to set that up with Opal:

Let index.rb be our source file:

class Test

def initialize

end

def crash
print x
end

end

Test.new.crash

Since you would rather not use a lot of extraneous utilties, let us directly use the Opal API. Create a file builder.rb which will compile the file above:

require 'opal'
Opal::Processor.source_map_enabled = true
Opal.append_path "."

builder = Opal::Builder.new.build('index')

# Write the output file containing a referece to sourcemap
# which we generate below : this will help the browser locate the
# sourcemap. Note that we are generating sourcemap for only code and not
# the entire Opal corelib.
#
File.binwrite "build.js", "#{builder.to_s}\n//# sourceMappingURL=build.js.map"
File.binwrite "build.js.map", builder.source_map.to_s

File.binwrite "opal_lib.js", Opal::Builder.build('opal_lib')

Also create an opal_lib.rb file containing only:

require 'opal'

Finally create an index.html which will allow us to run the script in browser.

<!DOCTYPE html>
<html>
<head>
<script src="opal_lib.js"></script>
<script src="build.js"></script>
</head>
<body>
</body>
</html>

Now to actually compile your file, run:

ruby builder.rb

This will generate compiled javascript files opal_lib.js and build.js which are referenced by our index.html file. Now just open index.html in your browser. You will get a full call-stack and source view:

Opal error stack screenshot

The line numbers of the source file are available:

Opal error stack trace


As an alternative to using the browser, you can also use Node.js for the same purpose. This requires you have Node.js and npm installed. You will also need to install npm module source-map-support

npm install source-map-support

Now you can open the node repl and enter the following:

require('source-map-support').install();
require('./opal_lib');
require('./build');

You will get a stack trace with correct source line numbers :

/home/gaurav/Workspace/opal-playground/opal_lib.js:4436
Error.captureStackTrace(err);
^
NoMethodError: undefined method `x' for #<Test:0x102>
at OpalClass.$new (/home/gaurav/Workspace/opal-playground/opal_lib.js:4436:15)
at OpalClass.$exception (/home/gaurav/Workspace/opal-playground/opal_lib.js:4454:31)
at $Test.$raise (/home/gaurav/Workspace/opal-playground/opal_lib.js:4204:31)
at $Test.Opal.defn.TMP_1 (/home/gaurav/Workspace/opal-playground/opal_lib.js:3032:19)
at $Test.method_missing_stub [as $x] (/home/gaurav/Workspace/opal-playground/opal_lib.js:886:35)
at $Test.$crash (/home/gaurav/Workspace/opal-playground/index.rb:8:11)
at /home/gaurav/Workspace/opal-playground/index.rb:13:10
at Object.<anonymous> (/home/gaurav/Workspace/opal-playground/index.rb:13:10)
at Module._compile (module.js:435:26)
at Object.Module._extensions..js (module.js:442:10)

I recommend that you use bundler for gem management. Here is the Gemfile for fetching Opal master:

source 'http://production.cf.rubygems.org'

gem 'opal', github: 'opal/opal'

To compile you will have to run:

bundle install
bundle exec ruby builder.rb

Sprockets integration / rack integration that others have mentioned use the same API underneath, abstracting away the plumbing.


Update:

Since we have the correct line numbers in stack, it is fairly to programatically parse the stack and extract this line number into a variable:

require('./opal_lib');
require('source-map-support').install();
var $e = null;
try {
require('./build');
} catch (e) {
$e = e;
}
var lines = e.split('\n').map(function(line){ return line.match(/^.*\((\S+):(\d+):(\d+)\)/) })

var first_source_line;

for (var i = 0; i < lines.length ; i++) {
var match = lines[i];
if (match == null) continue;
if (match[1].match(/index.rb$/) {
first_source_line = match;
break;
}
}

var line_number;
if (first_source_line) line_number = first_source_line[2] // ==> 8

And of course you can do it ruby as well (but if you are running this in browser you will have to include source-map-support here as well):

class Test

def initialize

end

def crash
print x
end

end

line_num = nil
begin
Test.new.crash
rescue => e
if line = e.backtrace.map{|line| line.match(/^.*\((\S+):(\d+):(\d+)\)/) }.compact.find{|match| match[1] =~ /index.rb$/ }
line_num = line[2]
end
end

puts "line_num => #{line_num}" # ==> 8

How to do exception handling for undefined local variables?

You can write

d = a + e rescue nil

which would catch the exception and assigns d = nil.

Or

begin
d = a + e
rescue
end

Which only catches the exception and does nothing else because of the empty rescue block.

But I would consider this a bad practice. Because apart from in the console you should never run into this issue in real-life. This error is trivial and should be noticed and fixed before the app gets into production.

Ruby - Random number in range with exceptions

shuffle will permute the entire array, which is potentially slow for large arrays. sample is a much faster operation

(1..99999).to_a.sample(3)

For benchmarking purposes:

> require 'benchmark'
> arr = (0..99999).to_a; 0
> Benchmark.realtime { 10_000.times { arr.sample(3) } }
=> 0.002874
> Benchmark.realtime { 10_000.times { arr.shuffle[0,3] } }
=> 18.107669


Related Topics



Leave a reply



Submit