Tracing

Ruby defines a number of features for tracing the execution of a program. These are mainly useful for debugging code and printing informative error messages. Two of the simplest features are actual language keywords: __FILE__ and __LINE__. These keyword expressions always evaluate to the name of the file and the line number within that file on which they appear, and they allow an error message to specify the exact location at which it was generated:

STDERR.puts "#{__FILE__}:#{__LINE__): invalid data"

As an aside, note that the methods Kernel.eval, Object.instance_eval, and Module.class_eval all accept a filename (or other string) and a line number as their final two arguments. If you are evaluating code that you have extracted from a file of some sort, you can use these arguments to specify the values of __FILE__ and __LINE__ for the evaluation.

You have undoubtedly noticed that when an exception is raised and not handled, the error message printed to the console contains filename and line number information. This information is based on __FILE__ and __LINE__, of course. Every Exception object has a backtrace associated with it that shows exactly where it was raised, where the method that raised the exception was invoked, where that method was invoked, and so on. The Exception.backtrace method returns an array of strings containing this information. The first element of this array is the location at which the exception occurred, and each subsequent element is one stack frame higher.

You needn’t raise an exception to obtain a current stack trace, however. The Kernel.caller method returns the current state of the call stack in the same form as Exception.backtrace. With no argument, caller returns a stack trace whose first element is the method that invoked the method that calls caller. That is, caller[0] specifies the location from which the current method was invoked. You can also invoke caller with an argument that specifies how many stack frames to drop from the start of the backtrace. The default is 1, and caller(0)[0] specifies the location at which the caller method is invoked. This means, for example, that caller[0] is the same thing as caller(0)[1] and that caller(2) is the same as caller[1..-1].

Stack traces returned by Exception.backtrace and Kernel.caller also include method names. Prior to Ruby 1.9, you must parse the stack trace strings to extract method names. In Ruby 1.9, however, you can obtain the name (as a symbol) of the currently executing method with Kernel.__method__ or its synonym, Kernel.__callee__. __method__ is useful in conjunction with __FILE__ and __LINE__:

raise "Assertion failed in #{__method__} at #{__FILE__}:#{__LINE__}"

Note that __method__ returns the name by which a method was originally defined, even if the method was invoked through an alias.

Instead of simply printing the filename and number at which an error occurs, you can take it one step further and display the actual line of code. If your program defines a global constant named SCRIPT_LINES__ and sets it equal to a hash, then the require and load methods add an entry to this hash for each file they load. The hash keys are filenames and the values associated with those keys are arrays that contain the lines of those files. If you want to include the main file (rather than just the files it requires) in the hash, initialize it like this:

SCRIPT_LINES__ = {__FILE__ => File.readlines(__FILE__)}

If you do this, then you can obtain the current line of source code anywhere in your program with this expression:

SCRIPT_LINES__[__FILE__][__LINE__-1]

Ruby allows you to trace assignments to global variables with Kernel.trace_var. Pass this method a symbol that names a global variable and a string or block of code. When the value of the named variable changes, the string will be evaluated or the block will be invoked. When a block is specified, the new value of the variable is passed as an argument. To stop tracing the variable, call Kernel.untrace_var. In the following example, note the use of caller[1] to determine the program location at which the variable tracing block was invoked:

# Print a message every time $SAFE changes
trace_var(:$SAFE) {|v|
  puts "$SAFE set to #{v} at #{caller[1]}"
}

The final tracing method is Kernel.set_trace_func, which registers a Proc to be invoked after every line of a Ruby program. set_trace_func is useful if you want to write a debugger module that allows line-by-line stepping through a program, but we won’t cover it in any detail here.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.34.39