Class: ScoutApm::AutoInstrument::Rails::Rewriter

Inherits:
Parser::TreeRewriter
  • Object
show all
Defined in:
lib/scout_apm/auto_instrument/rails.rb

Instance Method Summary collapse

Constructor Details

#initializeRewriter

Returns a new instance of Rewriter.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/scout_apm/auto_instrument/rails.rb', line 50

def initialize
  super

  # Keeps track of the parent - child relationship between nodes:
  @nesting = []

  # The stack of method nodes (type :def):
  @method = []

  # The stack of class nodes:
  @scope = []

  @cache = Cache.new
end

Instance Method Details

#instrument(source, file_name, line) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/scout_apm/auto_instrument/rails.rb', line 65

def instrument(source, file_name, line)
  # Don't log huge chunks of code... just the first line:
  if lines = source.lines and lines.count > 1
    source = lines.first.chomp + "..."
  end

  method_name = @method.last.children[0]
  class_name = @scope.last.children[1]
  bt = ["#{file_name}:#{line}:in `#{method_name}'"]

  return [
    "::ScoutApm::AutoInstrument("+ source.dump + ",#{bt}){",
    "}"
  ]
end

#on_and_asgn(node) ⇒ Object



113
114
115
# File 'lib/scout_apm/auto_instrument/rails.rb', line 113

def on_and_asgn(node)
  process(node.children[1])
end

#on_block(node) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/scout_apm/auto_instrument/rails.rb', line 88

def on_block(node)
  # If we are not in a method, don't do any instrumentation:
  return if @method.empty?

  line = node.location.line || 'line?'
  column = node.location.column || 'column?' # not used
  method_name = node.children[0].children[1] || '*unknown*' # not used
  file_name = @source_rewriter.source_buffer.name

  wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
end

#on_mlhs(node) ⇒ Object



100
101
102
103
# File 'lib/scout_apm/auto_instrument/rails.rb', line 100

def on_mlhs(node)
  # Ignore / don't instrument multiple assignment (LHS).
  return
end

#on_op_asgn(node) ⇒ Object



105
106
107
# File 'lib/scout_apm/auto_instrument/rails.rb', line 105

def on_op_asgn(node)
  process(node.children[2])
end

#on_or_asgn(node) ⇒ Object



109
110
111
# File 'lib/scout_apm/auto_instrument/rails.rb', line 109

def on_or_asgn(node)
  process(node.children[1])
end

#on_send(node) ⇒ Object

Handle the method call AST node. If this method doesn’t call ‘super`, no futher rewriting is applied to children.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/scout_apm/auto_instrument/rails.rb', line 118

def on_send(node)
  # We aren't interested in top level function calls:
  return if @method.empty?

  if @cache.local_assignments?(node)
    return super
  end

  # This ignores both initial block method invocation `*x*{}`, and subsequent nested invocations `x{*y*}`:
  return if parent_type?(:block)

  # Extract useful metadata for instrumentation:
  line = node.location.line || 'line?'
  column = node.location.column || 'column?' # not used
  method_name = node.children[1] || '*unknown*' # not used
  file_name = @source_rewriter.source_buffer.name

  # Wrap the expression with instrumentation:
  wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
end

#parent_type?(type, up = 1) ⇒ Boolean

Look up 1 or more nodes to check if the parent exists and matches the given type.

Parameters:

  • type (Symbol)

    the symbol type to match.

  • up (Integer) (defaults to: 1)

    how far up to look.

Returns:

  • (Boolean)


84
85
86
# File 'lib/scout_apm/auto_instrument/rails.rb', line 84

def parent_type?(type, up = 1)
  parent = @nesting[@nesting.size - up - 1] and parent.type == type
end

#process(node) ⇒ Object

Invoked for every AST node as it is processed top to bottom.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/scout_apm/auto_instrument/rails.rb', line 150

def process(node)
  # We are nesting inside this node:
  @nesting.push(node)

  if node and node.type == :def
    # If the node is a method, push it on the method stack as well:
    @method.push(node)
    super
    @method.pop
  elsif node and node.type == :class
    @scope.push(node.children[0])
    super
    @scope.pop
  else
    super
  end

  @nesting.pop
end