appsignal 3.0.0.beta.1 → 3.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +1 -1
  3. data/.semaphore/semaphore.yml +88 -88
  4. data/CHANGELOG.md +41 -1
  5. data/Rakefile +12 -4
  6. data/appsignal.gemspec +7 -5
  7. data/build_matrix.yml +11 -11
  8. data/ext/agent.yml +17 -17
  9. data/gemfiles/no_dependencies.gemfile +0 -7
  10. data/lib/appsignal.rb +1 -2
  11. data/lib/appsignal/config.rb +1 -1
  12. data/lib/appsignal/extension.rb +50 -0
  13. data/lib/appsignal/helpers/instrumentation.rb +69 -5
  14. data/lib/appsignal/hooks.rb +16 -0
  15. data/lib/appsignal/hooks/action_cable.rb +10 -2
  16. data/lib/appsignal/hooks/sidekiq.rb +9 -142
  17. data/lib/appsignal/integrations/object.rb +21 -43
  18. data/lib/appsignal/integrations/railtie.rb +0 -4
  19. data/lib/appsignal/integrations/sidekiq.rb +171 -0
  20. data/lib/appsignal/minutely.rb +6 -0
  21. data/lib/appsignal/transaction.rb +2 -2
  22. data/lib/appsignal/version.rb +1 -1
  23. data/spec/lib/appsignal/config_spec.rb +2 -0
  24. data/spec/lib/appsignal/extension_install_failure_spec.rb +0 -7
  25. data/spec/lib/appsignal/extension_spec.rb +43 -9
  26. data/spec/lib/appsignal/hooks/action_cable_spec.rb +88 -0
  27. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +60 -458
  28. data/spec/lib/appsignal/hooks_spec.rb +41 -0
  29. data/spec/lib/appsignal/integrations/object_spec.rb +91 -4
  30. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +524 -0
  31. data/spec/lib/appsignal/transaction_spec.rb +17 -0
  32. data/spec/lib/appsignal/utils/data_spec.rb +133 -87
  33. data/spec/lib/appsignal_spec.rb +162 -47
  34. data/spec/lib/puma/appsignal_spec.rb +28 -0
  35. data/spec/spec_helper.rb +22 -0
  36. data/spec/support/testing.rb +11 -1
  37. metadata +9 -8
  38. data/gemfiles/rails-4.0.gemfile +0 -6
  39. data/gemfiles/rails-4.1.gemfile +0 -6
@@ -246,6 +246,23 @@ describe Appsignal::Transaction do
246
246
  expect(transaction.ext).to_not be_nil
247
247
  end
248
248
 
249
+ context "when extension is not loaded", :extension_installation_failure do
250
+ around do |example|
251
+ Appsignal::Testing.without_testing { example.run }
252
+ end
253
+
254
+ it "does not error on missing extension method calls" do
255
+ expect(transaction.ext).to be_kind_of(Appsignal::Extension::MockTransaction)
256
+ transaction.start_event
257
+ transaction.finish_event(
258
+ "name",
259
+ "title",
260
+ "body",
261
+ Appsignal::EventFormatter::DEFAULT
262
+ )
263
+ end
264
+ end
265
+
249
266
  it "sets the transaction id" do
250
267
  expect(transaction.transaction_id).to eq "1"
251
268
  end
@@ -4,110 +4,156 @@ describe Appsignal::Utils::Data do
4
4
  describe ".generate" do
5
5
  subject { Appsignal::Utils::Data.generate(body) }
6
6
 
7
- context "with a valid hash body" do
8
- let(:body) do
9
- {
10
- "the" => "payload",
11
- "int" => 1, # Fixnum
12
- "int61" => 1 << 61, # Fixnum
13
- "int62" => 1 << 62, # Bignum, this one still works
14
- "int63" => 1 << 63, # Bignum, turnover point for C, too big for long
15
- "int64" => 1 << 64, # Bignum
16
- "float" => 1.0,
17
- 1 => true,
18
- nil => "test",
19
- :foo => [1, 2, "three", { "foo" => "bar" }],
20
- "bar" => nil,
21
- "baz" => { "foo" => "bʊr", "arr" => [1, 2] }
22
- }
7
+ context "when extension is not loaded", :extension_installation_failure do
8
+ around do |example|
9
+ Appsignal::Testing.without_testing { example.run }
23
10
  end
24
11
 
25
- it { is_expected.to eq Appsignal::Utils::Data.generate(body) }
26
- it { is_expected.to_not eq Appsignal::Utils::Data.generate({}) }
27
-
28
- describe "#to_s" do
29
- it "returns a serialized hash" do
30
- expect(subject.to_s).to eq %({"":"test",) +
31
- %("1":true,) +
32
- %("bar":null,) +
33
- %("baz":{"arr":[1,2],"foo":"bʊr"},) +
34
- %("float":1.0,) +
35
- %("foo":[1,2,"three",{"foo":"bar"}],) +
36
- %("int":1,) +
37
- %("int61":#{1 << 61},) +
38
- %("int62":#{1 << 62},) +
39
- %("int63":"bigint:#{1 << 63}",) +
40
- %("int64":"bigint:#{1 << 64}",) +
41
- %("the":"payload"})
12
+ context "with valid hash body" do
13
+ let(:body) { hash_body }
14
+
15
+ it "does not error and returns MockData class" do
16
+ expect(subject).to be_kind_of(Appsignal::Extension::MockData)
17
+ expect(subject.to_s).to eql("{}")
42
18
  end
43
19
  end
44
- end
45
20
 
46
- context "with a valid array body" do
47
- let(:body) do
48
- [
49
- nil,
50
- true,
51
- false,
52
- "string",
53
- 1, # Fixnum
54
- 1.0, # Float
55
- 1 << 61, # Fixnum
56
- 1 << 62, # Bignum, this one still works
57
- 1 << 63, # Bignum, turnover point for C, too big for long
58
- 1 << 64, # Bignum
59
- { "arr" => [1, 2, "three"], "foo" => "bʊr" }
60
- ]
21
+ context "with valid array body" do
22
+ let(:body) { array_body }
23
+
24
+ it "does not error and returns MockData class" do
25
+ expect(subject).to be_kind_of(Appsignal::Extension::MockData)
26
+ expect(subject.to_s).to eql("{}")
27
+ end
61
28
  end
62
29
 
63
- it { is_expected.to eq Appsignal::Utils::Data.generate(body) }
64
- it { is_expected.to_not eq Appsignal::Utils::Data.generate({}) }
65
-
66
- describe "#to_s" do
67
- it "returns a serialized array" do
68
- expect(subject.to_s).to eq %([null,) +
69
- %(true,) +
70
- %(false,) +
71
- %(\"string\",) +
72
- %(1,) +
73
- %(1.0,) +
74
- %(#{1 << 61},) +
75
- %(#{1 << 62},) +
76
- %("bigint:#{1 << 63}",) +
77
- %("bigint:#{1 << 64}",) +
78
- %({\"arr\":[1,2,\"three\"],\"foo\":\"bʊr\"}])
30
+ context "with an invalid body" do
31
+ let(:body) { "body" }
32
+
33
+ it "raise a type error" do
34
+ expect do
35
+ subject
36
+ end.to raise_error TypeError
79
37
  end
80
38
  end
81
39
  end
82
40
 
83
- context "with a body that contains strings with invalid utf-8 content" do
84
- let(:string_with_invalid_utf8) { [0x61, 0x61, 0x85].pack("c*") }
85
- let(:body) do
86
- {
87
- "field_one" => [0x61, 0x61].pack("c*"),
88
- :field_two => string_with_invalid_utf8,
89
- "field_three" => [
90
- "one", string_with_invalid_utf8
91
- ],
92
- "field_four" => {
93
- "one" => string_with_invalid_utf8
94
- }
95
- }
41
+ context "when extension is loaded" do
42
+ context "with a valid hash body" do
43
+ let(:body) { hash_body }
44
+
45
+ it "returns a valid Data object" do
46
+ is_expected.to eq Appsignal::Utils::Data.generate(body)
47
+ is_expected.to_not eq Appsignal::Utils::Data.generate({})
48
+ end
49
+
50
+ describe "#to_s" do
51
+ it "returns a serialized hash" do
52
+ expect(subject.to_s).to eq %({"":"test",) +
53
+ %("1":true,) +
54
+ %("bar":null,) +
55
+ %("baz":{"arr":[1,2],"foo":"bʊr"},) +
56
+ %("float":1.0,) +
57
+ %("foo":[1,2,"three",{"foo":"bar"}],) +
58
+ %("int":1,) +
59
+ %("int61":#{1 << 61},) +
60
+ %("int62":#{1 << 62},) +
61
+ %("int63":"bigint:#{1 << 63}",) +
62
+ %("int64":"bigint:#{1 << 64}",) +
63
+ %("the":"payload"})
64
+ end
65
+ end
66
+ end
67
+
68
+ context "with a valid array body" do
69
+ let(:body) { array_body }
70
+
71
+ it "returns a valid Data object" do
72
+ is_expected.to eq Appsignal::Utils::Data.generate(body)
73
+ is_expected.to_not eq Appsignal::Utils::Data.generate({})
74
+ end
75
+
76
+ describe "#to_s" do
77
+ it "returns a serialized array" do
78
+ expect(subject.to_s).to eq %([null,) +
79
+ %(true,) +
80
+ %(false,) +
81
+ %(\"string\",) +
82
+ %(1,) +
83
+ %(1.0,) +
84
+ %(#{1 << 61},) +
85
+ %(#{1 << 62},) +
86
+ %("bigint:#{1 << 63}",) +
87
+ %("bigint:#{1 << 64}",) +
88
+ %({\"arr\":[1,2,\"three\"],\"foo\":\"bʊr\"}])
89
+ end
90
+ end
96
91
  end
97
92
 
98
- describe "#to_s" do
99
- it { expect(subject.to_s).to eq %({"field_four":{"one":"aa�"},"field_one":"aa","field_three":["one","aa�"],"field_two":"aa�"}) }
93
+ context "with a body that contains strings with invalid utf-8 content" do
94
+ let(:string_with_invalid_utf8) { [0x61, 0x61, 0x85].pack("c*") }
95
+ let(:body) do
96
+ {
97
+ "field_one" => [0x61, 0x61].pack("c*"),
98
+ :field_two => string_with_invalid_utf8,
99
+ "field_three" => [
100
+ "one", string_with_invalid_utf8
101
+ ],
102
+ "field_four" => {
103
+ "one" => string_with_invalid_utf8
104
+ }
105
+ }
106
+ end
107
+
108
+ describe "#to_s" do
109
+ it "returns a JSON representation in a String" do
110
+ expect(subject.to_s).to eq %({"field_four":{"one":"aa�"},"field_one":"aa","field_three":["one","aa�"],"field_two":"aa�"})
111
+ end
112
+ end
100
113
  end
101
- end
102
114
 
103
- context "with an invalid body" do
104
- let(:body) { "body" }
115
+ context "with an invalid body" do
116
+ let(:body) { "body" }
105
117
 
106
- it "should raise a type error" do
107
- expect do
108
- subject
109
- end.to raise_error TypeError
118
+ it "raises a type error" do
119
+ expect do
120
+ subject
121
+ end.to raise_error TypeError
122
+ end
110
123
  end
111
124
  end
112
125
  end
126
+
127
+ def hash_body
128
+ {
129
+ "the" => "payload",
130
+ "int" => 1, # Fixnum
131
+ "int61" => 1 << 61, # Fixnum
132
+ "int62" => 1 << 62, # Bignum, this one still works
133
+ "int63" => 1 << 63, # Bignum, turnover point for C, too big for long
134
+ "int64" => 1 << 64, # Bignum
135
+ "float" => 1.0,
136
+ 1 => true,
137
+ nil => "test",
138
+ :foo => [1, 2, "three", { "foo" => "bar" }],
139
+ "bar" => nil,
140
+ "baz" => { "foo" => "bʊr", "arr" => [1, 2] }
141
+ }
142
+ end
143
+
144
+ def array_body
145
+ [
146
+ nil,
147
+ true,
148
+ false,
149
+ "string",
150
+ 1, # Fixnum
151
+ 1.0, # Float
152
+ 1 << 61, # Fixnum
153
+ 1 << 62, # Bignum, this one still works
154
+ 1 << 63, # Bignum, turnover point for C, too big for long
155
+ 1 << 64, # Bignum
156
+ { "arr" => [1, 2, "three"], "foo" => "bʊr" }
157
+ ]
158
+ end
113
159
  end
@@ -686,6 +686,9 @@ describe Appsignal do
686
686
  )
687
687
  end
688
688
  let(:error) { ExampleException.new }
689
+ let(:err_stream) { std_stream }
690
+ let(:stderr) { err_stream.read }
691
+ around { |example| keep_transactions { example.run } }
689
692
 
690
693
  it "sends the error to AppSignal" do
691
694
  expect(Appsignal::Transaction).to receive(:new).with(
@@ -719,30 +722,60 @@ describe Appsignal do
719
722
 
720
723
  context "with tags" do
721
724
  let(:tags) { { :a => "a", :b => "b" } }
722
- before do
723
- allow(Appsignal::Transaction).to receive(:new).and_return(transaction)
724
- end
725
725
 
726
- it "tags the request before sending it" do
727
- expect(transaction).to receive(:set_tags).with(tags).and_call_original
728
- expect(transaction).to receive(:complete)
726
+ it "prints a deprecation warning and tags the transaction" do
727
+ logs = capture_logs do
728
+ expect do
729
+ capture_std_streams(std_stream, err_stream) do
730
+ Appsignal.send_error(error, tags)
731
+ end
732
+ end.to change { created_transactions.count }.by(1)
733
+ end
734
+
735
+ transaction = last_transaction
736
+ transaction_hash = transaction.to_h
737
+ expect(transaction_hash).to include(
738
+ "sample_data" => hash_including(
739
+ "tags" => { "a" => "a", "b" => "b" }
740
+ )
741
+ )
729
742
 
730
- Appsignal.send_error(error, tags)
743
+ message = "The tags argument for `Appsignal.send_error` is deprecated. " \
744
+ "Please use the block method to set tags instead.\n\n" \
745
+ " Appsignal.send_error(error) do |transaction|\n" \
746
+ " transaction.set_tags(#{tags.inspect})\n" \
747
+ " end\n\n" \
748
+ "Appsignal.send_error called on location: #{__FILE__}:"
749
+ expect(stderr).to include("appsignal WARNING: #{message}")
750
+ expect(logs).to include(message)
731
751
  end
732
752
  end
733
753
 
734
754
  context "with namespace" do
735
755
  let(:namespace) { "admin" }
736
756
 
737
- it "sets the namespace on the transaction" do
738
- expect(Appsignal::Transaction).to receive(:new).with(
739
- kind_of(String),
740
- "admin",
741
- kind_of(Appsignal::Transaction::GenericRequest)
742
- ).and_call_original
743
- end
757
+ it "prints a deprecation warning and sets the namespace on the transaction" do
758
+ logs = capture_logs do
759
+ expect do
760
+ capture_std_streams(std_stream, err_stream) do
761
+ Appsignal.send_error(error, nil, namespace)
762
+ end
763
+ end.to change { created_transactions.count }.by(1)
764
+ end
765
+
766
+ transaction = last_transaction
767
+ transaction_hash = transaction.to_h
768
+ expect(transaction_hash).to include("namespace" => namespace)
744
769
 
745
- after { Appsignal.send_error(error, nil, namespace) }
770
+ message = "The namespace argument for `Appsignal.send_error` is deprecated. " \
771
+ "Please use the block method to set the namespace instead.\n\n" \
772
+ " Appsignal.send_error(error) do |transaction|\n" \
773
+ " transaction.set_namespace(#{namespace.inspect})\n" \
774
+ " end\n\n" \
775
+ "Appsignal.send_error called on location: #{__FILE__}:"
776
+ expect(stderr).to include("appsignal WARNING: #{message}")
777
+ expect(logs).to include(message)
778
+ end
746
779
  end
747
780
 
748
781
  context "when given a block" do
@@ -769,53 +802,85 @@ describe Appsignal do
769
802
  end
770
803
 
771
804
  describe ".listen_for_error" do
805
+ around { |example| keep_transactions { example.run } }
806
+
772
807
  it "records the error and re-raise it" do
773
- expect(Appsignal).to receive(:send_error).with(
774
- kind_of(ExampleException),
775
- nil,
776
- Appsignal::Transaction::HTTP_REQUEST
777
- )
778
808
  expect do
779
- Appsignal.listen_for_error do
780
- raise ExampleException, "I am an exception"
781
- end
782
- end.to raise_error(ExampleException, "I am an exception")
809
+ expect do
810
+ Appsignal.listen_for_error do
811
+ raise ExampleException, "I am an exception"
812
+ end
813
+ end.to raise_error(ExampleException, "I am an exception")
814
+ end.to change { created_transactions.count }.by(1)
815
+
816
+ expect(last_transaction.to_h).to include(
817
+ "error" => {
818
+ "name" => "ExampleException",
819
+ "message" => "I am an exception",
820
+ "backtrace" => kind_of(String)
821
+ },
822
+ "namespace" => Appsignal::Transaction::HTTP_REQUEST, # Default namespace
823
+ "sample_data" => hash_including(
824
+ "tags" => {}
825
+ )
826
+ )
783
827
  end
784
828
 
785
829
  context "with tags" do
786
830
  it "adds tags to the transaction" do
787
- expect(Appsignal).to receive(:send_error).with(
788
- kind_of(ExampleException),
789
- { "foo" => "bar" },
790
- Appsignal::Transaction::HTTP_REQUEST
791
- )
792
831
  expect do
793
- Appsignal.listen_for_error("foo" => "bar") do
794
- raise ExampleException, "I am an exception"
795
- end
796
- end.to raise_error(ExampleException, "I am an exception")
832
+ expect do
833
+ Appsignal.listen_for_error("foo" => "bar") do
834
+ raise ExampleException, "I am an exception"
835
+ end
836
+ end.to raise_error(ExampleException, "I am an exception")
837
+ end.to change { created_transactions.count }.by(1)
838
+
839
+ expect(last_transaction.to_h).to include(
840
+ "error" => {
841
+ "name" => "ExampleException",
842
+ "message" => "I am an exception",
843
+ "backtrace" => kind_of(String)
844
+ },
845
+ "namespace" => Appsignal::Transaction::HTTP_REQUEST, # Default namespace
846
+ "sample_data" => hash_including(
847
+ "tags" => { "foo" => "bar" }
848
+ )
849
+ )
797
850
  end
798
851
  end
799
852
 
800
853
  context "with a custom namespace" do
801
854
  it "adds the namespace to the transaction" do
802
- expect(Appsignal).to receive(:send_error).with(
803
- kind_of(ExampleException),
804
- nil,
805
- "custom_namespace"
806
- )
807
855
  expect do
808
- Appsignal.listen_for_error(nil, "custom_namespace") do
809
- raise ExampleException, "I am an exception"
810
- end
811
- end.to raise_error(ExampleException, "I am an exception")
856
+ expect do
857
+ Appsignal.listen_for_error(nil, "custom_namespace") do
858
+ raise ExampleException, "I am an exception"
859
+ end
860
+ end.to raise_error(ExampleException, "I am an exception")
861
+ end.to change { created_transactions.count }.by(1)
862
+
863
+ expect(last_transaction.to_h).to include(
864
+ "error" => {
865
+ "name" => "ExampleException",
866
+ "message" => "I am an exception",
867
+ "backtrace" => kind_of(String)
868
+ },
869
+ "namespace" => "custom_namespace",
870
+ "sample_data" => hash_including(
871
+ "tags" => {}
872
+ )
873
+ )
812
874
  end
813
875
  end
814
876
  end
815
877
 
816
878
  describe ".set_error" do
879
+ let(:err_stream) { std_stream }
880
+ let(:stderr) { err_stream.read }
881
+ let(:error) { ExampleException.new("I am an exception") }
817
882
  before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) }
818
- let(:error) { RuntimeError.new("I am an exception") }
883
+ around { |example| keep_transactions { example.run } }
819
884
 
820
885
  context "when there is an active transaction" do
821
886
  it "adds the error to the active transaction" do
@@ -845,24 +910,74 @@ describe Appsignal do
845
910
  context "with tags" do
846
911
  let(:tags) { { "foo" => "bar" } }
847
912
 
848
- it "sets the tags on the transaction" do
913
+ it "prints a deprecation warning and tags the transaction" do
849
914
  expect(transaction).to receive(:set_error).with(error)
850
915
  expect(transaction).to receive(:set_tags).with(tags)
851
916
  expect(transaction).to_not receive(:set_namespace)
852
917
 
853
- Appsignal.set_error(error, tags)
918
+ logs = capture_logs do
919
+ capture_std_streams(std_stream, err_stream) do
920
+ Appsignal.set_error(error, tags)
921
+ end
922
+ end
923
+
924
+ message = "The tags argument for `Appsignal.set_error` is deprecated. " \
925
+ "Please use the block method to set tags instead.\n\n" \
926
+ " Appsignal.set_error(error) do |transaction|\n" \
927
+ " transaction.set_tags(#{tags.inspect})\n" \
928
+ " end\n\n" \
929
+ "Appsignal.set_error called on location: #{__FILE__}:"
930
+ expect(stderr).to include("appsignal WARNING: #{message}")
931
+ expect(logs).to include(message)
854
932
  end
855
933
  end
856
934
 
857
935
  context "with namespace" do
858
936
  let(:namespace) { "admin" }
859
937
 
860
- it "sets the namespace on the transaction" do
938
+ it "prints a deprecation warning andsets the namespace on the transaction" do
861
939
  expect(transaction).to receive(:set_error).with(error)
862
940
  expect(transaction).to_not receive(:set_tags)
863
941
  expect(transaction).to receive(:set_namespace).with(namespace)
864
942
 
865
- Appsignal.set_error(error, nil, namespace)
943
+ logs = capture_logs do
944
+ capture_std_streams(std_stream, err_stream) do
945
+ Appsignal.set_error(error, nil, namespace)
946
+ end
947
+ end
948
+
949
+ message = "The namespace argument for `Appsignal.set_error` is deprecated. " \
950
+ "Please use the block method to set the namespace instead.\n\n" \
951
+ " Appsignal.set_error(error) do |transaction|\n" \
952
+ " transaction.set_namespace(#{namespace.inspect})\n" \
953
+ " end\n\n" \
954
+ "Appsignal.set_error called on location: #{__FILE__}:"
955
+ expect(stderr).to include("appsignal WARNING: #{message}")
956
+ expect(logs).to include(message)
957
+ end
958
+ end
959
+
960
+ context "when given a block" do
961
+ it "yields the transaction and allows additional metadata to be set" do
962
+ captured_transaction = nil
963
+ keep_transactions do
964
+ Appsignal.set_error(StandardError.new("my_error")) do |transaction|
965
+ captured_transaction = transaction
966
+ transaction.set_action("my_action")
967
+ transaction.set_namespace("my_namespace")
968
+ end
969
+ end
970
+
971
+ expect(transaction).to eql(captured_transaction)
972
+ expect(captured_transaction.to_h).to include(
973
+ "namespace" => "my_namespace",
974
+ "action" => "my_action",
975
+ "error" => {
976
+ "name" => "StandardError",
977
+ "message" => "my_error",
978
+ "backtrace" => kind_of(String)
979
+ }
980
+ )
866
981
  end
867
982
  end
868
983
  end