Module: RailsTracepointStack::Renderer::Tree
- Defined in:
- lib/rails_tracepoint_stack/renderer/tree.rb
Overview
Renders a session as an indented call tree.
The format is built for reading in a terminal or pasting into a prompt, so it favours short lines: paths are relative to the working directory, values are compact JSON, and a return sits one level under the call it belongs to.
Constant Summary collapse
- INDENT =
" ".freeze
- SINGLETON_CLASS =
/\A#<Class:([A-Z][\w:]*)>\z/- TEMPLATE_FILE =
/\.(erb|haml|slim|builder|jbuilder|rabl)\z/i- TEMPLATE_LOCALS =
Everything a template receives besides the locals belongs to the rendering machinery, not to the developer.
"local_assigns".freeze
Class Method Summary collapse
- .call(session) ⇒ Object
- .call_line(record) ⇒ Object
- .compact(value) ⇒ Object
-
.empty_notice(session) ⇒ Object
An empty tree reads like a broken tool.
- .indent(depth) ⇒ Object
- .line_for(record) ⇒ Object
- .location(record) ⇒ Object
-
.qualified_name(record) ⇒ Object
A class method is defined on the singleton class, which prints as #Class:Foo.
- .raise_line(record) ⇒ Object
- .relative_path(file_path) ⇒ Object
- .return_line(record) ⇒ Object
-
.template?(record) ⇒ Boolean
A compiled template is a generated method on an anonymous class, named after a hash of the file.
- .template_line(record) ⇒ Object
- .template_locals(record) ⇒ Object
- .truncation_notice ⇒ Object
Class Method Details
.call(session) ⇒ Object
14 15 16 17 18 19 20 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 14 def self.call(session) lines = session.traces.map { |record| line_for(record) } lines << truncation_notice if session.truncated? lines << Summary.line(session) lines << empty_notice(session) if session.traces.empty? lines.join("\n") end |
.call_line(record) ⇒ Object
46 47 48 49 50 51 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 46 def self.call_line(record) return template_line(record) if template?(record) "#{indent(record.depth)}#{qualified_name(record)} " \ "(#{location(record)}) #{compact(record.params)}" end |
.compact(value) ⇒ Object
108 109 110 111 112 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 108 def self.compact(value) JSON.generate(value) rescue SystemStackError, StandardError value.to_s end |
.empty_notice(session) ⇒ Object
An empty tree reads like a broken tool. It usually means the block ran entirely inside gems - a bare ActiveRecord query calls no method the developer wrote - so say which of the two happened.
25 26 27 28 29 30 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 25 def self.empty_notice(session) return "no code ran inside the block" if session.filtered_count.to_i.zero? "no app code ran: #{session.filtered_count} traces from gems, the framework " \ "and Ruby itself were filtered out" end |
.indent(depth) ⇒ Object
94 95 96 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 94 def self.indent(depth) INDENT * depth end |
.line_for(record) ⇒ Object
32 33 34 35 36 37 38 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 32 def self.line_for(record) case record.kind when :call then call_line(record) when :return then return_line(record) when :raise then raise_line(record) end end |
.location(record) ⇒ Object
98 99 100 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 98 def self.location(record) "#{relative_path(record.file_path)}:#{record.line_number}" end |
.qualified_name(record) ⇒ Object
A class method is defined on the singleton class, which prints as
#Class:Foo. Ruby writes that call as Foo.bar, so the tree does too.
An anonymous singleton prints as an address and is left alone, since
turning that into 0x00007f1234.render helps nobody.
57 58 59 60 61 62 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 57 def self.qualified_name(record) singleton = SINGLETON_CLASS.match(record.class_name.to_s) return "#{singleton[1]}.#{record.method_name}" if singleton "#{record.class_name}##{record.method_name}" end |
.raise_line(record) ⇒ Object
86 87 88 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 86 def self.raise_line(record) "#{indent(record.depth + 1)}!! #{record.exception_class}: #{record.}" end |
.relative_path(file_path) ⇒ Object
102 103 104 105 106 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 102 def self.relative_path(file_path) return file_path unless file_path.to_s.start_with?("#{Dir.pwd}/") file_path[(Dir.pwd.length + 1)..] end |
.return_line(record) ⇒ Object
82 83 84 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 82 def self.return_line(record) "#{indent(record.depth + 1)}-> #{compact(record.return_value)}" end |
.template?(record) ⇒ Boolean
A compiled template is a generated method on an anonymous class, named after a hash of the file. None of that is worth showing: the path is.
66 67 68 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 66 def self.template?(record) TEMPLATE_FILE.match?(record.file_path.to_s) end |
.template_line(record) ⇒ Object
70 71 72 73 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 70 def self.template_line(record) "#{indent(record.depth)}render #{relative_path(record.file_path)} " \ "#{compact(template_locals(record))}" end |
.template_locals(record) ⇒ Object
75 76 77 78 79 80 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 75 def self.template_locals(record) params = record.params return {} unless params.is_a?(Hash) params.fetch(TEMPLATE_LOCALS, {}) end |
.truncation_notice ⇒ Object
90 91 92 |
# File 'lib/rails_tracepoint_stack/renderer/tree.rb', line 90 def self.truncation_notice "... truncated: the limit was reached before the block finished" end |