Class: Rack::Insight::Panel

Inherits:
Object
  • Object
show all
Extended by:
Database::EigenClient, Instrumentation::EigenClient, Logging
Includes:
ERB::Util, Database::RequestDataClient, Instrumentation::Client, Logging, Render
Defined in:
lib/rack/insight/panel.rb

Overview

Panels are also Rack middleware

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Database::EigenClient

included

Methods included from Instrumentation::EigenClient

included

Methods included from Logging

logger, verbose, verbosity

Methods included from Instrumentation::Client

#before_detect, #probe, #request_finish, #request_start

Methods included from Database::RequestDataClient

#count, #key_sql_template, #retrieve, #store, #table_length, #table_setup

Methods included from Render

#compile, #compile!, #compiled_source, #method_name, #method_name_without_locals, #render_template, #signed_params

Constructor Details

#initialize(app) ⇒ Panel

Returns a new instance of Panel.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rack/insight/panel.rb', line 105

def initialize(app)
  if panel_app
    #XXX use mappings
    @app = Rack::Cascade.new([panel_app, app])
  else
    @app = app
  end

  # User has explicitly declared what classes/methods to probe:
  #   Rack::Insight::Config.configure do |config|
  #     config[:panel_configs][:log] = {:probes => {'Logger' => [:instance, :add] } }
  #     # OR EQUIVALENTLY
  #     config[:panel_configs][:log] = {:probes => ['Logger', :instance, :add] }
  #   end
  panel_name = self.underscored_name.to_sym
  if self.has_custom_probes?(panel_name)
    # Both formats are valid and must be supported
    #config[:panel_configs][:log] = {:probes => {'Logger' => [:instance, :add]}}
    #config[:panel_configs][:log] = {:probes => ['Logger', :instance, :add]}
    custom_probes = Rack::Insight::Config.config[:panel_configs][panel_name][:probes]
    if custom_probes.kind_of?(Hash)
      probe(self) do
        custom_probes.each do |klass, method_probes|
          instrument klass do
            self.send("#{method_probes[0]}_probe", *(method_probes[1..-1]))
          end
        end
      end
    elsif custom_probes.kind_of?(Array) && custom_probes.length >=3
      probe(self) do
        custom_probes.each do |probe|
          klass = probe.shift
          probe_type = probe.shift
          instrument klass do
            self.send("#{probe_type}_probe", *probe)
          end
        end
      end
    else
      raise "Expected Rack::Insight::Config.config[:panel_configs][#{panel_name}][:probes] to be a kind of Hash or an Array with length >= 3, but is a #{Rack::Insight::Config.config[:panel_configs][self.as_sym][:probes].class}"
    end
  end

  # Setup a table for the panel unless
  # 1. self.has_table = false has been set for the Panel class
  # 2. class instance variable @has_table has been set to false
  # 3. table_setup has already been called by the sub class' initializer
  if !has_table?
    table_setup(self.name)
  end
end

Instance Attribute Details

#requestObject (readonly)

Returns the value of attribute request.



17
18
19
# File 'lib/rack/insight/panel.rb', line 17

def request
  @request
end

Class Method Details

.current_panel_file(sub) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/rack/insight/panel.rb', line 69

def current_panel_file(sub)
  file_name = nil
  matched_line = nil
  caller.each do |line|
    # First make sure we are not matching rack-insight's own panel class, which will be in the caller stack,
    # and which may match some custom load path added (try adding 'rack' as a custom load path!)
    # .*panel because the panels that ship with rack-insight also do not need custom template roots.
    next if line =~ /rack-insight.*\/lib\/rack\/insight\/.*panel.rb:/
    Rack::Insight::Config.config[:panel_load_paths].each do |load_path|
      regex = %r{^[^:]*#{load_path}/([^:]*)\.rb:}
      md = regex.match line
      file_name = md[1] unless md.nil?
      matched_line = line unless file_name.nil?
      break unless file_name.nil?
    end
    break unless file_name.nil?
  end
  set_sub_class_template_root(sub, File.dirname(matched_line.split(':')[0])) if matched_line.respond_to?(:split)
  return Thread::current['rack-panel_file'] || file_name
end

.excluded(klass = nil) ⇒ Object



99
100
101
# File 'lib/rack/insight/panel.rb', line 99

def excluded(klass = nil)
  Panel::panel_exclusion << klass || self
end

.file_indexObject



33
34
35
36
37
# File 'lib/rack/insight/panel.rb', line 33

def file_index
  return @file_index ||= Hash.new do |h,k|
    h[k] = []
  end
end

.from_file(rel_path) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/rack/insight/panel.rb', line 43

def from_file(rel_path)
  old_rel, Thread::current['rack-panel_file'] = Thread::current['rack-panel_file'], rel_path
  num_load_paths_to_check = Rack::Insight::Config.config[:panel_load_paths].length
  Rack::Insight::Config.config[:panel_load_paths].each_with_index do |load_path, index|
    begin
      require File::join(load_path, rel_path)
      break # once found
    rescue LoadError => e
      # TODO: If probes are defined for this panel, instantiate a magic panel
      # if self.has_custom_probes?
      if !verbose(:high) && (index + 1) == num_load_paths_to_check # You have failed me for the last time!
        warn "Rack::Insight #{e.class} while attempting to load '#{rel_path}' from :panel_load_paths #{Rack::Insight::Config.config[:panel_load_paths].inspect}."
      elsif verbose(:high)
        warn "Rack::Insight #{e.class} #{e.message} while attempting to load '#{rel_path}' from :panel_load_paths #{Rack::Insight::Config.config[:panel_load_paths].inspect} (just checked: #{load_path})."
      end
    end
  end
  return (file_index[rel_path] - panel_exclusion)
ensure
  Thread::current['rack-panel_file'] = old_rel
end

.has_tableObject

has table defaults to true for panels.



20
21
22
# File 'lib/rack/insight/panel.rb', line 20

def self.has_table
  self.has_table.nil? ? true : self.class.table.nil?
end

.inherited(sub) ⇒ Object



90
91
92
93
94
95
96
97
# File 'lib/rack/insight/panel.rb', line 90

def inherited(sub)
  if filename = current_panel_file(sub)
    logger.debug("panel inherited by #{sub.inspect} with template_root: #{sub.template_root}") if verbose(:high)
    Panel::file_index[filename] << sub
  else
    warn "Rack::Insight::Panel inherited by #{sub.name} outside rack-insight's :panel_load_paths.  Discarded.  Configured panel load paths are: #{Rack::Insight::Config.config[:panel_load_paths].inspect}"
  end
end

.panel_exclusionObject



39
40
41
# File 'lib/rack/insight/panel.rb', line 39

def panel_exclusion
  return @panel_exclusion ||= []
end

.panel_mappingsObject



181
182
183
# File 'lib/rack/insight/panel.rb', line 181

def self.panel_mappings
  {}
end

.set_sub_class_template_root(sub_class, path) ⇒ Object



65
66
67
# File 'lib/rack/insight/panel.rb', line 65

def set_sub_class_template_root(sub_class, path)
  sub_class.template_root = path
end

Instance Method Details

#after(env, status, headers, body) ⇒ Object



313
314
# File 'lib/rack/insight/panel.rb', line 313

def after(env, status, headers, body)
end

#after_detect(method_call, timing, args, result) ⇒ Object

Override in subclasses. This is to make magic classes work.



304
305
306
307
308
# File 'lib/rack/insight/panel.rb', line 304

def after_detect(method_call, timing, args, result)
  if self.is_magic? && self.has_table? && self.is_probing?
    store(@env, Rack::Insight::DefaultInvocation.new(method_call.method.to_s, timing, args, result, method_call.backtrace[2..-1]))
  end
end

#before(env) ⇒ Object



310
311
# File 'lib/rack/insight/panel.rb', line 310

def before(env)
end

#bool_prop(prop) ⇒ Object



161
162
163
# File 'lib/rack/insight/panel.rb', line 161

def bool_prop(prop)
  self.send(prop) ? 'Y' : 'N'
end

#call(env) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/rack/insight/panel.rb', line 165

def call(env)
  @env = env
  logger.debug{ "Before call: #{self.name}" } if verbose(:debug)
  before(env)
  status, headers, body = @app.call(env)
  @request = Rack::Request.new(env)
  logger.debug{ "After call: #{self.name}" } if verbose(:debug)
  after(env, status, headers, body)
  env["rack-insight.panels"] << self
  return [status, headers, body]
end

#camelized_name(str = self.underscored_name) ⇒ Object



235
236
237
# File 'lib/rack/insight/panel.rb', line 235

def camelized_name(str = self.underscored_name)
  str.split('_').map {|w| w.capitalize}.join
end

#contentObject



285
286
287
288
289
290
# File 'lib/rack/insight/panel.rb', line 285

def content
  logger.info("Rack::Insight is using default content for #{self.class}") if verbose(:med)
  render_template 'no_content', :name => self.camelized_name
rescue StandardError => exception
  handle_error_for('content', exception)
end

#content_for_request(number) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/rack/insight/panel.rb', line 258

def content_for_request(number)
  logger.info("Rack::Insight is using default content_for_request for #{self.class}") if verbose(:med)
  if !self.has_table?
    logger.info("#{self.class} is being used without a table") if verbose(:med)
    content
  elsif self.is_probing? # Checking probed because we only get here when the subclass panel hasn't overridden this method
    invocations = retrieve(number)
    if invocations.length > 0
      logger.info("Rack::Insight is using #{self.is_magic? ? 'magic' : 'default'} content for #{self.class}, which is probed")# if verbose(:med)
      render_template 'magic_panel', :magic_insights => invocations, :name => self.camelized_name
    else
      logger.info("Rack::Insight has no data for #{self.is_magic? ? 'magic' : 'default'} content for #{self.class}, which is probed")
      render_template 'no_data', :name => self.camelized_name
    end
  else
    content
  end
rescue StandardError => exception
  handle_error_for('content_for_request', exception)
end

#handle_error_for(method_name, exception) ⇒ Object



292
293
294
295
296
297
298
299
300
# File 'lib/rack/insight/panel.rb', line 292

def handle_error_for(method_name, exception)
  nom = self.name rescue "xxx"
  msg = ["#{self.class}##{method_name} failed","#{exception.class}: #{exception.message}"] + exception.backtrace
  logger.error(msg.join("\n"))
  # return HTML
  "Error in #{nom}
  <!-- Panel: #{self.inspect}\n
  #{msg.join("\n")} -->"
end

#has_content?Boolean

Returns:

  • (Boolean)


193
194
195
# File 'lib/rack/insight/panel.rb', line 193

def has_content?
  true
end

#has_custom_probes?(panel_name = self.underscored_name.to_sym) ⇒ Boolean

Returns:

  • (Boolean)


201
202
203
204
# File 'lib/rack/insight/panel.rb', line 201

def has_custom_probes?(panel_name = self.underscored_name.to_sym)
  Rack::Insight::Config.config[:panel_configs][panel_name].respond_to?(:[]) &&
    !Rack::Insight::Config.config[:panel_configs][panel_name][:probes].nil?
end

#has_table?Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/rack/insight/panel.rb', line 185

def has_table?
  !!self.class.has_table
end

#headingObject



279
280
281
282
283
# File 'lib/rack/insight/panel.rb', line 279

def heading
  self.camelized_name
rescue StandardError => exception
  handle_error_for('heading', exception)
end

#heading_for_request(number) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/rack/insight/panel.rb', line 239

def heading_for_request(number)
  if !self.has_table?
    heading
  else
    num = count(number)
    if num.kind_of?(Numeric)
      if num == 0
        heading
      else
        "#{self.camelized_name} (#{num})"
      end
    else
      heading
    end
  end
rescue StandardError => exception
  handle_error_for('heading_for_request', exception)
end

#inspectObject



157
158
159
# File 'lib/rack/insight/panel.rb', line 157

def inspect
  "#{self.underscored_name} Magic:#{self.bool_prop(:is_magic?)} Table:#{self.bool_prop(:has_table?)} Probe:#{self.bool_prop(:is_probing?)} Custom:#{self.bool_prop(:has_custom_probes?)}" rescue "XXX inspect failed"
end

#is_magic?Boolean

Returns:

  • (Boolean)


189
190
191
# File 'lib/rack/insight/panel.rb', line 189

def is_magic?
  !!self.class.is_magic
end

#is_probing?Boolean

Returns:

  • (Boolean)


197
198
199
# File 'lib/rack/insight/panel.rb', line 197

def is_probing?
  !!self.class.is_probing
end

#nameObject

The name informs the table name, and the panel_configs hash among other things. Override in subclass panels if you want a custom name



208
209
210
# File 'lib/rack/insight/panel.rb', line 208

def name
  self.underscored_name
end

#panel_appObject



177
178
179
# File 'lib/rack/insight/panel.rb', line 177

def panel_app
  nil
end

#render(template) ⇒ Object



316
317
# File 'lib/rack/insight/panel.rb', line 316

def render(template)
end

#underscored_name(word = self.class.to_s) ⇒ Object

Mostly stolen from Rails’ ActiveSupport’ underscore method: See activesupport/lib/active_support/inflector/methods.rb, line 77 HTTPClientPanel => http_client LogPanel => log ActiveRecordPanel => active_record



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/rack/insight/panel.rb', line 217

def underscored_name(word = self.class.to_s)
  @underscored_name ||= begin
    words = word.dup.split('::')
    word = words.last
    if word == 'Panel'
      word = words[-2] # Panel class is Panel... and this won't do.
    end
    # This bit from rails probably isn't needed here, and wouldn't work anyways.
    #word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
    word.gsub!(/Panel$/,'')
    word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
    word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
    word.tr!("-", "_")
    word.downcase!
    word
  end
end