A backtrace is data you can read¶
Level: 201 · a Python programmer who has used traceback and inspect and wants the Ruby names for the same frames
One line: caller returns an Array of Strings shaped file:line:in 'Klass#method', caller_locations returns the same frames as objects with label, path and lineno, and a rescued exception carries both as backtrace and backtrace_locations — the twins of traceback.extract_stack, inspect.stack and e.__traceback__, with 4.0's Klass#method labels matching Python's co_qualname.
Three sources of the same information. Inside a running method, caller (the callers, innermost first) and caller_locations(start, count) describe the stack right now; __method__ names the current method as a Symbol. A raised exception freezes that stack: e.backtrace is the string form and e.backtrace_locations the object form, both nil until the exception is actually raised. And e.full_message is what the interpreter prints for an uncaught exception — the first line holds the location, the message and the class — while e.detailed_message is the message with the class appended and, when error_highlight is loaded (it is, by default), a snippet of the failing source under it. The labels changed in 3.4: 'Worker#inner' instead of `inner', so a frame now says which class the method belongs to; base_label is the bare method name. The program masks the one line that carries a path and a line number, and reduces every other frame to its label with a regex, so the key is stable.
Verified output of backtraces_and_caller_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
1. caller, reduced to labels: ["Worker#outer", "<main>"]
2. caller_locations(0, 3) labels: ["Worker#inner", "Worker#outer", "<main>"]
3. ...and their base_labels: ["inner", "outer", "<main>"]
4. __method__ inside the method: :inner
5. a location has a path and a line: backtraces_and_caller_rb.rb, lineno is Integer
6. e.backtrace (Array) labels: ["Worker#inner", "Worker#outer", "<main>"]
7. e.backtrace_locations labels: ["Worker#inner", "Worker#outer", "<main>"]
8. full_message first line: FILE:LINE:in 'Worker#inner': bad input (ArgumentError)
9. detailed_message first line: bad input (ArgumentError) (error_highlight added a snippet below it: true)
10. after set_backtrace: backtrace ["custom:1:in 'made_up'"], backtrace_locations still the originals: true
11. an exception never raised: RuntimeError.new("x").backtrace is nil
12. caller at the top level: []
13. labels elsewhere: top-level def: "Object#top_level_method" block: "block in <main>" main: "<main>"
Reading the output¶
Rows 1–3 are the live stack seen from Worker#inner: caller lists its callers (Worker#outer, then <main>), caller_locations(0, 3) starts at the current frame and adds it, and base_label strips the class. Row 4 is __method__. Row 5 shows a location's path (the program's own file, printed by basename) and that lineno is an Integer. Rows 6 and 7 are the same three frames captured by the exception. Row 8 is the first line of full_message — FILE:LINE:in 'Worker#inner': bad input (ArgumentError) — the exact line an uncaught exception prints. Row 9 is detailed_message: the message with the class name in parentheses, and true because error_highlight appended the source snippet. Row 10 is set_backtrace, which replaces the strings but, given strings, does not touch backtrace_locations — a subtlety worth knowing when a library rewrites backtraces. Row 11: a new exception has no backtrace until raised. Row 12: at the top level caller is empty. Row 13: a top-level def is Object#top_level_method, a block is block in <main>, and the main program is <main>.
For stopping inside a frame rather than reading it: binding.irb on any line opens an irb session there, and the bundled debug gem gives binding.break (alias debugger) with rdbg as its command-line front end, with step, next, up, info locals and breakpoints by file:line (Not machine-checked here.).
Compared with Python¶
Python's frames are objects from the start. traceback.extract_stack() returns the stack outermost first (row 1 reads ['<module>', 'outer', 'inner'] where Ruby's caller read innermost first), and inspect.stack() innermost first like caller_locations (row 2). A code object's co_qualname (3.11+) is Worker.inner, the twin of the Worker#inner label, and co_name is the base_label (row 3); sys._getframe().f_code.co_name is the __method__ idiom (row 4). Row 5: a FrameSummary has filename and lineno. An exception carries its traceback as e.__traceback__, a traceback object that traceback.extract_tb turns into the same frame summaries (rows 6–7). There is no full_message: format_exception_only gives the last line, ValueError: bad input, with no location in it (row 8), and 3.11's add_note appends lines to it (row 9), the closest thing to detailed_message. with_traceback(None) (row 10) is set_backtrace's counterpart. Rows 11 and 12 match Ruby: an unraised exception has None, and the module-level stack is just <module>. Row 13: a lambda's co_qualname is <lambda>. The full traceback text is deliberately never printed here — 3.13 added colours and carets whose exact characters differ between 3.12 and 3.14. breakpoint() (a hook into pdb by default) and python3 -m pdb script.py are the debugger side (Not machine-checked here.).
Verified output of backtraces_and_caller_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. extract_stack names (outermost first): ['<module>', 'outer', 'inner']
2. inspect.stack names (innermost first): ['inner', 'outer', '<module>']
3. co_qualname and co_name: 'Worker.inner', 'inner'
4. f_code.co_name of the current frame: 'inner'
5. a frame has a filename and a line: backtraces_and_caller_py.py, lineno is int
6. e.__traceback__ is a traceback; its names: ['<module>', 'outer', 'inner']
7. extract_tb gives frames with .name: ['<module>', 'outer', 'inner']
8. format_exception_only: ['ValueError: bad input\n'] (no location at all)
9. after add_note (3.11+): ['ValueError: bad input\n', 'checked by the twin\n']
10. after with_traceback(None): e.__traceback__ is None
11. an exception never raised: ValueError('x').__traceback__ is None
12. extract_stack at module level: ['<module>']
13. names elsewhere: top-level def: 'top_level_function' lambda: '<lambda>' module: '<module>'
Try it¶
- Write
who_called_me, which returns the label of whatever called it (caller_locations(1, 1)). Call it from an instance method, from inside a block in an instance method, and from the top level; print the three answers and notice theblock inprefix. - Rescue a
ZeroDivisionErrorraised three methods deep, then printe.backtrace_locationsas a table: one row per frame withbase_label,labeland whetherpathequals__FILE__— and count how many frames belong to your own file.
Solution to 1
backtraces_and_caller_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.
# Kata: `who_called_me` answers with the label of whatever called it -- a
# method, a block inside a method, or the main program.
def who_called_me
caller_locations(1, 1).first.label
end
class Report
def build = who_called_me
def each_line = [1, 2].map { who_called_me }
end
puts "1. from a method: #{Report.new.build}"
puts "2. from a block: #{Report.new.each_line.inspect}"
puts "3. from the top: #{who_called_me}"
Verified output of backtraces_and_caller_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
See also¶
- Exceptions have a cause — what
full_messageshows below the first line when one exception caused another - TracePoint — watching frames enter and leave instead of reading them afterwards
irbechoes every value; a script does not —binding.irbis that session, opened at a frame- Custom exception classes —
detailed_messageandfull_messageon a class of your own - Introspection —
source_location, the other thing a method knows about its own file - A Minitest test is a plain class — where the
[FILE:LINE]of a failing assertion comes from raisehas four forms —raise "msg"is aRuntimeError,raise Klassan instance whose message is the class name, `raise…