8.6. 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.
No comments:
Post a Comment