appsignal 1.0.4 → 1.0.5.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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