appsignal 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +19 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +30 -0
  4. data/Gemfile +3 -0
  5. data/LICENCE +20 -0
  6. data/README.md +48 -0
  7. data/Rakefile +52 -0
  8. data/appsignal.gemspec +33 -0
  9. data/bin/appsignal +13 -0
  10. data/config/appsignal.yml +8 -0
  11. data/gemfiles/3.0.gemfile +16 -0
  12. data/gemfiles/3.1.gemfile +16 -0
  13. data/gemfiles/3.2.gemfile +16 -0
  14. data/gemfiles/edge.gemfile +16 -0
  15. data/lib/appsignal.rb +45 -0
  16. data/lib/appsignal/agent.rb +104 -0
  17. data/lib/appsignal/auth_check.rb +19 -0
  18. data/lib/appsignal/capistrano.rb +41 -0
  19. data/lib/appsignal/cli.rb +118 -0
  20. data/lib/appsignal/config.rb +30 -0
  21. data/lib/appsignal/exception_notification.rb +25 -0
  22. data/lib/appsignal/marker.rb +35 -0
  23. data/lib/appsignal/middleware.rb +30 -0
  24. data/lib/appsignal/railtie.rb +19 -0
  25. data/lib/appsignal/transaction.rb +77 -0
  26. data/lib/appsignal/transaction/faulty_request_formatter.rb +30 -0
  27. data/lib/appsignal/transaction/params_sanitizer.rb +36 -0
  28. data/lib/appsignal/transaction/regular_request_formatter.rb +11 -0
  29. data/lib/appsignal/transaction/slow_request_formatter.rb +34 -0
  30. data/lib/appsignal/transaction/transaction_formatter.rb +93 -0
  31. data/lib/appsignal/transmitter.rb +53 -0
  32. data/lib/appsignal/version.rb +3 -0
  33. data/lib/generators/appsignal/USAGE +8 -0
  34. data/lib/generators/appsignal/appsignal_generator.rb +70 -0
  35. data/lib/generators/appsignal/templates/appsignal.yml +4 -0
  36. data/log/.gitkeep +0 -0
  37. data/resources/cacert.pem +3849 -0
  38. data/spec/appsignal/agent_spec.rb +259 -0
  39. data/spec/appsignal/auth_check_spec.rb +36 -0
  40. data/spec/appsignal/capistrano_spec.rb +81 -0
  41. data/spec/appsignal/cli_spec.rb +124 -0
  42. data/spec/appsignal/config_spec.rb +40 -0
  43. data/spec/appsignal/exception_notification_spec.rb +12 -0
  44. data/spec/appsignal/inactive_railtie_spec.rb +30 -0
  45. data/spec/appsignal/marker_spec.rb +83 -0
  46. data/spec/appsignal/middleware_spec.rb +73 -0
  47. data/spec/appsignal/railtie_spec.rb +54 -0
  48. data/spec/appsignal/transaction/faulty_request_formatter_spec.rb +49 -0
  49. data/spec/appsignal/transaction/params_sanitizer_spec.rb +68 -0
  50. data/spec/appsignal/transaction/regular_request_formatter_spec.rb +14 -0
  51. data/spec/appsignal/transaction/slow_request_formatter_spec.rb +76 -0
  52. data/spec/appsignal/transaction/transaction_formatter_spec.rb +178 -0
  53. data/spec/appsignal/transaction_spec.rb +191 -0
  54. data/spec/appsignal/transmitter_spec.rb +64 -0
  55. data/spec/appsignal_spec.rb +66 -0
  56. data/spec/generators/appsignal/appsignal_generator_spec.rb +222 -0
  57. data/spec/spec_helper.rb +85 -0
  58. data/spec/support/delegate_matcher.rb +39 -0
  59. metadata +247 -0
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Config do
4
+ subject { Appsignal::Config.new(Dir.pwd, 'test').load }
5
+
6
+ it {
7
+ should == {
8
+ :ignore_exceptions => [],
9
+ :endpoint => 'http://localhost:3000/1',
10
+ :slow_request_threshold => 200,
11
+ :api_key => 'abc',
12
+ :active => true
13
+ }
14
+ }
15
+
16
+ context 'when there is no config file' do
17
+ before { Dir.stub(:pwd => '/not/existing') }
18
+
19
+ it "should log error" do
20
+ Appsignal.logger.should_receive(:error).with(
21
+ "config not found at:"\
22
+ " /not/existing/config/appsignal.yml"
23
+ )
24
+ end
25
+
26
+ after { subject }
27
+ end
28
+
29
+ context "the env is not in the config" do
30
+ subject { Appsignal::Config.new(Dir.pwd, 'staging').load }
31
+
32
+ it "should generate error" do
33
+ Appsignal.logger.should_receive(:error).with(
34
+ "config for 'staging' not found"
35
+ )
36
+ end
37
+
38
+ after { subject }
39
+ end
40
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::ExceptionNotification do
4
+ let(:error) { StandardError.new('moo') }
5
+ let(:notification) { Appsignal::ExceptionNotification.new({}, error) }
6
+ subject { notification }
7
+ before { Rails.stub(:respond_to? => false) }
8
+
9
+ its(:exception) { should == error }
10
+ its(:name) { should == 'StandardError' }
11
+ its(:message) { should == 'moo' }
12
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Inactive Appsignal::Railtie" do
4
+ it "should not insert itself into the middleware stack" do
5
+ # This uses a hack because Rails really dislikes you trying to
6
+ # start multiple applications in one process. This works decently
7
+ # on every platform except JRuby, so we're disabling this test on
8
+ # JRuby for now.
9
+ if RUBY_PLATFORM == "java"
10
+ pending "This spec cannot run on JRuby currently"
11
+ else
12
+ pid = fork do
13
+ Appsignal.stub(:active? => false)
14
+ Rails.application = nil
15
+ instance_eval do
16
+ module MyTempApp
17
+ class Application < Rails::Application
18
+ config.active_support.deprecation = proc { |message, stack| }
19
+ end
20
+ end
21
+ end
22
+ MyTempApp::Application.initialize!
23
+
24
+ MyTempApp::Application.middleware.to_a.should_not include Appsignal::Middleware
25
+ end
26
+ Process.wait(pid)
27
+ raise 'Example failed' unless $?.exitstatus == 0
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Marker do
4
+ let(:marker) {
5
+ Appsignal::Marker.new({
6
+ :revision => '503ce0923ed177a3ce000005',
7
+ :repository => 'master',
8
+ :user => 'batman',
9
+ :rails_env => 'development'
10
+ },
11
+ Dir.pwd,
12
+ 'development',
13
+ logger
14
+ )
15
+ }
16
+ let(:log) { StringIO.new }
17
+ let(:logger) { Logger.new(log) }
18
+
19
+ context "transmit" do
20
+ before do
21
+ @transmitter = mock()
22
+ Appsignal::Transmitter.should_receive(:new).
23
+ with('http://localhost:3000/1', 'markers', 'abc').
24
+ and_return(@transmitter)
25
+ end
26
+
27
+ it "should transmit data" do
28
+ @transmitter.should_receive(:transmit).
29
+ with(
30
+ {
31
+ :revision => "503ce0923ed177a3ce000005",
32
+ :repository => "master",
33
+ :user => "batman",
34
+ :rails_env => "development"
35
+ }
36
+ )
37
+
38
+ marker.transmit
39
+ end
40
+
41
+ context "logs" do
42
+ shared_examples_for "logging info and errors" do
43
+ it "should log status 200" do
44
+ @transmitter.should_receive(:transmit).and_return('200')
45
+
46
+ marker.transmit
47
+
48
+ log.string.should include('Notifying Appsignal of deploy...')
49
+ log.string.should include(
50
+ 'Appsignal has been notified of this deploy!'
51
+ )
52
+ end
53
+
54
+ it "should log other status" do
55
+ @transmitter.should_receive(:transmit).and_return('500')
56
+ @transmitter.should_receive(:uri).and_return('http://localhost:3000/1/markers')
57
+
58
+ marker.transmit
59
+
60
+ log.string.should include('Notifying Appsignal of deploy...')
61
+ log.string.should include(
62
+ 'Something went wrong while trying to notify Appsignal: 500 at http://localhost:3000/1/markers'
63
+ )
64
+ log.string.should_not include(
65
+ 'Appsignal has been notified of this deploy!'
66
+ )
67
+ end
68
+ end
69
+
70
+ it_should_behave_like "logging info and errors"
71
+
72
+ context "with a Capistrano logger" do
73
+ let(:logger) {
74
+ Capistrano::Logger.new(:output => log).tap do |logger|
75
+ logger.level = Capistrano::Logger::MAX_LEVEL
76
+ end
77
+ }
78
+
79
+ it_should_behave_like "logging info and errors"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ module Appsignal
4
+ IgnoreMeError = Class.new(StandardError)
5
+ end
6
+
7
+ class AppWithError
8
+ def self.call(env)
9
+ raise Appsignal::IgnoreMeError, 'the roof'
10
+ end
11
+ end
12
+
13
+ describe Appsignal::Middleware do
14
+ describe '#call' do
15
+ let(:app) { stub(:call => true) }
16
+ let(:env) { {'action_dispatch.request_id' => '1'} }
17
+ let(:middleware) { Appsignal::Middleware.new(app, {})}
18
+ let(:current) { stub(:complete! => true, :add_exception => true) }
19
+ before { Appsignal::Transaction.stub(:current => current) }
20
+
21
+ describe 'around call' do
22
+ it 'should call appsignal transaction' do
23
+ Appsignal::Transaction.should_receive(:create).with('1', env)
24
+ end
25
+
26
+ it 'should call complete! after the call' do
27
+ current.should_receive(:complete!)
28
+ end
29
+
30
+ after { middleware.call(env) }
31
+ end
32
+
33
+ describe 'with exception' do
34
+ let(:app) { AppWithError }
35
+
36
+ it 'should re-raise the exception' do
37
+ expect {
38
+ middleware.call(env)
39
+ }.to raise_error
40
+ end
41
+
42
+ it 'should catch the exception and notify the transaction of it' do
43
+ Appsignal::ExceptionNotification.should_receive(:new)
44
+ current.should_receive(:add_exception)
45
+ middleware.call(env) rescue nil
46
+ end
47
+
48
+ context 'when ignoring exception' do
49
+ before { Appsignal.stub(:config => {:ignore_exceptions => 'Appsignal::IgnoreMeError'})}
50
+
51
+ it 'should re-raise the exception' do
52
+ expect {
53
+ middleware.call(env)
54
+ }.to raise_error
55
+ end
56
+
57
+ it 'should ignore the error' do
58
+ Appsignal::ExceptionNotification.should_not_receive(:new)
59
+ current.should_not_receive(:add_exception)
60
+ middleware.call(env) rescue nil
61
+ end
62
+ end
63
+
64
+ describe 'after an error' do
65
+ it 'should call complete! after the call' do
66
+ current.should_receive(:complete!)
67
+ end
68
+
69
+ after { middleware.call(env) rescue nil }
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'action_controller/railtie'
3
+ require 'appsignal/railtie'
4
+
5
+ describe Appsignal::Railtie do
6
+
7
+ before(:all) { MyApp::Application.initialize! }
8
+
9
+ it "should have set the appsignal subscriber" do
10
+ Appsignal.subscriber.
11
+ should be_a ActiveSupport::Notifications::Fanout::Subscriber
12
+ end
13
+
14
+ it "should have added the middleware for exceptions" do
15
+ MyApp::Application.middleware.to_a.should include Appsignal::Middleware
16
+ end
17
+
18
+ context "non action_controller event" do
19
+ it "should call add_event for non action_controller event" do
20
+ current = stub
21
+ current.should_receive(:add_event)
22
+ Appsignal::Transaction.should_receive(:current).twice.
23
+ and_return(current)
24
+ end
25
+
26
+ after { ActiveSupport::Notifications.instrument 'query.mongoid' }
27
+ end
28
+
29
+ context "action_controller event" do
30
+ it "should call set_process_action_event for action_controller event" do
31
+ current = stub
32
+ current.should_receive(:set_process_action_event)
33
+ current.should_receive(:add_event)
34
+ Appsignal::Transaction.should_receive(:current).exactly(3).times.
35
+ and_return(current)
36
+ end
37
+
38
+ after do
39
+ ActiveSupport::Notifications.
40
+ instrument 'process_action.action_controller'
41
+ end
42
+ end
43
+
44
+ context "event that starts with a bang" do
45
+ it "should not be processed" do
46
+ Appsignal::Transaction.should_not_receive(:current)
47
+ end
48
+
49
+ after do
50
+ ActiveSupport::Notifications.
51
+ instrument '!render_template'
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::TransactionFormatter::FaultyRequestFormatter do
4
+ let(:parent) { Appsignal::TransactionFormatter }
5
+ let(:transaction) { transaction_with_exception }
6
+ let(:faulty) { parent::FaultyRequestFormatter.new(transaction) }
7
+ subject { faulty }
8
+
9
+ describe "#to_hash" do
10
+ it "can call #to_hash on its superclass" do
11
+ parent.new(transaction).respond_to?(:to_hash).should be_true
12
+ end
13
+
14
+ context "return value" do
15
+ subject { faulty.to_hash }
16
+ before { faulty.stub(:formatted_exception => :faulty_request) }
17
+
18
+ it "includes the exception" do
19
+ subject[:exception].should == :faulty_request
20
+ end
21
+ end
22
+ end
23
+
24
+ # protected
25
+
26
+ it { should delegate(:backtrace).to(:exception) }
27
+ it { should delegate(:name).to(:exception) }
28
+ it { should delegate(:message).to(:exception) }
29
+
30
+ describe "#formatted_exception" do
31
+ subject { faulty.send(:formatted_exception) }
32
+
33
+ its(:keys) { should include :backtrace }
34
+ its(:keys) { should include :exception }
35
+ its(:keys) { should include :message }
36
+ end
37
+
38
+ describe "#basic_process_action_event" do
39
+ subject { faulty.send(:basic_process_action_event) }
40
+
41
+ it "should return a hash with extra keys" do
42
+ subject[:environment].should == {
43
+ "HTTP_USER_AGENT" => "IE6",
44
+ "SERVER_NAME" => "localhost"
45
+ }
46
+ subject[:session_data].should == {}
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::ParamsSanitizer do
4
+ describe ".sanitize" do
5
+ let(:file) { ActionDispatch::Http::UploadedFile.new(:tempfile => '/tmp') }
6
+ let(:params) do
7
+ {
8
+ :text => 'string',
9
+ :file => file,
10
+ :hash => {
11
+ :nested_text => 'string',
12
+ :nested_array => [
13
+ 'something',
14
+ 'else',
15
+ file,
16
+ {
17
+ :key => 'value',
18
+ :nested_array_hash_file => file,
19
+ }
20
+ ]
21
+ }
22
+ }
23
+ end
24
+ let(:sanitized_params) { Appsignal::ParamsSanitizer.sanitize(params) }
25
+
26
+ subject { sanitized_params }
27
+
28
+ it { should be_instance_of Hash }
29
+ it('should have a text') { subject[:text].should == 'string' }
30
+ it('should have a file') do
31
+ subject[:file].should be_instance_of String
32
+ subject[:file].should include '#<ActionDispatch::Http::UploadedFile:'
33
+ end
34
+
35
+ context "hash" do
36
+ subject { sanitized_params[:hash] }
37
+
38
+ it { should be_instance_of Hash }
39
+ it('should have a nested text') { subject[:nested_text].should == 'string' }
40
+
41
+ context "nested_array" do
42
+ subject { sanitized_params[:hash][:nested_array] }
43
+
44
+ it { should be_instance_of Array }
45
+
46
+ it("should have two string items") do
47
+ subject.first.should == 'something'
48
+ subject.second.should == 'else'
49
+ end
50
+ it "should have a file" do
51
+ subject.third.should be_instance_of String
52
+ subject.third.should include '#<ActionDispatch::Http::UploadedFile:'
53
+ end
54
+
55
+ context "nested hash" do
56
+ subject { sanitized_params[:hash][:nested_array].fourth }
57
+
58
+ it { should be_instance_of Hash }
59
+ it('should have a text') { subject[:key].should == 'value' }
60
+ it('should have a file') do
61
+ subject[:nested_array_hash_file].should be_instance_of String
62
+ subject[:nested_array_hash_file].should include '#<ActionDispatch::Http::UploadedFile:'
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::TransactionFormatter::RegularRequestFormatter do
4
+ let(:parent) { Appsignal::TransactionFormatter }
5
+ let(:transaction) { appsignal_transaction }
6
+ let(:klass) { parent::RegularRequestFormatter }
7
+ let(:regular) { klass.new(transaction) }
8
+
9
+ describe "#sanitized_event_payload" do
10
+ subject { regular.sanitized_event_payload(:whatever, :arguments) }
11
+
12
+ it { should == {} }
13
+ end
14
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe Appsignal::TransactionFormatter::SlowRequestFormatter do
5
+ let(:parent) { Appsignal::TransactionFormatter }
6
+ let(:transaction) { slow_transaction }
7
+ let(:klass) { parent::SlowRequestFormatter }
8
+ let(:slow) { klass.new(transaction) }
9
+
10
+ describe "#to_hash" do
11
+ subject { slow.to_hash }
12
+ before { slow.stub(:detailed_events => :startled) }
13
+
14
+ it "includes events" do
15
+ subject[:events].should == :startled
16
+ end
17
+ end
18
+
19
+ # protected
20
+
21
+ context "with an event" do
22
+ let(:start_time) { Time.at(2.71828182) }
23
+ let(:end_time) { Time.at(3.141592654) }
24
+ let(:event) do
25
+ mock(
26
+ :event,
27
+ :name => 'Startled',
28
+ :duration => 2,
29
+ :time => start_time,
30
+ :end => end_time,
31
+ :payload => {
32
+ :controller => 'controller',
33
+ :action => 'action',
34
+ :sensitive => 'data'
35
+ }
36
+ )
37
+ end
38
+
39
+ describe "#detailed_events" do
40
+ subject { slow.send(:detailed_events) }
41
+ before do
42
+ slow.stub(
43
+ :events => [event],
44
+ :format => :foo
45
+ )
46
+ end
47
+
48
+ it { should == [:foo] }
49
+ end
50
+
51
+ describe "#format" do
52
+ subject { slow.send(:format, event) }
53
+ before { slow.stub(:sanitized_event_payload => :sanitized) }
54
+
55
+ it { should == {
56
+ :name => 'Startled',
57
+ :duration => 2,
58
+ :time => start_time.to_f,
59
+ :end => end_time.to_f,
60
+ :payload => :sanitized
61
+ } }
62
+ end
63
+ end
64
+
65
+ describe "#basic_process_action_event" do
66
+ subject { slow.send(:basic_process_action_event) }
67
+
68
+ it "should return a hash with extra keys" do
69
+ subject[:environment].should == {
70
+ "HTTP_USER_AGENT" => "IE6",
71
+ "SERVER_NAME" => "localhost"
72
+ }
73
+ subject[:session_data].should == {}
74
+ end
75
+ end
76
+ end