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

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 (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