appsignal 1.0.4 → 1.0.5.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/ext/agent.yml +7 -7
  4. data/ext/appsignal_extension.c +5 -3
  5. data/lib/appsignal/event_formatter.rb +2 -0
  6. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +1 -47
  7. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +88 -0
  8. data/lib/appsignal/event_formatter/moped/query_formatter.rb +6 -7
  9. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +13 -0
  10. data/lib/appsignal/hooks/sequel.rb +4 -7
  11. data/lib/appsignal/integrations/capistrano/appsignal.cap +1 -1
  12. data/lib/appsignal/integrations/mongo_ruby_driver.rb +9 -5
  13. data/lib/appsignal/subscriber.rb +3 -2
  14. data/lib/appsignal/transaction.rb +6 -0
  15. data/lib/appsignal/utils.rb +15 -4
  16. data/lib/appsignal/version.rb +1 -1
  17. data/spec/lib/appsignal/capistrano3_spec.rb +21 -1
  18. data/spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb +1 -1
  19. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +14 -186
  20. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +115 -0
  21. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +4 -4
  22. data/spec/lib/appsignal/event_formatter/sequel/sql_formatter_spec.rb +22 -0
  23. data/spec/lib/appsignal/extension_spec.rb +1 -1
  24. data/spec/lib/appsignal/hooks/sequel_spec.rb +1 -1
  25. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +8 -5
  26. data/spec/lib/appsignal/subscriber_spec.rb +23 -5
  27. data/spec/lib/appsignal/transaction_spec.rb +21 -0
  28. data/spec/lib/appsignal/utils_spec.rb +16 -0
  29. data/spec/support/helpers/env_helpers.rb +1 -0
  30. metadata +10 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0350232f5d3a6ec3503533f55ec40e31f7f8d332
4
- data.tar.gz: d0a7745539d05747eb88f2b43469c73b8902f0bd
3
+ metadata.gz: 60c1db0e297973e3f82bb7f4e066f81e7f3aa21b
4
+ data.tar.gz: 1ac6c8bd4fc0e5d427c92fde8c1fa2a277137f02
5
5
  SHA512:
6
- metadata.gz: 2a29b7d6750d40d67889cf456f5f161b7c5f64fb405ce54c9986649b9179895560429b920568b1ca8ea947e8e38993b86086727315121a24fea2311d85b6bbc9
7
- data.tar.gz: 396c3d1e3a5370363e4e0ae777e1c767cfb58a611103f5af939aa0d552b646998b36d40aa2b996b2b558c9aad9d0422ba619973dfaaac574c699fc32d19a2039
6
+ metadata.gz: b92b2e03ddd2ab61e795545deb0241e7bb87270389bd558b9b6bb2b72da38f3a91b6171b595df64b0b2f498a9345622afb3048cbf9cde4363e0df413a38dbd09
7
+ data.tar.gz: d26efb030425c886260fd4e0fa9788127dd4bb829bc4bb9e8efc44e041e92d03be44a0246b54aadec6b7b9b72c9be703d60de48efdff428213d523a2a557d69e
@@ -1,3 +1,8 @@
1
+ # 1.0.5
2
+ * Improved sql sanitization
3
+ * Improved mongoid/mongodb sanitization
4
+ * Minor performance improvements
5
+
1
6
  # 1.0.4
2
7
  * Make working dir configurable using `APPSIGNAL_WORKING_DIR_PATH` or `:working_dir_path`
3
8
 
@@ -1,15 +1,15 @@
1
1
  ---
2
- version: '2038949'
2
+ version: 6b0801e
3
3
  triples:
4
4
  x86_64-linux:
5
- checksum: 947bd26d16a70d059520ad30d651acfe50be30868b470d7b0d7ebfdd6d28d189
6
- download_url: https://appsignal-agent-releases.global.ssl.fastly.net/2038949/appsignal-agent-x86_64-linux-static.tar.gz
5
+ checksum: 510a28ef2d76351ca308ebf8950ad50e8c93b4ec955c25f72b658f3ab009d42b
6
+ download_url: https://appsignal-agent-releases.global.ssl.fastly.net/6b0801e/appsignal-agent-x86_64-linux-static.tar.gz
7
7
  lib_filename: libappsignal.a
8
8
  i686-linux:
9
- checksum: 43ebd910b46886cacef6ecc6ac719f0aa8d268e4e49c73cd43814ef76688f319
10
- download_url: https://appsignal-agent-releases.global.ssl.fastly.net/2038949/appsignal-agent-i686-linux-static.tar.gz
9
+ checksum: 7aed9373264128c38c515463c671fba03db2fbc863c85bae77c56a2d240ac86f
10
+ download_url: https://appsignal-agent-releases.global.ssl.fastly.net/6b0801e/appsignal-agent-i686-linux-static.tar.gz
11
11
  lib_filename: libappsignal.a
12
12
  x86_64-darwin:
13
- checksum: 37aee7a624471ea5257fa0118531bc585d832c9b255e64d924b6fc1c85e2baa7
14
- download_url: https://appsignal-agent-releases.global.ssl.fastly.net/2038949/appsignal-agent-x86_64-darwin-static.tar.gz
13
+ checksum: 60ac2a74933d9ac58f441f6ee3f875f5c0cbdee24b6b671f7fc81db992ea117c
14
+ download_url: https://appsignal-agent-releases.global.ssl.fastly.net/6b0801e/appsignal-agent-x86_64-darwin-static.tar.gz
15
15
  lib_filename: libappsignal.a
@@ -26,17 +26,19 @@ static VALUE start_event(VALUE self, VALUE transaction_index) {
26
26
  return Qnil;
27
27
  }
28
28
 
29
- static VALUE finish_event(VALUE self, VALUE transaction_index, VALUE name, VALUE title, VALUE body) {
29
+ static VALUE finish_event(VALUE self, VALUE transaction_index, VALUE name, VALUE title, VALUE body, VALUE body_format) {
30
30
  Check_Type(transaction_index, T_FIXNUM);
31
31
  Check_Type(name, T_STRING);
32
32
  Check_Type(title, T_STRING);
33
33
  Check_Type(body, T_STRING);
34
+ Check_Type(body_format, T_FIXNUM);
34
35
 
35
36
  appsignal_finish_event(
36
37
  FIX2INT(transaction_index),
37
38
  StringValueCStr(name),
38
39
  StringValueCStr(title),
39
- StringValueCStr(body)
40
+ StringValueCStr(body),
41
+ FIX2INT(body_format)
40
42
  );
41
43
  return Qnil;
42
44
  }
@@ -216,7 +218,7 @@ void Init_appsignal_extension(void) {
216
218
  rb_define_singleton_method(Extension, "stop", stop, 0);
217
219
  rb_define_singleton_method(Extension, "start_transaction", start_transaction, 2);
218
220
  rb_define_singleton_method(Extension, "start_event", start_event, 1);
219
- rb_define_singleton_method(Extension, "finish_event", finish_event, 4);
221
+ rb_define_singleton_method(Extension, "finish_event", finish_event, 5);
220
222
  rb_define_singleton_method(Extension, "set_transaction_error", set_transaction_error, 4);
221
223
  rb_define_singleton_method(Extension, "set_transaction_sample_data", set_transaction_sample_data, 3);
222
224
  rb_define_singleton_method(Extension, "set_transaction_action", set_transaction_action, 2);
@@ -60,6 +60,8 @@ module Appsignal
60
60
  end
61
61
  end
62
62
  end
63
+
64
+ SQL_BODY_FORMAT = 1
63
65
  end
64
66
 
65
67
  Dir.glob(File.expand_path('../event_formatter/**/*.rb', __FILE__)).each do |file|
@@ -4,55 +4,9 @@ module Appsignal
4
4
  class SqlFormatter < Appsignal::EventFormatter
5
5
  register 'sql.active_record'
6
6
 
7
- SINGLE_QUOTED_STRING = /'(.?|[^']).*'/.freeze
8
- DOUBLE_QUOTED_STRING = /"(.?|[^"]).*"/.freeze
9
- IN_OPERATOR_CONTENT = /(IN \()[^SELECT][^\)]+(\))/.freeze
10
- NUMERIC = /\d*\.?\d+/.freeze
11
- REPLACEMENT = '?'.freeze
12
- IN_REPLACEMENT = '\1?\2'.freeze
13
- SCHEMA = 'SCHEMA'.freeze
14
-
15
- attr_reader :adapter_uses_double_quoted_table_names
16
-
17
- def initialize
18
- @connection_config = connection_config
19
- @adapter_uses_double_quoted_table_names = adapter_uses_double_quoted_table_names?
20
- rescue ::ActiveRecord::ConnectionNotEstablished
21
- Appsignal::EventFormatter.unregister('sql.active_record', self.class)
22
- Appsignal.logger.error('Error while getting ActiveRecord connection info, unregistering sql.active_record event formatter')
23
- end
24
-
25
7
  def format(payload)
26
- return nil if schema_query?(payload) || !payload[:sql]
27
- sql_string = payload[:sql].dup
28
- unless adapter_uses_double_quoted_table_names
29
- sql_string.gsub!(DOUBLE_QUOTED_STRING, REPLACEMENT)
30
- end
31
- sql_string.gsub!(SINGLE_QUOTED_STRING, REPLACEMENT)
32
- sql_string.gsub!(IN_OPERATOR_CONTENT, IN_REPLACEMENT)
33
- sql_string.gsub!(NUMERIC, REPLACEMENT)
34
- [payload[:name], sql_string]
8
+ [payload[:name], payload[:sql], SQL_BODY_FORMAT]
35
9
  end
36
-
37
- protected
38
-
39
- def schema_query?(payload)
40
- payload[:name] == SCHEMA
41
- end
42
-
43
- def connection_config
44
- # TODO handle ActiveRecord::ConnectionNotEstablished
45
- if ::ActiveRecord::Base.respond_to?(:connection_config)
46
- ::ActiveRecord::Base.connection_config
47
- else
48
- ::ActiveRecord::Base.connection_pool.spec.config
49
- end
50
- end
51
-
52
- def adapter_uses_double_quoted_table_names?
53
- adapter = @connection_config[:adapter]
54
- adapter =~ /postgres/ || adapter =~ /sqlite/
55
- end
56
10
  end
57
11
  end
58
12
  end
@@ -0,0 +1,88 @@
1
+ module Appsignal
2
+ class EventFormatter
3
+ module MongoRubyDriver
4
+ class QueryFormatter
5
+ ALLOWED = {
6
+ "find" => {
7
+ "find" => :allow,
8
+ "filter" => :sanitize_document
9
+ },
10
+ "count" => {
11
+ "count" => :allow,
12
+ "query" => :sanitize_document
13
+ },
14
+ "distinct" => {
15
+ "distinct" => :allow,
16
+ "key" => :allow,
17
+ "query" => :sanitize_document
18
+ },
19
+ "insert" => {
20
+ "insert" => :allow,
21
+ "documents" => :deny_array,
22
+ "ordered" => :allow
23
+ },
24
+ "update" => {
25
+ "update" => :allow,
26
+ "updates" => :sanitize_bulk,
27
+ "ordered" => :allow
28
+ },
29
+ "findandmodify" => {
30
+ "findandmodify" => :allow,
31
+ "query" => :sanitize_document,
32
+ "update" => :deny_array,
33
+ "new" => :allow
34
+ },
35
+ "delete" => {
36
+ "delete" => :allow,
37
+ "deletes" => :sanitize_bulk,
38
+ "ordered" => :allow
39
+ },
40
+ "bulk" => {
41
+ "q" => :sanitize_document,
42
+ "u" => :deny_array,
43
+ "limit" => :allow,
44
+ "multi" => :allow,
45
+ "upsert" => :allow
46
+ }
47
+ }
48
+
49
+ # Format command based on given strategy
50
+ def self.format(strategy, command)
51
+ # Stop processing if command is not a hash
52
+ return {} unless command.is_a?(Hash)
53
+
54
+ # Get the strategy and stop if it's not present
55
+ strategies = ALLOWED[strategy.to_s]
56
+ return {} unless strategies
57
+
58
+ {}.tap do |hsh|
59
+ command.each do |key, val|
60
+ hsh[key] = self.apply_strategy(strategies[key], val)
61
+ end
62
+ end
63
+ end
64
+
65
+ # Applies strategy on hash values based on keys
66
+ def self.apply_strategy(strategy, val)
67
+ case strategy
68
+ when :allow then val
69
+ when :deny then '?'
70
+ when :deny_array then '[?]'
71
+ when :sanitize_document
72
+ Appsignal::Utils.sanitize(val, true, :mongodb)
73
+ when :sanitize_bulk
74
+ if val.length > 1
75
+ [
76
+ self.format(:bulk, val.first),
77
+ "[...]"
78
+ ]
79
+ else
80
+ val.map { |v| self.format(:bulk, v) }
81
+ end
82
+ else '?'
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -11,12 +11,12 @@ module Appsignal
11
11
  when 'Moped::Protocol::Command'
12
12
  return ['Command', {
13
13
  :database => op.full_collection_name,
14
- :selector => Appsignal::Utils.sanitize(op.selector)
14
+ :selector => Appsignal::Utils.sanitize(op.selector, true, :mongodb)
15
15
  }.inspect]
16
16
  when 'Moped::Protocol::Query'
17
17
  return ['Query', {
18
18
  :database => op.full_collection_name,
19
- :selector => Appsignal::Utils.sanitize(op.selector),
19
+ :selector => Appsignal::Utils.sanitize(op.selector, false, :mongodb),
20
20
  :flags => op.flags,
21
21
  :limit => op.limit,
22
22
  :skip => op.skip,
@@ -25,21 +25,21 @@ module Appsignal
25
25
  when 'Moped::Protocol::Delete'
26
26
  return ['Delete', {
27
27
  :database => op.full_collection_name,
28
- :selector => Appsignal::Utils.sanitize(op.selector),
28
+ :selector => Appsignal::Utils.sanitize(op.selector, false, :mongodb),
29
29
  :flags => op.flags,
30
30
  }.inspect]
31
31
  when 'Moped::Protocol::Insert'
32
32
  return ['Insert', {
33
33
  :database => op.full_collection_name,
34
- :documents => Appsignal::Utils.sanitize(op.documents, true),
34
+ :documents => Appsignal::Utils.sanitize(op.documents, true, :mongodb),
35
35
  :count => op.documents.count,
36
36
  :flags => op.flags,
37
37
  }.inspect]
38
38
  when 'Moped::Protocol::Update'
39
39
  return ['Update', {
40
40
  :database => op.full_collection_name,
41
- :selector => Appsignal::Utils.sanitize(op.selector),
42
- :update => Appsignal::Utils.sanitize(op.update, true),
41
+ :selector => Appsignal::Utils.sanitize(op.selector, false, :mongodb),
42
+ :update => Appsignal::Utils.sanitize(op.update, true, :mongodb),
43
43
  :flags => op.flags,
44
44
  }.inspect]
45
45
  when 'Moped::Protocol::KillCursors'
@@ -53,7 +53,6 @@ module Appsignal
53
53
  end
54
54
  end
55
55
  end
56
-
57
56
  end
58
57
  end
59
58
  end
@@ -0,0 +1,13 @@
1
+ module Appsignal
2
+ class EventFormatter
3
+ module Sequel
4
+ class SqlFormatter < Appsignal::EventFormatter
5
+ register 'sql.sequel'
6
+
7
+ def format(payload)
8
+ [nil, payload[:sql], SQL_BODY_FORMAT]
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -3,13 +3,10 @@ module Appsignal
3
3
  module SequelExtension
4
4
  # Add query instrumentation
5
5
  def log_yield(sql, args = nil)
6
-
7
- # We'd like to get full sql queries in the payloads as well. To do
8
- # that we need to find out a way to ask Sequel which quoting strategy
9
- # is used by the adapter. We can then do something similar to the AR
10
- # formatter.
11
-
12
- ActiveSupport::Notifications.instrument('sql.sequel') do
6
+ ActiveSupport::Notifications.instrument(
7
+ 'sql.sequel',
8
+ :sql => sql
9
+ ) do
13
10
  yield
14
11
  end
15
12
  end
@@ -1,6 +1,6 @@
1
1
  namespace :appsignal do
2
2
  task :deploy do
3
- env = fetch(:rails_env, fetch(:rack_env, 'production'))
3
+ env = fetch(:stage, fetch(:rails_env, fetch(:rack_env, 'production')))
4
4
  user = ENV['USER'] || ENV['USERNAME']
5
5
  revision = fetch(:appsignal_revision, fetch(:current_revision))
6
6
  logger = fetch(:logger, Logger.new($stdout))
@@ -1,16 +1,19 @@
1
1
  module Appsignal
2
2
  class Hooks
3
3
  class MongoMonitorSubscriber
4
-
5
4
  # Called by Mongo::Monitor when query starts
6
5
  def started(event)
7
6
  transaction = Appsignal::Transaction.current
8
7
  return if transaction.nil_transaction?
9
8
  return if transaction.paused?
10
9
 
10
+ # Format the command
11
+ command = Appsignal::EventFormatter::MongoRubyDriver::QueryFormatter
12
+ .format(event.command_name, event.command)
13
+
11
14
  # Store the query on the transaction, we need it when the event finishes
12
15
  store = transaction.store('mongo_driver')
13
- store[event.request_id] = Appsignal::Utils.sanitize(event.command)
16
+ store[event.request_id] = command
14
17
 
15
18
  # Start this event
16
19
  Appsignal::Extension.start_event(transaction.transaction_index)
@@ -36,14 +39,15 @@ module Appsignal
36
39
 
37
40
  # Get the query from the transaction store
38
41
  store = transaction.store('mongo_driver')
39
- command = store[event.request_id].inspect
42
+ command = store.delete(event.request_id) || {}
40
43
 
41
44
  # Finish the event in the extension.
42
45
  Appsignal::Extension.finish_event(
43
46
  transaction.transaction_index,
44
47
  'query.mongodb',
45
- event.command_name.to_s,
46
- %Q(#{event.database_name} | #{result} | #{command})
48
+ "#{event.command_name.to_s} | #{event.database_name} | #{result}",
49
+ JSON.generate(command),
50
+ 0
47
51
  )
48
52
  end
49
53
  end
@@ -45,12 +45,13 @@ module Appsignal
45
45
  return unless transaction = Appsignal::Transaction.current
46
46
  return if transaction.nil_transaction? || transaction.paused?
47
47
 
48
- title, body = Appsignal::EventFormatter.format(name, payload)
48
+ title, body, body_format = Appsignal::EventFormatter.format(name, payload)
49
49
  Appsignal::Extension.finish_event(
50
50
  transaction.transaction_index,
51
51
  name,
52
52
  title || BLANK,
53
- body || BLANK
53
+ body || BLANK,
54
+ body_format || 0
54
55
  )
55
56
  end
56
57
  end
@@ -124,6 +124,7 @@ module Appsignal
124
124
  :params => sanitized_params,
125
125
  :environment => sanitized_environment,
126
126
  :session_data => sanitized_session_data,
127
+ :metadata => metadata,
127
128
  :tags => sanitized_tags
128
129
  }.each do |key, data|
129
130
  set_sample_data(key, data)
@@ -212,6 +213,11 @@ module Appsignal
212
213
  Appsignal::ParamsSanitizer.sanitize(session.to_hash)
213
214
  end
214
215
 
216
+ def metadata
217
+ return unless request.env
218
+ request.env[:metadata]
219
+ end
220
+
215
221
  # Only keep tags if they meet the following criteria:
216
222
  # * Key is a symbol or string with less then 100 chars
217
223
  # * Value is a symbol or string with less then 100 chars
@@ -1,25 +1,36 @@
1
1
  module Appsignal
2
2
  module Utils
3
- def self.sanitize(params, only_top_level=false)
3
+ def self.sanitize(params, only_top_level=false, key_sanitizer=nil)
4
4
  if params.is_a?(Hash)
5
5
  {}.tap do |hsh|
6
6
  params.each do |key, val|
7
- hsh[key] = only_top_level ? '?' : sanitize(val, only_top_level)
7
+ hsh[self.sanitize_key(key, key_sanitizer)] = if only_top_level
8
+ '?'
9
+ else
10
+ sanitize(val, only_top_level, key_sanitizer=nil)
11
+ end
8
12
  end
9
13
  end
10
14
  elsif params.is_a?(Array)
11
15
  if only_top_level
12
- sanitize(params[0], only_top_level)
16
+ sanitize(params[0], only_top_level, key_sanitizer=nil)
13
17
  elsif params.first.is_a?(String)
14
18
  ['?']
15
19
  else
16
20
  params.map do |item|
17
- sanitize(item, only_top_level)
21
+ sanitize(item, only_top_level, key_sanitizer=nil)
18
22
  end
19
23
  end
20
24
  else
21
25
  '?'
22
26
  end
23
27
  end
28
+
29
+ def self.sanitize_key(key, sanitizer)
30
+ case sanitizer
31
+ when :mongodb then key.gsub(/(\..+)/, '.?')
32
+ else key
33
+ end
34
+ end
24
35
  end
25
36
  end
@@ -1,5 +1,5 @@
1
1
  require 'yaml'
2
2
 
3
3
  module Appsignal
4
- VERSION = '1.0.4'
4
+ VERSION = '1.0.5.beta.1'
5
5
  end
@@ -80,9 +80,29 @@ if capistrano3_present?
80
80
  )
81
81
  end
82
82
  end
83
+
84
+ context "when stage is used instead of rack_env / rails_env" do
85
+ before do
86
+ @capistrano_config.delete(:rails_env)
87
+ @capistrano_config.set(:stage, 'stage_production')
88
+ end
89
+
90
+ it "should be instantiated with the right params" do
91
+ Appsignal::Config.should_receive(:new).with(
92
+ project_fixture_path,
93
+ 'stage_production',
94
+ {:name => 'AppName'},
95
+ kind_of(Logger)
96
+ )
97
+ end
98
+ end
83
99
  end
84
100
 
85
- after { invoke('appsignal:deploy') }
101
+ after do
102
+ invoke('appsignal:deploy')
103
+ @capistrano_config.delete(:stage)
104
+ @capistrano_config.delete(:rack_env)
105
+ end
86
106
  end
87
107
 
88
108
  context "send marker" do
@@ -4,7 +4,7 @@ describe Appsignal::EventFormatter::ActiveRecord::InstantiationFormatter do
4
4
  let(:klass) { Appsignal::EventFormatter::ActiveRecord::InstantiationFormatter }
5
5
  let(:formatter) { klass.new }
6
6
 
7
- it "should register request.net_http" do
7
+ it "should register instantiation.active_record" do
8
8
  Appsignal::EventFormatter.registered?('instantiation.active_record', klass).should be_true
9
9
  end
10
10
 
@@ -1,195 +1,23 @@
1
1
  require 'spec_helper'
2
2
 
3
- if active_record_present?
4
- require 'active_record'
3
+ describe Appsignal::EventFormatter::ActiveRecord::InstantiationFormatter do
4
+ let(:klass) { Appsignal::EventFormatter::ActiveRecord::SqlFormatter }
5
+ let(:formatter) { klass.new }
5
6
 
6
- describe Appsignal::EventFormatter::ActiveRecord::SqlFormatter do
7
- let(:klass) { Appsignal::EventFormatter::ActiveRecord::SqlFormatter }
8
- let(:formatter) { klass.new }
9
- let(:connection_config) { {} }
10
- before do
11
- if ActiveRecord::Base.respond_to?(:connection_config)
12
- # Rails 3.1+
13
- ActiveRecord::Base.stub(
14
- :connection_config => connection_config
15
- )
16
- else
17
- # Rails 3.0
18
- spec = double(:config => connection_config)
19
- ActiveRecord::Base.stub(
20
- :connection_pool => double(:spec => spec)
21
- )
22
- end
23
- end
24
-
25
- pending "should register sql.activerecord" do
26
- Appsignal::EventFormatter.registered?('sql.active_record', klass).should be_true
27
- end
28
-
29
- context "if a connection cannot be established" do
30
- before do
31
- ActiveRecord::Base.stub(:connection_config).and_raise(ActiveRecord::ConnectionNotEstablished)
32
- end
33
-
34
- it "should log the error and unregister the formatter" do
35
- Appsignal.logger.should_receive(:error).with(
36
- 'Error while getting ActiveRecord connection info, unregistering sql.active_record event formatter'
37
- )
38
-
39
- lambda {
40
- formatter
41
- }.should_not raise_error
42
-
43
- Appsignal::EventFormatter.registered?('sql.active_record').should be_false
44
- end
45
- end
46
-
47
- describe "#format" do
48
- let(:name) { 'Model load' }
49
- let(:payload) { {:sql => sql, :name => name} }
50
- subject { formatter.format(payload) }
51
-
52
- context "with backtick table names" do
53
- before { formatter.stub(:adapter_uses_double_quoted_table_names => false) }
54
-
55
- context "single quoted data value" do
56
- let(:sql) { "SELECT `table`.* FROM `table` WHERE `id` = 'secret' ORDER BY `table`.`id` ASC LIMIT 1" }
57
-
58
- it { should == ['Model load', "SELECT `table`.* FROM `table` WHERE `id` = ? ORDER BY `table`.`id` ASC LIMIT ?"] }
59
-
60
- context "with escaped single quotes in the string" do
61
- let(:sql) { "`id` = 'this is a \'big\' secret'" }
62
-
63
- it { should == ['Model load', "`id` = ?"] }
64
- end
65
- end
66
-
67
- context "double quoted data value" do
68
- let(:sql) { 'SELECT `table`.* FROM `table` WHERE `id` = "secret"' }
69
-
70
- it { should == ['Model load', 'SELECT `table`.* FROM `table` WHERE `id` = ?'] }
71
-
72
- context "with escaped double quotes in the string" do
73
- let(:sql) { '`id` = "this is a \"big\" secret"' }
74
-
75
- it { should == ['Model load', "`id` = ?"] }
76
- end
77
- end
78
-
79
- context "numeric parameter" do
80
- context "integer" do
81
- let(:sql) { 'SELECT `table`.* FROM `table` WHERE `id` = 1' }
82
-
83
- it { should == ['Model load', 'SELECT `table`.* FROM `table` WHERE `id` = ?'] }
84
- end
85
-
86
- context "float" do
87
- let(:sql) { 'SELECT `table`.* FROM `table` WHERE `value` = 10.0' }
88
-
89
- it { should == ['Model load', 'SELECT `table`.* FROM `table` WHERE `value` = ?'] }
90
- end
91
- end
92
-
93
- context "in operator with values" do
94
- let(:sql) { 'SELECT `table`.* FROM `table` WHERE `id` IN (1, 2)' }
95
-
96
- it { should == ['Model load', 'SELECT `table`.* FROM `table` WHERE `id` IN (?)'] }
97
- end
98
-
99
- context "in operator with inner query" do
100
- let(:sql) { 'SELECT `table`.* FROM `table` WHERE `id` IN (SELECT `id` from `other_table` WHERE `value` = 10.0)' }
101
-
102
- it { should == ['Model load', 'SELECT `table`.* FROM `table` WHERE `id` IN (SELECT `id` from `other_table` WHERE `value` = ?)'] }
103
- end
104
- end
105
-
106
- context "with double quote style table names" do
107
- let(:connection_config) { {:adapter => 'postgresql'} }
108
-
109
- context "single quoted data value" do
110
- let(:sql) { "SELECT \"table\".* FROM \"table\" WHERE \"id\" = 'secret' ORDER BY \"table\".\"id\" ASC LIMIT 1" }
111
-
112
- it { should == ['Model load', "SELECT \"table\".* FROM \"table\" WHERE \"id\" = ? ORDER BY \"table\".\"id\" ASC LIMIT ?"] }
113
-
114
- context "with an escaped single quote" do
115
- let(:sql) { "\"id\" = 'this is a \'big\' secret'" }
116
-
117
- it { should == ['Model load', "\"id\" = ?"] }
118
- end
119
- end
120
-
121
- context "numeric parameter" do
122
- context "integer" do
123
- let(:sql) { 'SELECT "table".* FROM "table" WHERE "id"=1' }
124
-
125
- it { should == ['Model load', 'SELECT "table".* FROM "table" WHERE "id"=?'] }
126
- end
127
-
128
- context "float" do
129
- let(:sql) { 'SELECT "table".* FROM "table" WHERE "value"=10.0' }
130
-
131
- it { should == ['Model load', 'SELECT "table".* FROM "table" WHERE "value"=?'] }
132
- end
133
- end
134
- end
135
-
136
- context "return nil for schema queries" do
137
- let(:name) { 'SCHEMA' }
138
- let(:sql) { 'SET client_min_messages TO 22' }
139
-
140
- it { should be_nil }
141
- end
142
-
143
- context "with a a frozen sql string" do
144
- let(:sql) { "SELECT `table`.* FROM `table` WHERE `id` = 'secret'".freeze }
145
-
146
- it { should == ['Model load', "SELECT `table`.* FROM `table` WHERE `id` = ?"] }
147
- end
148
- end
149
-
150
- describe "#schema_query?" do
151
- let(:payload) { {} }
152
- subject { formatter.send(:schema_query?, payload) }
153
-
154
- it { should be_false }
155
-
156
- context "when name is schema" do
157
- let(:payload) { {:name => 'SCHEMA'} }
7
+ it "should register sql.active_record" do
8
+ Appsignal::EventFormatter.registered?('sql.active_record', klass).should be_true
9
+ end
158
10
 
159
- it { should be_true }
160
- end
11
+ describe "#format" do
12
+ let(:payload) do
13
+ {
14
+ name: 'User load',
15
+ sql: 'SELECT * FROM users'
16
+ }
161
17
  end
162
18
 
163
- context "connection config" do
164
- describe "#connection_config" do
165
- let(:connection_config) { {:adapter => 'adapter'} }
166
-
167
- subject { formatter.send(:connection_config) }
168
-
169
- it { should == {:adapter => 'adapter'} }
170
- end
171
-
172
- describe "#adapter_uses_double_quoted_table_names" do
173
- subject { formatter.adapter_uses_double_quoted_table_names }
19
+ subject { formatter.format(payload) }
174
20
 
175
- context "when using mysql" do
176
- let(:connection_config) { {:adapter => 'mysql'} }
177
-
178
- it { should be_false }
179
- end
180
-
181
- context "when using postgresql" do
182
- let(:connection_config) { {:adapter => 'postgresql'} }
183
-
184
- it { should be_true }
185
- end
186
-
187
- context "when using sqlite" do
188
- let(:connection_config) { {:adapter => 'sqlite'} }
189
-
190
- it { should be_true }
191
- end
192
- end
193
- end
21
+ it { should == ['User load', 'SELECT * FROM users', 1] }
194
22
  end
195
23
  end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::EventFormatter::MongoRubyDriver::QueryFormatter do
4
+ let(:formatter) { Appsignal::EventFormatter::MongoRubyDriver::QueryFormatter }
5
+
6
+ describe ".format" do
7
+ let(:strategy) { :find }
8
+ let(:command) do
9
+ {
10
+ "find" => "users",
11
+ "filter" => {"_id" => 1}
12
+ }
13
+ end
14
+
15
+ it "should apply a strategy for each key" do
16
+ expect( formatter ).to receive(:apply_strategy)
17
+ .with(:sanitize_document, {"_id" => 1})
18
+
19
+ expect( formatter ).to receive(:apply_strategy)
20
+ .with(:allow, "users")
21
+
22
+ formatter.format(strategy, command)
23
+ end
24
+
25
+ context "when strategy is unkown" do
26
+ let(:strategy) { :bananas }
27
+
28
+ it "should return an empty hash" do
29
+ expect( formatter.format(strategy, command) ).to eql({})
30
+ end
31
+ end
32
+
33
+ context "when command is not a hash " do
34
+ let(:command) { :bananas }
35
+
36
+ it "should return an empty hash" do
37
+ expect( formatter.format(strategy, command) ).to eql({})
38
+ end
39
+ end
40
+ end
41
+
42
+ describe ".apply_strategy" do
43
+ context "when strategy is allow" do
44
+ let(:strategy) { :allow }
45
+ let(:value) { {"_id" => 1} }
46
+
47
+ it "should return the given value" do
48
+ expect( formatter.apply_strategy(strategy, value) ).to eql(value)
49
+ end
50
+ end
51
+
52
+ context "when strategy is deny" do
53
+ let(:strategy) { :deny }
54
+ let(:value) { {"_id" => 1} }
55
+
56
+ it "should return a '?'" do
57
+ expect( formatter.apply_strategy(strategy, value) ).to eql('?')
58
+ end
59
+ end
60
+
61
+ context "when strategy is deny_array" do
62
+ let(:strategy) { :deny_array }
63
+ let(:value) { {"_id" => 1} }
64
+
65
+ it "should return a sanitized array string" do
66
+ expect( formatter.apply_strategy(strategy, value) ).to eql("[?]")
67
+ end
68
+ end
69
+
70
+ context "when strategy is sanitize_document" do
71
+ let(:strategy) { :sanitize_document }
72
+ let(:value) { {"_id" => 1} }
73
+
74
+ it "should return a sanitized document" do
75
+ expect( formatter.apply_strategy(strategy, value) ).to eql({"_id" => '?'})
76
+ end
77
+ end
78
+
79
+ context "when strategy is sanitize_bulk" do
80
+ let(:strategy) { :sanitize_bulk }
81
+ let(:value) { [{"q" => {"_id" => 1}, "u" => [{"foo" => "bar"}]}] }
82
+
83
+ it "should return an array of sanitized bulk documents" do
84
+ expect( formatter.apply_strategy(strategy, value) ).to eql([
85
+ {"q" => {"_id" => '?'}, "u" => '[?]'}
86
+ ])
87
+ end
88
+
89
+ context "when bulk has more than one update" do
90
+ let(:value) do
91
+ [
92
+ {"q" => {"_id" => 1}, "u" => [{"foo" => "bar"}]},
93
+ {"q" => {"_id" => 2}, "u" => [{"foo" => "baz"}]},
94
+ ]
95
+ end
96
+
97
+ it "should return only the first value of sanitized bulk documents" do
98
+ expect( formatter.apply_strategy(strategy, value) ).to eql([
99
+ {"q" => {"_id" => '?'}, "u" => '[?]'},
100
+ "[...]"
101
+ ])
102
+ end
103
+ end
104
+ end
105
+
106
+ context "when strategy is missing" do
107
+ let(:strategy) { nil }
108
+ let(:value) { {"_id" => 1} }
109
+
110
+ it "should return a '?'" do
111
+ expect( formatter.apply_strategy(strategy, value) ).to eql('?')
112
+ end
113
+ end
114
+ end
115
+ end
@@ -22,12 +22,12 @@ describe Appsignal::EventFormatter::Moped::QueryFormatter do
22
22
  let(:op) do
23
23
  double(
24
24
  :full_collection_name => 'database.collection',
25
- :selector => {'_id' => 'abc'},
25
+ :selector => {'query' => {'_id' => 'abc'}},
26
26
  :class => double(:to_s => 'Moped::Protocol::Command')
27
27
  )
28
28
  end
29
29
 
30
- it { should == ['Command', '{:database=>"database.collection", :selector=>{"_id"=>"?"}}'] }
30
+ it { should == ['Command', '{:database=>"database.collection", :selector=>{"query"=>"?"}}'] }
31
31
  end
32
32
 
33
33
  context "Moped::Protocol::Query" do
@@ -80,13 +80,13 @@ describe Appsignal::EventFormatter::Moped::QueryFormatter do
80
80
  double(
81
81
  :full_collection_name => 'database.collection',
82
82
  :selector => {'_id' => 'abc'},
83
- :update => {'name' => 'James Bond'},
83
+ :update => {'user.name' => 'James Bond'},
84
84
  :flags => [],
85
85
  :class => double(:to_s => 'Moped::Protocol::Update')
86
86
  )
87
87
  end
88
88
 
89
- it { should == ['Update', '{:database=>"database.collection", :selector=>{"_id"=>"?"}, :update=>{"name"=>"?"}, :flags=>[]}'] }
89
+ it { should == ['Update', '{:database=>"database.collection", :selector=>{"_id"=>"?"}, :update=>{"user.?"=>"?"}, :flags=>[]}'] }
90
90
  end
91
91
 
92
92
  context "Moped::Protocol::KillCursors" do
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::EventFormatter::Sequel::SqlFormatter do
4
+ let(:klass) { Appsignal::EventFormatter::Sequel::SqlFormatter }
5
+ let(:formatter) { klass.new }
6
+
7
+ it "should register sql.sequel" do
8
+ Appsignal::EventFormatter.registered?('sql.sequel', klass).should be_true
9
+ end
10
+
11
+ describe "#format" do
12
+ let(:payload) do
13
+ {
14
+ sql: 'SELECT * FROM users'
15
+ }
16
+ end
17
+
18
+ subject { formatter.format(payload) }
19
+
20
+ it { should == [nil, 'SELECT * FROM users', 1] }
21
+ end
22
+ end
@@ -46,7 +46,7 @@ describe "extension loading and operation" do
46
46
  end
47
47
 
48
48
  it "should have a finish_event method" do
49
- subject.finish_event(1, 'name', 'title', 'body')
49
+ subject.finish_event(1, 'name', 'title', 'body', 0)
50
50
  end
51
51
 
52
52
  it "should have a set_transaction_error method" do
@@ -15,7 +15,7 @@ describe "Sequel integration", if: sequel_present? do
15
15
  .at_least(:once)
16
16
  expect( Appsignal::Extension ).to receive(:finish_event)
17
17
  .at_least(:once)
18
- .with(kind_of(Integer), "sql.sequel", "", "")
18
+ .with(kind_of(Integer), "sql.sequel", "", kind_of(String), 1)
19
19
 
20
20
  db['SELECT 1'].all
21
21
  end
@@ -11,13 +11,15 @@ describe Appsignal::Hooks::MongoMonitorSubscriber do
11
11
  describe "#started" do
12
12
  let(:event) do
13
13
  double(
14
- :request_id => 1,
15
- :command => {'foo' => 'bar'}
14
+ :request_id => 1,
15
+ :command_name => 'find',
16
+ :command => {'foo' => 'bar'}
16
17
  )
17
18
  end
18
19
 
19
20
  it "should sanitize command" do
20
- Appsignal::Utils.should receive(:sanitize).with({'foo' => 'bar'} )
21
+ Appsignal::EventFormatter::MongoRubyDriver::QueryFormatter
22
+ .should receive(:format).with('find', {'foo' => 'bar'})
21
23
 
22
24
  subscriber.started(event)
23
25
  end
@@ -81,8 +83,9 @@ describe Appsignal::Hooks::MongoMonitorSubscriber do
81
83
  Appsignal::Extension.should receive(:finish_event).with(
82
84
  transaction.transaction_index,
83
85
  'query.mongodb',
84
- 'find',
85
- "test | SUCCEEDED | {\"foo\"=>\"?\"}"
86
+ 'find | test | SUCCEEDED',
87
+ "{\"foo\":\"?\"}",
88
+ 0
86
89
  )
87
90
 
88
91
  subscriber.finish('SUCCEEDED', event)
@@ -98,10 +98,10 @@ describe Appsignal::Subscriber do
98
98
 
99
99
  it "should call native start and finish event for every event" do
100
100
  Appsignal::Extension.should_receive(:start_event).exactly(4).times
101
- Appsignal::Extension.should_receive(:finish_event).with(kind_of(Integer), 'one', '', '').once
102
- Appsignal::Extension.should_receive(:finish_event).with(kind_of(Integer), 'two', '', '').once
103
- Appsignal::Extension.should_receive(:finish_event).with(kind_of(Integer), 'two.three', '', '').once
104
- Appsignal::Extension.should_receive(:finish_event).with(kind_of(Integer), 'one.three', '', '').once
101
+ Appsignal::Extension.should_receive(:finish_event).with(kind_of(Integer), 'one', '', '', 0).once
102
+ Appsignal::Extension.should_receive(:finish_event).with(kind_of(Integer), 'two', '', '', 0).once
103
+ Appsignal::Extension.should_receive(:finish_event).with(kind_of(Integer), 'two.three', '', '', 0).once
104
+ Appsignal::Extension.should_receive(:finish_event).with(kind_of(Integer), 'one.three', '', '', 0).once
105
105
 
106
106
  ActiveSupport::Notifications.instrument('one') do
107
107
  ActiveSupport::Notifications.instrument('two') do
@@ -119,7 +119,8 @@ describe Appsignal::Subscriber do
119
119
  kind_of(Integer),
120
120
  'request.net_http',
121
121
  'GET http://www.google.com',
122
- ''
122
+ '',
123
+ 0
123
124
  ).once
124
125
 
125
126
  ActiveSupport::Notifications.instrument(
@@ -130,6 +131,23 @@ describe Appsignal::Subscriber do
130
131
  )
131
132
  end
132
133
 
134
+ it "should call finish with title, body and body format if there is a formatter that returns it" do
135
+ Appsignal::Extension.should_receive(:start_event).once
136
+ Appsignal::Extension.should_receive(:finish_event).with(
137
+ kind_of(Integer),
138
+ 'sql.active_record',
139
+ 'Something load',
140
+ 'SELECT * FROM something',
141
+ 1
142
+ ).once
143
+
144
+ ActiveSupport::Notifications.instrument(
145
+ 'sql.active_record',
146
+ :name => 'Something load',
147
+ :sql => 'SELECT * FROM something'
148
+ )
149
+ end
150
+
133
151
  context "when paused" do
134
152
  before { transaction.pause! }
135
153
 
@@ -331,6 +331,11 @@ describe Appsignal::Transaction do
331
331
  'params',
332
332
  '{"controller":"blog_posts","action":"show","id":"1"}'
333
333
  ).once
334
+ Appsignal::Extension.should_receive(:set_transaction_sample_data).with(
335
+ kind_of(Integer),
336
+ 'metadata',
337
+ '{"key":"value"}'
338
+ ).once
334
339
  Appsignal::Extension.should_receive(:set_transaction_sample_data).with(
335
340
  kind_of(Integer),
336
341
  'tags',
@@ -616,6 +621,22 @@ describe Appsignal::Transaction do
616
621
  end
617
622
  end
618
623
 
624
+ describe "#metadata" do
625
+ subject { transaction.send(:metadata) }
626
+
627
+ context "when env is nil" do
628
+ before { transaction.request.stub(:env => nil) }
629
+
630
+ it { should be_nil }
631
+ end
632
+
633
+ context "when env is present" do
634
+ let(:env) { {:metadata => {:key => 'value'}} }
635
+
636
+ it { should == env[:metadata] }
637
+ end
638
+ end
639
+
619
640
  describe '#sanitized_tags' do
620
641
  before do
621
642
  transaction.set_tags(
@@ -33,4 +33,20 @@ describe Appsignal::Utils do
33
33
  end
34
34
  end
35
35
  end
36
+
37
+ describe ".sanitize_key" do
38
+ it "should not sanitize key when no key_sanitizer is given" do
39
+ expect( Appsignal::Utils.sanitize_key('foo', nil) ).to eql('foo')
40
+ end
41
+
42
+ context "with mongodb sanitizer" do
43
+ it "should not sanitize key when no dots are in the key" do
44
+ expect( Appsignal::Utils.sanitize_key('foo', :mongodb) ).to eql('foo')
45
+ end
46
+
47
+ it "should sanitize key when dots are in the key" do
48
+ expect( Appsignal::Utils.sanitize_key('foo.bar', :mongodb) ).to eql('foo.?')
49
+ end
50
+ end
51
+ end
36
52
  end
@@ -15,6 +15,7 @@ module EnvHelpers
15
15
  :status => '200',
16
16
  :view_runtime => 500,
17
17
  :db_runtime => 500,
18
+ :metadata => {:key => 'value'}
18
19
  ).merge(args)
19
20
  end
20
21
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appsignal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Beekman
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-02-24 00:00:00.000000000 Z
12
+ date: 2016-03-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -154,8 +154,10 @@ files:
154
154
  - lib/appsignal/event_formatter/action_view/render_formatter.rb
155
155
  - lib/appsignal/event_formatter/active_record/instantiation_formatter.rb
156
156
  - lib/appsignal/event_formatter/active_record/sql_formatter.rb
157
+ - lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb
157
158
  - lib/appsignal/event_formatter/moped/query_formatter.rb
158
159
  - lib/appsignal/event_formatter/net_http/request_formatter.rb
160
+ - lib/appsignal/event_formatter/sequel/sql_formatter.rb
159
161
  - lib/appsignal/extension.rb
160
162
  - lib/appsignal/hooks.rb
161
163
  - lib/appsignal/hooks/celluloid.rb
@@ -209,8 +211,10 @@ files:
209
211
  - spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb
210
212
  - spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb
211
213
  - spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb
214
+ - spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb
212
215
  - spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb
213
216
  - spec/lib/appsignal/event_formatter/net_http/request_formatter_spec.rb
217
+ - spec/lib/appsignal/event_formatter/sequel/sql_formatter_spec.rb
214
218
  - spec/lib/appsignal/event_formatter_spec.rb
215
219
  - spec/lib/appsignal/extension_spec.rb
216
220
  - spec/lib/appsignal/hooks/celluloid_spec.rb
@@ -274,9 +278,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
274
278
  version: '1.9'
275
279
  required_rubygems_version: !ruby/object:Gem::Requirement
276
280
  requirements:
277
- - - ">="
281
+ - - ">"
278
282
  - !ruby/object:Gem::Version
279
- version: '0'
283
+ version: 1.3.1
280
284
  requirements: []
281
285
  rubyforge_project:
282
286
  rubygems_version: 2.2.5
@@ -292,8 +296,10 @@ test_files:
292
296
  - spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb
293
297
  - spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb
294
298
  - spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb
299
+ - spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb
295
300
  - spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb
296
301
  - spec/lib/appsignal/event_formatter/net_http/request_formatter_spec.rb
302
+ - spec/lib/appsignal/event_formatter/sequel/sql_formatter_spec.rb
297
303
  - spec/lib/appsignal/event_formatter_spec.rb
298
304
  - spec/lib/appsignal/extension_spec.rb
299
305
  - spec/lib/appsignal/hooks/celluloid_spec.rb