Class: Threatstack::Instrumentation::Instrumenter

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/instrumentation/instrumenter.rb

Constant Summary collapse

CLASS_SUFFIX =
'class'
@@logger =
Threatstack::Utils::TSLogger.create 'Instrumenter'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.define_callback(klass, method, suffix = nil, &block) ⇒ Object


24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/instrumentation/instrumenter.rb', line 24

def self.define_callback(klass, method, suffix = nil, &block)
  backup_name = get_backup_name(method, suffix)
  outer_block = block
  Proc.new do |*args, &block|
    caller_loc = caller_locations(1, 10)
    if outer_block
      # exec callback
      outer_block.call(:caller_loc => caller_loc, :args => args, :method_name => method, :target_class => klass)
    end
    __send__(backup_name, *args, &block)
  end
end

.get_backup_name(method, suffix = nil) ⇒ Object


179
180
181
# File 'lib/instrumentation/instrumenter.rb', line 179

def self.get_backup_name(method, suffix = nil)
  "ts_#{method}_backup#{suffix ? "_#{suffix}" : ''}".to_sym
end

.get_wrapped_name(method, suffix = nil) ⇒ Object


183
184
185
# File 'lib/instrumentation/instrumenter.rb', line 183

def self.get_wrapped_name(method, suffix = nil)
  "ts_#{method}_wrapped#{suffix ? "_#{suffix}" : ''}".to_sym
end

Instance Method Details

#get_backup_name(method, suffix = nil) ⇒ Object


187
188
189
# File 'lib/instrumentation/instrumenter.rb', line 187

def get_backup_name(method, suffix = nil)
  Instrumenter.get_backup_name(method, suffix)
end

#get_wrapped_name(method, suffix = nil) ⇒ Object


191
192
193
# File 'lib/instrumentation/instrumenter.rb', line 191

def get_wrapped_name(method, suffix = nil)
  Instrumenter.get_wrapped_name(method, suffix)
end

#is_class_method?(klass, method) ⇒ Boolean

Returns:

  • (Boolean)

201
202
203
204
# File 'lib/instrumentation/instrumenter.rb', line 201

def is_class_method?(klass, method)
  method = normalize_method_name(method)
  klass.singleton_methods.include?(method) || klass.respond_to?(method, true)
end

#is_instance_method?(klass, method) ⇒ Boolean

Returns:

  • (Boolean)

195
196
197
198
199
# File 'lib/instrumentation/instrumenter.rb', line 195

def is_instance_method?(klass, method)
  return false unless klass.respond_to?(:instance_methods)
  method = normalize_method_name(method)
  klass.instance_methods.include?(method) || klass.private_instance_methods.include?(method)
end

#method_exists?(obj, method) ⇒ Boolean

Returns:

  • (Boolean)

206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/instrumentation/instrumenter.rb', line 206

def method_exists?(obj, method)
  msg = "Method exists? #{obj}.#{method} =>"
  if obj.nil? || method.nil?
    @@logger.debug "#{msg} nil"
    return false
  end
  if is_class_method?(obj, method)
    @@logger.debug "#{msg} class method"
    return true
  end
  if is_instance_method?(obj, method)
    @@logger.debug "#{msg} instance method"
    return true
  end
  false
end

#normalize_method_name(method) ⇒ Object


15
16
17
# File 'lib/instrumentation/instrumenter.rb', line 15

def normalize_method_name(method)
  method.to_s
end

#unwrap_class_method(klass, method) ⇒ Object


135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/instrumentation/instrumenter.rb', line 135

def unwrap_class_method(klass, method)
  @@logger.debug "Unwrapping class method: #{klass}.#{method}"
  original_name = method.to_sym
  backup_name = get_backup_name(original_name, CLASS_SUFFIX)
  visibility = nil

  klass.singleton_class.instance_eval do
    if public_method_defined?(original_name)
      visibility = :public
    elsif protected_method_defined?(original_name)
      visibility = :protected
    elsif private_method_defined?(original_name)
      visibility = :private
    end
    unless visibility.nil?
      alias_method original_name, backup_name
      __send__(visibility, original_name)
      remove_method backup_name
    end
  end
end

#unwrap_instance_method(klass, method) ⇒ Object


157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/instrumentation/instrumenter.rb', line 157

def unwrap_instance_method(klass, method)
  @@logger.debug "Unwrapping instance method: #{klass}.#{method}"
  original_name = method.to_sym
  backup_name = get_backup_name original_name
  visibility = nil

  klass.class_eval do
    if public_method_defined?(original_name)
      visibility = :public
    elsif protected_method_defined?(original_name)
      visibility = :protected
    elsif private_method_defined?(original_name)
      visibility = :private
    end
    unless visibility.nil?
      alias_method original_name, backup_name
      __send__(visibility, original_name)
      remove_method backup_name
    end
  end
end

#unwrap_method(klass, method) ⇒ Object


121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/instrumentation/instrumenter.rb', line 121

def unwrap_method(klass, method)
  original_name = method.to_sym
  inst_backup_name = get_backup_name(original_name)
  class_backup_name = get_backup_name(original_name, CLASS_SUFFIX)
  # unwrap instance methods of that name
  if is_instance_method?(klass, inst_backup_name)
    unwrap_instance_method(klass, original_name)
  end
  # unwrap class methods of that name
  if is_class_method?(klass, class_backup_name)
    unwrap_class_method(klass, original_name)
  end
end

#wrap_class_method(klass, method, &block) ⇒ Object


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/instrumentation/instrumenter.rb', line 49

def wrap_class_method(klass, method, &block)
  @@logger.debug "Attempting wrap of class method: #{klass}.#{method}"
  original_name = method.to_sym
  backup_name = get_backup_name(original_name, CLASS_SUFFIX)
  wrapped_name = get_wrapped_name(original_name, CLASS_SUFFIX)

  if method_exists?(klass, backup_name)
    msg = "#{klass}.#{method} already instrumented"
    @@logger.error msg
    raise msg
  end

  @@logger.debug "Wrapping class method: #{klass}.#{method}"
  klass.singleton_class.instance_eval do
    alias_method backup_name, original_name

    p = Instrumenter.define_callback(klass, original_name, CLASS_SUFFIX, &block)
    define_method(wrapped_name, p)

    private wrapped_name

    visibility = nil
    if public_method_defined? original_name
      visibility = :public
    elsif protected_method_defined? original_name
      visibility = :protected
    elsif private_method_defined? original_name
      visibility = :private
    end

    alias_method original_name, wrapped_name
    __send__(visibility, original_name)
    private backup_name
  end
end

#wrap_instance_method(klass, method, &block) ⇒ Object


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/instrumentation/instrumenter.rb', line 85

def wrap_instance_method(klass, method, &block)
  @@logger.debug "Attempting wrap of instance method: #{klass}.#{method}"
  original_name = method.to_sym
  backup_name = get_backup_name original_name
  wrapped_name = get_wrapped_name original_name

  if method_exists?(klass, backup_name)
    msg = "#{klass}.#{method} already instrumented"
    @@logger.error msg
    raise msg
  end

  @@logger.debug "Wrapping instance method: #{klass}.#{method}"
  p = Instrumenter.define_callback(klass, original_name, nil, &block)
  visibility = nil
  klass.class_eval do
    alias_method backup_name, original_name

    define_method(wrapped_name, p)

    if public_method_defined?(original_name)
      visibility = :public
    elsif protected_method_defined?(original_name)
      visibility = :protected
    elsif private_method_defined?(original_name)
      visibility = :private
    end

    alias_method original_name, wrapped_name
    private backup_name
    private wrapped_name
    __send__(visibility, original_name)
  end
  backup_name
end

#wrap_method(klass, method, &block) ⇒ Object


37
38
39
40
41
42
43
44
45
46
47
# File 'lib/instrumentation/instrumenter.rb', line 37

def wrap_method(klass, method, &block)
  if is_class_method?(klass, method)
    wrap_class_method(klass, method, &block)
  elsif is_instance_method?(klass, method)
    wrap_instance_method(klass, method, &block)
  elsif klass.respond_to?(method, true)
    wrap_class_method(klass, method, &block)
  else
    raise "#{klass}.#{method} is not a class nor instance method"
  end
end