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
# 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]
  bt = ["#{file_name}:#{line}:in `#{method_name}'"]

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

#on_and_asgn(node) ⇒ Object



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

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

#on_block(node) ⇒ Object



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

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



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

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

#on_op_asgn(node) ⇒ Object



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

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

#on_or_asgn(node) ⇒ Object



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

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.



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

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)


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

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.



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

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