newrelic_rpm 3.1.2 → 3.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of newrelic_rpm might be problematic. Click here for more details.

Files changed (69) hide show
  1. data/CHANGELOG +9 -0
  2. data/lib/new_relic/agent.rb +12 -3
  3. data/lib/new_relic/agent/agent.rb +99 -97
  4. data/lib/new_relic/agent/database.rb +203 -0
  5. data/lib/new_relic/agent/instrumentation/active_merchant.rb +2 -0
  6. data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +2 -0
  7. data/lib/new_relic/agent/instrumentation/authlogic.rb +2 -0
  8. data/lib/new_relic/agent/instrumentation/data_mapper.rb +2 -0
  9. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +2 -0
  10. data/lib/new_relic/agent/instrumentation/memcache.rb +23 -13
  11. data/lib/new_relic/agent/instrumentation/merb/controller.rb +2 -1
  12. data/lib/new_relic/agent/instrumentation/merb/errors.rb +2 -0
  13. data/lib/new_relic/agent/instrumentation/metric_frame.rb +7 -1
  14. data/lib/new_relic/agent/instrumentation/metric_frame/pop.rb +1 -0
  15. data/lib/new_relic/agent/instrumentation/net.rb +2 -0
  16. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +2 -0
  17. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +63 -36
  18. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +2 -0
  19. data/lib/new_relic/agent/instrumentation/rails/active_record_instrumentation.rb +5 -2
  20. data/lib/new_relic/agent/instrumentation/rails/errors.rb +4 -2
  21. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +56 -2
  22. data/lib/new_relic/agent/instrumentation/rails3/active_record_instrumentation.rb +5 -2
  23. data/lib/new_relic/agent/instrumentation/rails3/errors.rb +3 -1
  24. data/lib/new_relic/agent/instrumentation/sunspot.rb +2 -0
  25. data/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb +2 -1
  26. data/lib/new_relic/agent/shim_agent.rb +1 -0
  27. data/lib/new_relic/agent/sql_sampler.rb +230 -0
  28. data/lib/new_relic/agent/stats_engine/transactions.rb +10 -0
  29. data/lib/new_relic/agent/transaction_sampler.rb +11 -6
  30. data/lib/new_relic/collection_helper.rb +7 -4
  31. data/lib/new_relic/commands/deployments.rb +1 -1
  32. data/lib/new_relic/commands/install.rb +2 -13
  33. data/lib/new_relic/control/class_methods.rb +4 -3
  34. data/lib/new_relic/control/configuration.rb +21 -0
  35. data/lib/new_relic/control/frameworks/rails.rb +1 -1
  36. data/lib/new_relic/control/logging_methods.rb +17 -6
  37. data/lib/new_relic/delayed_job_injection.rb +1 -1
  38. data/lib/new_relic/local_environment.rb +8 -14
  39. data/lib/new_relic/rack/developer_mode.rb +1 -0
  40. data/lib/new_relic/stats.rb +1 -0
  41. data/lib/new_relic/transaction_sample.rb +5 -60
  42. data/lib/new_relic/transaction_sample/segment.rb +7 -82
  43. data/lib/new_relic/version.rb +3 -3
  44. data/newrelic_rpm.gemspec +8 -3
  45. data/test/new_relic/agent/agent/connect_test.rb +95 -0
  46. data/test/new_relic/agent/agent/start_test.rb +0 -85
  47. data/test/new_relic/agent/agent/start_worker_thread_test.rb +1 -0
  48. data/test/new_relic/agent/agent_test.rb +0 -73
  49. data/test/new_relic/agent/browser_monitoring_test.rb +1 -1
  50. data/test/new_relic/agent/database_test.rb +160 -0
  51. data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +3 -0
  52. data/test/new_relic/agent/memcache_instrumentation_test.rb +14 -15
  53. data/test/new_relic/agent/sql_sampler_test.rb +135 -0
  54. data/test/new_relic/agent/transaction_sampler_test.rb +12 -3
  55. data/test/new_relic/collection_helper_test.rb +4 -4
  56. data/test/new_relic/control/configuration_test.rb +31 -0
  57. data/test/new_relic/control/logging_methods_test.rb +20 -4
  58. data/test/new_relic/delayed_job_injection_test.rb +1 -1
  59. data/test/new_relic/rack/developer_mode_helper_test.rb +141 -0
  60. data/test/new_relic/stats_test.rb +3 -3
  61. data/test/new_relic/transaction_sample/segment_test.rb +4 -92
  62. data/test/new_relic/transaction_sample_test.rb +1 -1
  63. data/test/test_helper.rb +1 -1
  64. data/ui/helpers/developer_mode_helper.rb +14 -8
  65. data/ui/helpers/google_pie_chart.rb +0 -1
  66. data/vendor/gems/dependency_detection-0.0.1.build/lib/dependency_detection.rb +5 -0
  67. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/mem_cache.rb +11 -11
  68. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/view.rb +4 -0
  69. metadata +15 -10
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ v3.2.0
2
+ * Fix over-detection of mongrel and unicorn and only start the agent when
3
+ actual server is running
4
+ * Improve developer mode backtraces to support ruby 1.9.2, windows, and fix
5
+ nil de-reference in developer mode
6
+ * Fixed some cases where Memcache instrumentation was failing to load
7
+ * Added View instrumentation back for Rails 3.1
8
+ * Ability to set log destination by NEW_RELIC_LOG env var
9
+
1
10
  v3.1.2
2
11
  * Fixed some thread safety issues
3
12
  * Work around for Ruby 1.8.7 Marshal crash bug
@@ -81,9 +81,11 @@ module NewRelic
81
81
  require 'new_relic/agent/worker_loop'
82
82
  require 'new_relic/agent/stats_engine'
83
83
  require 'new_relic/agent/transaction_sampler'
84
+ require 'new_relic/agent/sql_sampler'
84
85
  require 'new_relic/agent/error_collector'
85
86
  require 'new_relic/agent/busy_calculator'
86
87
  require 'new_relic/agent/sampler'
88
+ require 'new_relic/agent/database'
87
89
 
88
90
  require 'new_relic/agent/instrumentation/controller_instrumentation'
89
91
 
@@ -146,9 +148,16 @@ module NewRelic
146
148
  alias get_stats_no_scope get_stats
147
149
 
148
150
  # Get the logger for the agent. Available after the agent has initialized.
149
- # This sends output to the agent log file.
151
+ # This sends output to the agent log file. If the agent has not initialized
152
+ # a standard output logger is returned.
150
153
  def logger
151
- NewRelic::Control.instance.log
154
+ control = NewRelic::Control.instance(false)
155
+ if control
156
+ control.log
157
+ else
158
+ require 'logger'
159
+ @stdoutlog ||= Logger.new $stdout
160
+ end
152
161
  end
153
162
 
154
163
  # Call this to manually start the Agent in situations where the Agent does
@@ -272,7 +281,7 @@ module NewRelic
272
281
  # end
273
282
  #
274
283
  def set_sql_obfuscator(type = :replace, &block)
275
- agent.set_sql_obfuscator type, &block
284
+ NewRelic::Agent::Database.set_sql_obfuscator(type, &block)
276
285
  end
277
286
 
278
287
 
@@ -35,14 +35,16 @@ module NewRelic
35
35
  @metric_ids = {}
36
36
  @stats_engine = NewRelic::Agent::StatsEngine.new
37
37
  @transaction_sampler = NewRelic::Agent::TransactionSampler.new
38
+ @sql_sampler = NewRelic::Agent::SqlSampler.new
38
39
  @stats_engine.transaction_sampler = @transaction_sampler
40
+ @stats_engine.sql_sampler = @sql_sampler
39
41
  @error_collector = NewRelic::Agent::ErrorCollector.new
40
42
  @connect_attempts = 0
41
43
 
42
44
  @request_timeout = NewRelic::Control.instance.fetch('timeout', 2 * 60)
43
45
 
44
46
  @last_harvest_time = Time.now
45
- @obfuscator = method(:default_sql_obfuscator)
47
+ @obfuscator = lambda {|sql| NewRelic::Agent::Database.default_sql_obfuscator(sql) }
46
48
  end
47
49
 
48
50
  # contains all the class-level methods for NewRelic::Agent::Agent
@@ -64,6 +66,7 @@ module NewRelic
64
66
  attr_reader :stats_engine
65
67
  # the transaction sampler that handles recording transactions
66
68
  attr_reader :transaction_sampler
69
+ attr_reader :sql_sampler
67
70
  # error collector is a simple collection of recorded errors
68
71
  attr_reader :error_collector
69
72
  # whether we should record raw, obfuscated, or no sql
@@ -271,29 +274,6 @@ module NewRelic
271
274
  Thread.current[:newrelic_untraced].pop if Thread.current[:newrelic_untraced]
272
275
  end
273
276
 
274
- # Sets the sql obfuscator used to clean up sql when sending it
275
- # to the server. Possible types are:
276
- #
277
- # :before => sets the block to run before the existing
278
- # obfuscators
279
- #
280
- # :after => sets the block to run after the existing
281
- # obfuscator(s)
282
- #
283
- # :replace => removes the current obfuscator and replaces it
284
- # with the provided block
285
- def set_sql_obfuscator(type, &block)
286
- if type == :before
287
- @obfuscator = NewRelic::ChainedCall.new(block, @obfuscator)
288
- elsif type == :after
289
- @obfuscator = NewRelic::ChainedCall.new(@obfuscator, block)
290
- elsif type == :replace
291
- @obfuscator = block
292
- else
293
- fail "unknown sql_obfuscator type #{type}"
294
- end
295
- end
296
-
297
277
  # Shorthand to the NewRelic::Agent.logger method
298
278
  def log
299
279
  NewRelic::Agent.logger
@@ -331,68 +311,6 @@ module NewRelic
331
311
  log.info "Application: #{control.app_names.join(", ")}"
332
312
  end
333
313
 
334
- # apdex_f is always 4 times the apdex_t
335
- def apdex_f
336
- (4 * NewRelic::Control.instance.apdex_t).to_f
337
- end
338
-
339
- # If the transaction threshold is set to the string
340
- # 'apdex_f', we use 4 times the apdex_t value to record
341
- # transactions. This gears well with using apdex since you
342
- # will attempt to send any transactions that register as 'failing'
343
- def apdex_f_threshold?
344
- sampler_config.fetch('transaction_threshold', '') =~ /apdex_f/i
345
- end
346
-
347
- # Sets the sql recording configuration by trying to detect
348
- # any attempt to disable the sql collection - 'off',
349
- # 'false', 'none', and friends. Otherwise, we accept 'raw',
350
- # and unrecognized values default to 'obfuscated'
351
- def set_sql_recording!
352
- record_sql_config = sampler_config.fetch('record_sql', :obfuscated)
353
- case record_sql_config.to_s
354
- when 'off'
355
- @record_sql = :off
356
- when 'none'
357
- @record_sql = :off
358
- when 'false'
359
- @record_sql = :off
360
- when 'raw'
361
- @record_sql = :raw
362
- else
363
- @record_sql = :obfuscated
364
- end
365
-
366
- log_sql_transmission_warning?
367
- end
368
-
369
- # Warn the user when we are sending raw sql across the wire
370
- # - they should probably be using ssl when this is true
371
- def log_sql_transmission_warning?
372
- log_if((@record_sql == :raw), :warn, "Agent is configured to send raw SQL to the service")
373
- end
374
-
375
- # gets the sampler configuration from the control object's settings
376
- def sampler_config
377
- control.fetch('transaction_tracer', {})
378
- end
379
-
380
- # this entire method should be done on the transaction
381
- # sampler object, rather than here. We should pass in the
382
- # sampler config.
383
- def config_transaction_tracer
384
- @should_send_samples = @config_should_send_samples = sampler_config.fetch('enabled', true)
385
- @should_send_random_samples = sampler_config.fetch('random_sample', false)
386
- @explain_threshold = sampler_config.fetch('explain_threshold', 0.5).to_f
387
- @explain_enabled = sampler_config.fetch('explain_enabled', true)
388
- set_sql_recording!
389
-
390
- # default to 2.0, string 'apdex_f' will turn into your
391
- # apdex * 4
392
- @slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', 2.0).to_f
393
- @slowest_transaction_threshold = apdex_f if apdex_f_threshold?
394
- end
395
-
396
314
  # Connecting in the foreground blocks further startup of the
397
315
  # agent until we have a connection - useful in cases where
398
316
  # you're trying to log a very-short-running process and want
@@ -548,6 +466,16 @@ module NewRelic
548
466
  end
549
467
  end
550
468
 
469
+ def check_sql_sampler_status
470
+ # disable sql sampling if disabled by the server
471
+ # and we're not in dev mode
472
+ if control.developer_mode? || @should_send_samples
473
+ @sql_sampler.enable
474
+ else
475
+ @sql_sampler.disable
476
+ end
477
+ end
478
+
551
479
  # logs info about the worker loop so users can see when the
552
480
  # agent actually begins running in the background
553
481
  def log_worker_loop_start
@@ -633,6 +561,7 @@ module NewRelic
633
561
  connect(connection_options)
634
562
  if @connected
635
563
  check_transaction_sampler_status
564
+ check_sql_sampler_status
636
565
  log_worker_loop_start
637
566
  create_and_run_worker_loop
638
567
  # never reaches here unless there is a problem or
@@ -796,6 +725,8 @@ module NewRelic
796
725
  # are allowed to send errors. Pretty simple, and logs at
797
726
  # debug whether errors will or will not be sent.
798
727
  def configure_error_collector!(server_enabled)
728
+ # Reinitialize the error collector
729
+ @error_collector = NewRelic::Agent::ErrorCollector.new
799
730
  # Ask for permission to collect error data
800
731
  enabled = if error_collector.config_enabled && server_enabled
801
732
  error_collector.enabled = true
@@ -820,6 +751,24 @@ module NewRelic
820
751
  log.info "Transaction sampling enabled, rate = #{@transaction_sampler.sampling_rate}"
821
752
  end
822
753
 
754
+ # this entire method should be done on the transaction
755
+ # sampler object, rather than here. We should pass in the
756
+ # sampler config.
757
+ def config_transaction_tracer
758
+ # Reconfigure the transaction tracer
759
+ @transaction_sampler.configure!
760
+ @should_send_samples = @config_should_send_samples = sampler_config.fetch('enabled', true)
761
+ @should_send_random_samples = sampler_config.fetch('random_sample', false)
762
+ @explain_threshold = sampler_config.fetch('explain_threshold', 0.5).to_f
763
+ @explain_enabled = sampler_config.fetch('explain_enabled', true)
764
+ set_sql_recording!
765
+
766
+ # default to 2.0, string 'apdex_f' will turn into your
767
+ # apdex * 4
768
+ @slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', 2.0).to_f
769
+ @slowest_transaction_threshold = apdex_f if apdex_f_threshold?
770
+ end
771
+
823
772
  # Enables or disables the transaction tracer and sets its
824
773
  # options based on the options provided to the
825
774
  # method.
@@ -837,6 +786,52 @@ module NewRelic
837
786
  end
838
787
  end
839
788
 
789
+ # apdex_f is always 4 times the apdex_t
790
+ def apdex_f
791
+ (4 * NewRelic::Control.instance.apdex_t).to_f
792
+ end
793
+
794
+ # If the transaction threshold is set to the string
795
+ # 'apdex_f', we use 4 times the apdex_t value to record
796
+ # transactions. This gears well with using apdex since you
797
+ # will attempt to send any transactions that register as 'failing'
798
+ def apdex_f_threshold?
799
+ sampler_config.fetch('transaction_threshold', '') =~ /apdex_f/i
800
+ end
801
+
802
+ # Sets the sql recording configuration by trying to detect
803
+ # any attempt to disable the sql collection - 'off',
804
+ # 'false', 'none', and friends. Otherwise, we accept 'raw',
805
+ # and unrecognized values default to 'obfuscated'
806
+ def set_sql_recording!
807
+ record_sql_config = sampler_config.fetch('record_sql', :obfuscated)
808
+ case record_sql_config.to_s
809
+ when 'off'
810
+ @record_sql = :off
811
+ when 'none'
812
+ @record_sql = :off
813
+ when 'false'
814
+ @record_sql = :off
815
+ when 'raw'
816
+ @record_sql = :raw
817
+ else
818
+ @record_sql = :obfuscated
819
+ end
820
+
821
+ log_sql_transmission_warning?
822
+ end
823
+
824
+ # Warn the user when we are sending raw sql across the wire
825
+ # - they should probably be using ssl when this is true
826
+ def log_sql_transmission_warning?
827
+ log.warn("Agent is configured to send raw SQL to the service") if @record_sql == :raw
828
+ end
829
+
830
+ # gets the sampler configuration from the control object's settings
831
+ def sampler_config
832
+ control.fetch('transaction_tracer', {})
833
+ end
834
+
840
835
  # Asks the collector to tell us which sub-collector we
841
836
  # should be reporting to, and then does the name resolution
842
837
  # on that host so we don't block on DNS during the normal
@@ -868,7 +863,10 @@ module NewRelic
868
863
  @report_period = config_data['data_report_period']
869
864
  @url_rules = config_data['url_rules']
870
865
  @beacon_configuration = BeaconConfiguration.new(config_data)
866
+ @server_side_config_enabled = config_data['listen_to_server_config']
871
867
 
868
+ control.merge_server_side_config(config_data) if @server_side_config_enabled
869
+ config_transaction_tracer
872
870
  log_connection!(config_data)
873
871
  configure_transaction_tracer!(config_data['collect_traces'], config_data['sample_rate'])
874
872
  configure_error_collector!(config_data['collect_errors'])
@@ -1043,6 +1041,20 @@ module NewRelic
1043
1041
  @traces
1044
1042
  end
1045
1043
 
1044
+ def harvest_and_send_slowest_sql
1045
+ # FIXME add the code to try to resend if our connection is down
1046
+ sql_traces = @sql_sampler.harvest
1047
+ unless sql_traces.empty?
1048
+ log.debug "Sending (#{sql_traces.count}) sql traces"
1049
+ begin
1050
+ response = invoke_remote :sql_trace_data, sql_traces
1051
+ # log.debug "Sql trace response: #{response}"
1052
+ rescue
1053
+ @sql_sampler.merge sql_traces
1054
+ end
1055
+ end
1056
+ end
1057
+
1046
1058
  # This handles getting the transaction traces and then sending
1047
1059
  # them across the wire. This includes gathering SQL
1048
1060
  # explanations, stripping out stack traces, and normalizing
@@ -1257,6 +1269,7 @@ module NewRelic
1257
1269
  NewRelic::Agent.load_data
1258
1270
  harvest_and_send_errors
1259
1271
  harvest_and_send_slowest_sample
1272
+ harvest_and_send_slowest_sql
1260
1273
  harvest_and_send_timeslice_data
1261
1274
  else
1262
1275
  log.debug "Serializing agent data to disk"
@@ -1289,17 +1302,6 @@ module NewRelic
1289
1302
  log.debug "Bypassing graceful disconnect - agent not connected"
1290
1303
  end
1291
1304
  end
1292
- def default_sql_obfuscator(sql)
1293
- sql = sql.dup
1294
- # This is hardly readable. Use the unit tests.
1295
- # remove single quoted strings:
1296
- sql.gsub!(/'(.*?[^\\'])??'(?!')/, '?')
1297
- # remove double quoted strings:
1298
- sql.gsub!(/"(.*?[^\\"])??"(?!")/, '?')
1299
- # replace all number literals
1300
- sql.gsub!(/\d+/, "?")
1301
- sql
1302
- end
1303
1305
  end
1304
1306
 
1305
1307
  extend ClassMethods
@@ -0,0 +1,203 @@
1
+ require 'singleton'
2
+
3
+ module NewRelic
4
+ # columns for a mysql explain plan
5
+ MYSQL_EXPLAIN_COLUMNS = [
6
+ "Id",
7
+ "Select Type",
8
+ "Table",
9
+ "Type",
10
+ "Possible Keys",
11
+ "Key",
12
+ "Key Length",
13
+ "Ref",
14
+ "Rows",
15
+ "Extra"
16
+ ].freeze
17
+
18
+ module Agent
19
+ module Database
20
+ extend self
21
+
22
+ def obfuscate_sql(sql)
23
+ Obfuscator.instance.obfuscator.call(sql)
24
+ end
25
+
26
+ def set_sql_obfuscator(type, &block)
27
+ Obfuscator.instance.set_sql_obfuscator(type, &block)
28
+ end
29
+
30
+ def get_connection(config)
31
+ ConnectionManager.instance.get_connection(config)
32
+ end
33
+
34
+ def close_connections
35
+ ConnectionManager.instance.close_connections
36
+ end
37
+
38
+ # Perform this in the runtime environment of a managed
39
+ # application, to explain the sql statement executed within a
40
+ # segment of a transaction sample. Returns an array of
41
+ # explanations (which is an array rows consisting of an array of
42
+ # strings for each column returned by the the explain query)
43
+ # Note this happens only for statements whose execution time
44
+ # exceeds a threshold (e.g. 500ms) and only within the slowest
45
+ # transaction in a report period, selected for shipment to New
46
+ # Relic
47
+ def explain_sql(sql, connection_config)
48
+ return nil unless sql && connection_config
49
+ statement = sql.split(";\n")[0] # only explain the first
50
+ explain_sql = explain_statement(statement, connection_config)
51
+ return explain_sql || []
52
+ end
53
+
54
+ def explain_statement(statement, config)
55
+ if is_select?(statement)
56
+ handle_exception_in_explain do
57
+ connection = get_connection(config)
58
+ plan = nil
59
+ if connection
60
+ plan = process_resultset(connection.execute("EXPLAIN #{statement}"))
61
+ end
62
+ return plan
63
+ end
64
+ end
65
+ end
66
+
67
+ def process_resultset(items)
68
+ # The resultset type varies for different drivers. Only thing you can count on is
69
+ # that it implements each. Also: can't use select_rows because the native postgres
70
+ # driver doesn't know that method.
71
+
72
+ headers = []
73
+ values = []
74
+ if items.respond_to?(:each_hash)
75
+ items.each_hash do |row|
76
+ headers = row.keys
77
+ values << headers.map{|h| row[h] }
78
+ end
79
+ elsif items.respond_to?(:each)
80
+ items.each do |row|
81
+ if row.kind_of?(Hash)
82
+ headers = row.keys
83
+ values << headers.map{|h| row[h] }
84
+ else
85
+ values << row
86
+ end
87
+ end
88
+ else
89
+ values = [items]
90
+ end
91
+
92
+ headers = nil if headers.empty?
93
+ [headers, values]
94
+ end
95
+
96
+ def handle_exception_in_explain
97
+ yield
98
+ rescue Exception => e
99
+ begin
100
+ # guarantees no throw from explain_sql
101
+ NewRelic::Control.instance.log.error("Error getting query plan: #{e.message}")
102
+ NewRelic::Control.instance.log.debug(e.backtrace.join("\n"))
103
+ rescue Exception
104
+ # double exception. throw up your hands
105
+ end
106
+ end
107
+
108
+ def is_select?(statement)
109
+ # split the string into at most two segments on the
110
+ # system-defined field separator character
111
+ first_word, rest_of_statement = statement.split($;, 2)
112
+ (first_word.upcase == 'SELECT')
113
+ end
114
+
115
+ class ConnectionManager
116
+ include Singleton
117
+
118
+ # Returns a cached connection for a given ActiveRecord
119
+ # configuration - these are stored or reopened as needed, and if
120
+ # we cannot get one, we ignore it and move on without explaining
121
+ # the sql
122
+ def get_connection(config)
123
+ @connections ||= {}
124
+
125
+ connection = @connections[config]
126
+
127
+ return connection if connection
128
+
129
+ begin
130
+ connection = ActiveRecord::Base.send("#{config[:adapter]}_connection", config)
131
+ @connections[config] = connection
132
+ rescue => e
133
+ NewRelic::Agent.agent.log.error("Caught exception #{e} trying to get connection to DB for explain. Control: #{config}")
134
+ NewRelic::Agent.agent.log.error(e.backtrace.join("\n"))
135
+ nil
136
+ end
137
+ end
138
+
139
+ # Closes all the connections in the internal connection cache
140
+ def close_connections
141
+ @connections ||= {}
142
+ @connections.values.each do |connection|
143
+ begin
144
+ connection.disconnect!
145
+ rescue
146
+ end
147
+ end
148
+
149
+ @connections = {}
150
+ end
151
+ end
152
+
153
+ class Obfuscator
154
+ include Singleton
155
+
156
+ attr_reader :obfuscator
157
+
158
+ def initialize
159
+ reset
160
+ end
161
+
162
+ def reset
163
+ @obfuscator = method(:default_sql_obfuscator)
164
+ end
165
+
166
+ # Sets the sql obfuscator used to clean up sql when sending it
167
+ # to the server. Possible types are:
168
+ #
169
+ # :before => sets the block to run before the existing
170
+ # obfuscators
171
+ #
172
+ # :after => sets the block to run after the existing
173
+ # obfuscator(s)
174
+ #
175
+ # :replace => removes the current obfuscator and replaces it
176
+ # with the provided block
177
+ def set_sql_obfuscator(type, &block)
178
+ if type == :before
179
+ @obfuscator = NewRelic::ChainedCall.new(block, @obfuscator)
180
+ elsif type == :after
181
+ @obfuscator = NewRelic::ChainedCall.new(@obfuscator, block)
182
+ elsif type == :replace
183
+ @obfuscator = block
184
+ else
185
+ fail "unknown sql_obfuscator type #{type}"
186
+ end
187
+ end
188
+
189
+ def default_sql_obfuscator(sql)
190
+ sql = sql.dup
191
+ # This is hardly readable. Use the unit tests.
192
+ # remove single quoted strings:
193
+ sql.gsub!(/'(.*?[^\\'])??'(?!')/, '?')
194
+ # remove double quoted strings:
195
+ sql.gsub!(/"(.*?[^\\"])??"(?!")/, '?')
196
+ # replace all number literals
197
+ sql.gsub!(/\d+/, "?")
198
+ sql
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end