appsignal 1.1.0.beta.6 → 1.1.0.beta.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/ext/agent.yml +7 -7
  4. data/ext/appsignal_extension.c +7 -5
  5. data/ext/extconf.rb +9 -2
  6. data/lib/appsignal/config.rb +5 -2
  7. data/lib/appsignal/event_formatter.rb +2 -0
  8. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +1 -47
  9. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +88 -0
  10. data/lib/appsignal/event_formatter/moped/query_formatter.rb +6 -7
  11. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +13 -0
  12. data/lib/appsignal/hooks/sequel.rb +4 -7
  13. data/lib/appsignal/integrations/capistrano/appsignal.cap +1 -1
  14. data/lib/appsignal/integrations/mongo_ruby_driver.rb +9 -5
  15. data/lib/appsignal/js_exception_transaction.rb +2 -2
  16. data/lib/appsignal/subscriber.rb +3 -2
  17. data/lib/appsignal/transaction.rb +8 -2
  18. data/lib/appsignal/transmitter.rb +1 -1
  19. data/lib/appsignal/utils.rb +38 -4
  20. data/lib/appsignal/version.rb +1 -1
  21. data/spec/lib/appsignal/capistrano3_spec.rb +21 -1
  22. data/spec/lib/appsignal/config_spec.rb +12 -0
  23. data/spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb +1 -1
  24. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +14 -186
  25. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +115 -0
  26. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +4 -4
  27. data/spec/lib/appsignal/event_formatter/sequel/sql_formatter_spec.rb +22 -0
  28. data/spec/lib/appsignal/extension_spec.rb +1 -1
  29. data/spec/lib/appsignal/hooks/sequel_spec.rb +1 -1
  30. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +8 -5
  31. data/spec/lib/appsignal/subscriber_spec.rb +23 -5
  32. data/spec/lib/appsignal/transaction_spec.rb +21 -0
  33. data/spec/lib/appsignal/utils_spec.rb +48 -0
  34. data/spec/support/helpers/env_helpers.rb +1 -0
  35. metadata +8 -2
@@ -28,7 +28,7 @@ module Appsignal
28
28
  @transaction_index,
29
29
  @data['name'],
30
30
  @data['message'],
31
- JSON.generate(@data['backtrace'])
31
+ Appsignal::Utils.json_generate(@data['backtrace'])
32
32
  )
33
33
  end
34
34
 
@@ -43,7 +43,7 @@ module Appsignal
43
43
  Appsignal::Extension.set_transaction_sample_data(
44
44
  @transaction_index,
45
45
  key.to_s,
46
- JSON.generate(data)
46
+ Appsignal::Utils.json_generate(data)
47
47
  )
48
48
  rescue JSON::GeneratorError=>e
49
49
  Appsignal.logger.error("JSON generate error (#{e.message}) for '#{data.inspect}'")
@@ -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
@@ -113,7 +113,7 @@ module Appsignal
113
113
  Appsignal::Extension.set_transaction_sample_data(
114
114
  transaction_index,
115
115
  key.to_s,
116
- JSON.generate(data)
116
+ Appsignal::Utils.json_generate(data)
117
117
  )
118
118
  rescue JSON::GeneratorError=>e
119
119
  Appsignal.logger.error("JSON generate error (#{e.message}) for '#{data.inspect}'")
@@ -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)
@@ -141,7 +142,7 @@ module Appsignal
141
142
  transaction_index,
142
143
  error.class.name,
143
144
  error.message,
144
- backtrace ? JSON.generate(backtrace) : ''
145
+ backtrace ? Appsignal::Utils.json_generate(backtrace) : ''
145
146
  )
146
147
  rescue JSON::GeneratorError=>e
147
148
  Appsignal.logger.error("JSON generate error (#{e.message}) for '#{backtrace.inspect}'")
@@ -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
@@ -53,7 +53,7 @@ module Appsignal
53
53
  request['Content-Type'] = CONTENT_TYPE
54
54
  request['Content-Encoding'] = CONTENT_ENCODING
55
55
  request.body = Zlib::Deflate.deflate(
56
- JSON.generate(payload, :quirks_mode => true),
56
+ Appsignal::Utils.json_generate(payload),
57
57
  Zlib::BEST_SPEED
58
58
  )
59
59
  end
@@ -1,25 +1,59 @@
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.to_s.gsub(/(\..+)/, '.?')
32
+ else key
33
+ end
34
+ end
35
+
36
+ def self.json_generate(body)
37
+ JSON.generate(jsonify(body))
38
+ end
39
+
40
+ def self.jsonify(value)
41
+ case value
42
+ when String
43
+ value.encode(
44
+ 'utf-8',
45
+ :invalid => :replace,
46
+ :undef => :replace
47
+ )
48
+ when Numeric, NilClass, TrueClass, FalseClass
49
+ value
50
+ when Hash
51
+ Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
52
+ when Array
53
+ value.map { |v| jsonify(v) }
54
+ else
55
+ jsonify(value.to_s)
56
+ end
57
+ end
24
58
  end
25
59
  end
@@ -1,5 +1,5 @@
1
1
  require 'yaml'
2
2
 
3
3
  module Appsignal
4
- VERSION = '1.1.0.beta.6'
4
+ VERSION = '1.1.0.beta.7'
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
@@ -98,6 +98,18 @@ describe Appsignal::Config do
98
98
  ENV['APPSIGNAL_HTTP_PROXY'].should == 'http://localhost'
99
99
  ENV['APPSIGNAL_IGNORE_ACTIONS'].should == 'action1,action2'
100
100
  ENV['APPSIGNAL_RUNNING_IN_CONTAINER'].should == 'false'
101
+ ENV['APPSIGNAL_WORKING_DIR_PATH'].should be_nil
102
+ end
103
+
104
+ context "if working_dir_path is set" do
105
+ before do
106
+ subject.config_hash[:working_dir_path] = '/tmp/appsignal2'
107
+ subject.write_to_environment
108
+ end
109
+
110
+ it "should write the current config to env vars" do
111
+ ENV['APPSIGNAL_WORKING_DIR_PATH'].should == '/tmp/appsignal2'
112
+ end
101
113
  end
102
114
  end
103
115
 
@@ -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