appsignal 2.5.0.alpha.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (211) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.rspec +4 -0
  4. data/.rubocop.yml +66 -0
  5. data/.rubocop_todo.yml +124 -0
  6. data/.travis.yml +72 -0
  7. data/.yardopts +8 -0
  8. data/CHANGELOG.md +639 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE +20 -0
  11. data/README.md +264 -0
  12. data/Rakefile +214 -0
  13. data/appsignal.gemspec +42 -0
  14. data/benchmark.rake +77 -0
  15. data/bin/appsignal +13 -0
  16. data/ext/Rakefile +27 -0
  17. data/ext/agent.yml +64 -0
  18. data/ext/appsignal_extension.c +692 -0
  19. data/ext/base.rb +79 -0
  20. data/ext/extconf.rb +35 -0
  21. data/gemfiles/capistrano2.gemfile +7 -0
  22. data/gemfiles/capistrano3.gemfile +7 -0
  23. data/gemfiles/grape.gemfile +7 -0
  24. data/gemfiles/no_dependencies.gemfile +5 -0
  25. data/gemfiles/padrino.gemfile +7 -0
  26. data/gemfiles/que.gemfile +5 -0
  27. data/gemfiles/rails-3.2.gemfile +6 -0
  28. data/gemfiles/rails-4.0.gemfile +6 -0
  29. data/gemfiles/rails-4.1.gemfile +6 -0
  30. data/gemfiles/rails-4.2.gemfile +10 -0
  31. data/gemfiles/rails-5.0.gemfile +5 -0
  32. data/gemfiles/rails-5.1.gemfile +5 -0
  33. data/gemfiles/resque.gemfile +12 -0
  34. data/gemfiles/sequel-435.gemfile +11 -0
  35. data/gemfiles/sequel.gemfile +11 -0
  36. data/gemfiles/sinatra.gemfile +6 -0
  37. data/gemfiles/webmachine.gemfile +5 -0
  38. data/lib/appsignal.rb +804 -0
  39. data/lib/appsignal/auth_check.rb +65 -0
  40. data/lib/appsignal/capistrano.rb +10 -0
  41. data/lib/appsignal/cli.rb +108 -0
  42. data/lib/appsignal/cli/demo.rb +63 -0
  43. data/lib/appsignal/cli/diagnose.rb +500 -0
  44. data/lib/appsignal/cli/helpers.rb +72 -0
  45. data/lib/appsignal/cli/install.rb +277 -0
  46. data/lib/appsignal/cli/notify_of_deploy.rb +113 -0
  47. data/lib/appsignal/config.rb +287 -0
  48. data/lib/appsignal/demo.rb +107 -0
  49. data/lib/appsignal/event_formatter.rb +74 -0
  50. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +24 -0
  51. data/lib/appsignal/event_formatter/active_record/instantiation_formatter.rb +14 -0
  52. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +14 -0
  53. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +32 -0
  54. data/lib/appsignal/event_formatter/faraday/request_formatter.rb +19 -0
  55. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +89 -0
  56. data/lib/appsignal/event_formatter/moped/query_formatter.rb +80 -0
  57. data/lib/appsignal/extension.rb +63 -0
  58. data/lib/appsignal/extension/jruby.rb +460 -0
  59. data/lib/appsignal/garbage_collection_profiler.rb +48 -0
  60. data/lib/appsignal/hooks.rb +105 -0
  61. data/lib/appsignal/hooks/action_cable.rb +113 -0
  62. data/lib/appsignal/hooks/active_support_notifications.rb +52 -0
  63. data/lib/appsignal/hooks/celluloid.rb +30 -0
  64. data/lib/appsignal/hooks/data_mapper.rb +18 -0
  65. data/lib/appsignal/hooks/delayed_job.rb +19 -0
  66. data/lib/appsignal/hooks/mongo_ruby_driver.rb +21 -0
  67. data/lib/appsignal/hooks/net_http.rb +29 -0
  68. data/lib/appsignal/hooks/passenger.rb +22 -0
  69. data/lib/appsignal/hooks/puma.rb +35 -0
  70. data/lib/appsignal/hooks/que.rb +21 -0
  71. data/lib/appsignal/hooks/rake.rb +39 -0
  72. data/lib/appsignal/hooks/redis.rb +30 -0
  73. data/lib/appsignal/hooks/sequel.rb +60 -0
  74. data/lib/appsignal/hooks/shoryuken.rb +43 -0
  75. data/lib/appsignal/hooks/sidekiq.rb +144 -0
  76. data/lib/appsignal/hooks/unicorn.rb +40 -0
  77. data/lib/appsignal/hooks/webmachine.rb +23 -0
  78. data/lib/appsignal/integrations/capistrano/appsignal.cap +39 -0
  79. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +52 -0
  80. data/lib/appsignal/integrations/data_mapper.rb +33 -0
  81. data/lib/appsignal/integrations/delayed_job_plugin.rb +54 -0
  82. data/lib/appsignal/integrations/grape.rb +53 -0
  83. data/lib/appsignal/integrations/mongo_ruby_driver.rb +55 -0
  84. data/lib/appsignal/integrations/object.rb +35 -0
  85. data/lib/appsignal/integrations/padrino.rb +84 -0
  86. data/lib/appsignal/integrations/que.rb +43 -0
  87. data/lib/appsignal/integrations/railtie.rb +41 -0
  88. data/lib/appsignal/integrations/rake.rb +2 -0
  89. data/lib/appsignal/integrations/resque.rb +20 -0
  90. data/lib/appsignal/integrations/resque_active_job.rb +30 -0
  91. data/lib/appsignal/integrations/sinatra.rb +17 -0
  92. data/lib/appsignal/integrations/webmachine.rb +38 -0
  93. data/lib/appsignal/js_exception_transaction.rb +54 -0
  94. data/lib/appsignal/marker.rb +63 -0
  95. data/lib/appsignal/minutely.rb +42 -0
  96. data/lib/appsignal/rack/generic_instrumentation.rb +49 -0
  97. data/lib/appsignal/rack/js_exception_catcher.rb +70 -0
  98. data/lib/appsignal/rack/rails_instrumentation.rb +51 -0
  99. data/lib/appsignal/rack/sinatra_instrumentation.rb +99 -0
  100. data/lib/appsignal/rack/streaming_listener.rb +73 -0
  101. data/lib/appsignal/system.rb +81 -0
  102. data/lib/appsignal/transaction.rb +498 -0
  103. data/lib/appsignal/transmitter.rb +107 -0
  104. data/lib/appsignal/utils.rb +127 -0
  105. data/lib/appsignal/utils/params_sanitizer.rb +59 -0
  106. data/lib/appsignal/utils/query_params_sanitizer.rb +55 -0
  107. data/lib/appsignal/version.rb +3 -0
  108. data/lib/sequel/extensions/appsignal_integration.rb +3 -0
  109. data/resources/appsignal.yml.erb +39 -0
  110. data/resources/cacert.pem +3866 -0
  111. data/spec/.rubocop.yml +7 -0
  112. data/spec/lib/appsignal/auth_check_spec.rb +80 -0
  113. data/spec/lib/appsignal/capistrano2_spec.rb +224 -0
  114. data/spec/lib/appsignal/capistrano3_spec.rb +237 -0
  115. data/spec/lib/appsignal/cli/demo_spec.rb +67 -0
  116. data/spec/lib/appsignal/cli/diagnose_spec.rb +988 -0
  117. data/spec/lib/appsignal/cli/helpers_spec.rb +171 -0
  118. data/spec/lib/appsignal/cli/install_spec.rb +632 -0
  119. data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +168 -0
  120. data/spec/lib/appsignal/cli_spec.rb +56 -0
  121. data/spec/lib/appsignal/config_spec.rb +637 -0
  122. data/spec/lib/appsignal/demo_spec.rb +87 -0
  123. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +44 -0
  124. data/spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb +21 -0
  125. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +21 -0
  126. data/spec/lib/appsignal/event_formatter/elastic_search/search_formatter_spec.rb +52 -0
  127. data/spec/lib/appsignal/event_formatter/faraday/request_formatter_spec.rb +21 -0
  128. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +113 -0
  129. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +112 -0
  130. data/spec/lib/appsignal/event_formatter_spec.rb +100 -0
  131. data/spec/lib/appsignal/extension/jruby_spec.rb +43 -0
  132. data/spec/lib/appsignal/extension_spec.rb +137 -0
  133. data/spec/lib/appsignal/garbage_collection_profiler_spec.rb +66 -0
  134. data/spec/lib/appsignal/hooks/action_cable_spec.rb +370 -0
  135. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +92 -0
  136. data/spec/lib/appsignal/hooks/celluloid_spec.rb +35 -0
  137. data/spec/lib/appsignal/hooks/data_mapper_spec.rb +39 -0
  138. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +358 -0
  139. data/spec/lib/appsignal/hooks/mongo_ruby_driver_spec.rb +44 -0
  140. data/spec/lib/appsignal/hooks/net_http_spec.rb +53 -0
  141. data/spec/lib/appsignal/hooks/passenger_spec.rb +30 -0
  142. data/spec/lib/appsignal/hooks/puma_spec.rb +80 -0
  143. data/spec/lib/appsignal/hooks/que_spec.rb +19 -0
  144. data/spec/lib/appsignal/hooks/rake_spec.rb +73 -0
  145. data/spec/lib/appsignal/hooks/redis_spec.rb +55 -0
  146. data/spec/lib/appsignal/hooks/sequel_spec.rb +46 -0
  147. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +192 -0
  148. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +419 -0
  149. data/spec/lib/appsignal/hooks/unicorn_spec.rb +52 -0
  150. data/spec/lib/appsignal/hooks/webmachine_spec.rb +35 -0
  151. data/spec/lib/appsignal/hooks_spec.rb +195 -0
  152. data/spec/lib/appsignal/integrations/data_mapper_spec.rb +65 -0
  153. data/spec/lib/appsignal/integrations/grape_spec.rb +225 -0
  154. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +127 -0
  155. data/spec/lib/appsignal/integrations/object_spec.rb +249 -0
  156. data/spec/lib/appsignal/integrations/padrino_spec.rb +323 -0
  157. data/spec/lib/appsignal/integrations/que_spec.rb +174 -0
  158. data/spec/lib/appsignal/integrations/railtie_spec.rb +129 -0
  159. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +83 -0
  160. data/spec/lib/appsignal/integrations/resque_spec.rb +92 -0
  161. data/spec/lib/appsignal/integrations/sinatra_spec.rb +73 -0
  162. data/spec/lib/appsignal/integrations/webmachine_spec.rb +69 -0
  163. data/spec/lib/appsignal/js_exception_transaction_spec.rb +128 -0
  164. data/spec/lib/appsignal/marker_spec.rb +51 -0
  165. data/spec/lib/appsignal/minutely_spec.rb +50 -0
  166. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +90 -0
  167. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +147 -0
  168. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +117 -0
  169. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +213 -0
  170. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +161 -0
  171. data/spec/lib/appsignal/system_spec.rb +131 -0
  172. data/spec/lib/appsignal/transaction_spec.rb +1146 -0
  173. data/spec/lib/appsignal/transmitter_spec.rb +152 -0
  174. data/spec/lib/appsignal/utils/params_sanitizer_spec.rb +136 -0
  175. data/spec/lib/appsignal/utils/query_params_sanitizer_spec.rb +192 -0
  176. data/spec/lib/appsignal/utils_spec.rb +150 -0
  177. data/spec/lib/appsignal_spec.rb +1049 -0
  178. data/spec/spec_helper.rb +116 -0
  179. data/spec/support/fixtures/containers/cgroups/docker +14 -0
  180. data/spec/support/fixtures/containers/cgroups/docker_systemd +8 -0
  181. data/spec/support/fixtures/containers/cgroups/lxc +10 -0
  182. data/spec/support/fixtures/containers/cgroups/no_permission +0 -0
  183. data/spec/support/fixtures/containers/cgroups/none +1 -0
  184. data/spec/support/fixtures/generated_config.yml +24 -0
  185. data/spec/support/fixtures/uploaded_file.txt +0 -0
  186. data/spec/support/helpers/api_request_helper.rb +19 -0
  187. data/spec/support/helpers/cli_helpers.rb +26 -0
  188. data/spec/support/helpers/config_helpers.rb +21 -0
  189. data/spec/support/helpers/dependency_helper.rb +73 -0
  190. data/spec/support/helpers/directory_helper.rb +27 -0
  191. data/spec/support/helpers/env_helpers.rb +33 -0
  192. data/spec/support/helpers/example_exception.rb +13 -0
  193. data/spec/support/helpers/example_standard_error.rb +13 -0
  194. data/spec/support/helpers/log_helpers.rb +22 -0
  195. data/spec/support/helpers/std_streams_helper.rb +66 -0
  196. data/spec/support/helpers/system_helpers.rb +8 -0
  197. data/spec/support/helpers/time_helpers.rb +11 -0
  198. data/spec/support/helpers/transaction_helpers.rb +37 -0
  199. data/spec/support/matchers/contains_log.rb +7 -0
  200. data/spec/support/mocks/fake_gc_profiler.rb +19 -0
  201. data/spec/support/mocks/mock_extension.rb +6 -0
  202. data/spec/support/project_fixture/config/application.rb +0 -0
  203. data/spec/support/project_fixture/config/appsignal.yml +32 -0
  204. data/spec/support/project_fixture/config/environments/development.rb +0 -0
  205. data/spec/support/project_fixture/config/environments/production.rb +0 -0
  206. data/spec/support/project_fixture/config/environments/test.rb +0 -0
  207. data/spec/support/project_fixture/log/.gitkeep +0 -0
  208. data/spec/support/rails/my_app.rb +6 -0
  209. data/spec/support/shared_examples/instrument.rb +43 -0
  210. data/spec/support/stubs/delayed_job.rb +0 -0
  211. metadata +483 -0
@@ -0,0 +1,150 @@
1
+ # encoding: UTF-8
2
+
3
+ describe Appsignal::Utils do
4
+ describe ".data_generate" do
5
+ subject { Appsignal::Utils.data_generate(body) }
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
+ }
23
+ end
24
+
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"})
42
+ end
43
+ end
44
+ end
45
+
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
+ ]
61
+ end
62
+
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\"}])
79
+ end
80
+ end
81
+ end
82
+
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
+ }
96
+ end
97
+
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�"}) }
100
+ end
101
+ end
102
+
103
+ context "with an invalid body" do
104
+ let(:body) { "body" }
105
+
106
+ it "should raise a type error" do
107
+ expect do
108
+ subject
109
+ end.to raise_error TypeError
110
+ end
111
+ end
112
+ end
113
+
114
+ describe ".json_generate" do
115
+ subject { Appsignal::Utils.json_generate(body) }
116
+
117
+ context "with a valid body" do
118
+ let(:body) do
119
+ {
120
+ "the" => "payload",
121
+ 1 => true,
122
+ nil => "test",
123
+ :foo => [1, 2, "three"],
124
+ "bar" => nil,
125
+ "baz" => { "foo" => "bar" }
126
+ }
127
+ end
128
+
129
+ it { is_expected.to eq %({"the":"payload","1":true,"":"test","foo":[1,2,"three"],"bar":null,"baz":{"foo":"bar"}}) }
130
+ end
131
+
132
+ context "with a body that contains strings with invalid utf-8 content" do
133
+ let(:string_with_invalid_utf8) { [0x61, 0x61, 0x85].pack("c*") }
134
+ let(:body) do
135
+ {
136
+ "field_one" => [0x61, 0x61].pack("c*"),
137
+ :field_two => string_with_invalid_utf8,
138
+ "field_three" => [
139
+ "one", string_with_invalid_utf8
140
+ ],
141
+ "field_four" => {
142
+ "one" => string_with_invalid_utf8
143
+ }
144
+ }
145
+ end
146
+
147
+ it { is_expected.to eq %({"field_one":"aa","field_two":"aa�","field_three":["one","aa�"],"field_four":{"one":"aa�"}}) }
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,1049 @@
1
+ describe Appsignal do
2
+ before do
3
+ # Make sure we have a clean state because we want to test
4
+ # initialization here.
5
+ Appsignal.config = nil
6
+ Appsignal.extensions.clear
7
+ end
8
+
9
+ let(:transaction) { http_request_transaction }
10
+
11
+ describe ".config=" do
12
+ it "should set the config" do
13
+ config = project_fixture_config
14
+ expect(Appsignal.logger).to_not receive(:level=)
15
+
16
+ Appsignal.config = config
17
+ expect(Appsignal.config).to eq config
18
+ end
19
+ end
20
+
21
+ describe ".extensions" do
22
+ it "should keep a list of extensions" do
23
+ expect(Appsignal.extensions).to be_empty
24
+ Appsignal.extensions << Appsignal::MockExtension
25
+ expect(Appsignal.extensions.size).to eq(1)
26
+ end
27
+ end
28
+
29
+ describe ".start" do
30
+ context "with no config set beforehand" do
31
+ it "should do nothing when config is not set and there is no valid config in the env" do
32
+ expect(Appsignal.logger).to receive(:error).with(
33
+ "Push api key not set after loading config"
34
+ ).once
35
+ expect(Appsignal.logger).to receive(:error).with(
36
+ "Not starting, no valid config for this environment"
37
+ ).once
38
+ expect(Appsignal::Extension).to_not receive(:start)
39
+ Appsignal.start
40
+ end
41
+
42
+ it "should create a config from the env" do
43
+ ENV["APPSIGNAL_PUSH_API_KEY"] = "something"
44
+ expect(Appsignal::Extension).to receive(:start)
45
+ expect(Appsignal.logger).not_to receive(:error)
46
+ silence { Appsignal.start }
47
+ expect(Appsignal.config[:push_api_key]).to eq("something")
48
+ end
49
+ end
50
+
51
+ context "when config is loaded" do
52
+ before { Appsignal.config = project_fixture_config }
53
+
54
+ it "should initialize logging" do
55
+ Appsignal.start
56
+ expect(Appsignal.logger.level).to eq Logger::INFO
57
+ end
58
+
59
+ it "should start native" do
60
+ expect(Appsignal::Extension).to receive(:start)
61
+ Appsignal.start
62
+ end
63
+
64
+ it "should initialize formatters" do
65
+ expect(Appsignal::EventFormatter).to receive(:initialize_formatters)
66
+ Appsignal.start
67
+ end
68
+
69
+ context "with an extension" do
70
+ before { Appsignal.extensions << Appsignal::MockExtension }
71
+
72
+ it "should call the extension's initializer" do
73
+ expect(Appsignal::MockExtension).to receive(:initializer)
74
+ Appsignal.start
75
+ end
76
+ end
77
+
78
+ context "when allocation tracking and gc instrumentation have been enabled" do
79
+ before do
80
+ allow(GC::Profiler).to receive(:enable)
81
+ Appsignal.config.config_hash[:enable_allocation_tracking] = true
82
+ Appsignal.config.config_hash[:enable_gc_instrumentation] = true
83
+ end
84
+
85
+ it "should enable Ruby's GC::Profiler" do
86
+ expect(GC::Profiler).to receive(:enable)
87
+ Appsignal.start
88
+ end
89
+
90
+ unless Appsignal::System.jruby?
91
+ it "installs the allocation event hook" do
92
+ expect(Appsignal::Extension).to receive(:install_allocation_event_hook)
93
+ Appsignal.start
94
+ end
95
+ end
96
+
97
+ it "should add the gc probe to minutely" do
98
+ expect(Appsignal::Minutely).to receive(:add_gc_probe)
99
+ Appsignal.start
100
+ end
101
+ end
102
+
103
+ context "when allocation tracking and gc instrumentation have been disabled" do
104
+ before do
105
+ Appsignal.config.config_hash[:enable_allocation_tracking] = false
106
+ Appsignal.config.config_hash[:enable_gc_instrumentation] = false
107
+ end
108
+
109
+ it "should not enable Ruby's GC::Profiler" do
110
+ expect(GC::Profiler).not_to receive(:enable)
111
+ Appsignal.start
112
+ end
113
+
114
+ it "should not install the allocation event hook" do
115
+ expect(Appsignal::Minutely).not_to receive(:install_allocation_event_hook)
116
+ Appsignal.start
117
+ end
118
+
119
+ it "should not add the gc probe to minutely" do
120
+ expect(Appsignal::Minutely).not_to receive(:add_gc_probe)
121
+ Appsignal.start
122
+ end
123
+ end
124
+
125
+ context "when minutely metrics has been enabled" do
126
+ before do
127
+ Appsignal.config.config_hash[:enable_minutely_probes] = true
128
+ end
129
+
130
+ it "should start minutely" do
131
+ expect(Appsignal::Minutely).to receive(:start)
132
+ Appsignal.start
133
+ end
134
+ end
135
+
136
+ context "when minutely metrics has been disabled" do
137
+ before do
138
+ Appsignal.config.config_hash[:enable_minutely_probes] = false
139
+ end
140
+
141
+ it "should not start minutely" do
142
+ expect(Appsignal::Minutely).to_not receive(:start)
143
+ Appsignal.start
144
+ end
145
+ end
146
+ end
147
+
148
+ context "with debug logging" do
149
+ before { Appsignal.config = project_fixture_config("test") }
150
+
151
+ it "should change the log level" do
152
+ Appsignal.start
153
+ expect(Appsignal.logger.level).to eq Logger::DEBUG
154
+ end
155
+ end
156
+ end
157
+
158
+ describe ".forked" do
159
+ context "when not active" do
160
+ it "should should do nothing" do
161
+ expect(Appsignal::Extension).to_not receive(:start)
162
+
163
+ Appsignal.forked
164
+ end
165
+ end
166
+
167
+ context "when active" do
168
+ before do
169
+ Appsignal.config = project_fixture_config
170
+ end
171
+
172
+ it "should resubscribe and start the extension" do
173
+ expect(Appsignal).to receive(:start_logger)
174
+ expect(Appsignal::Extension).to receive(:start)
175
+
176
+ Appsignal.forked
177
+ end
178
+ end
179
+ end
180
+
181
+ describe ".stop" do
182
+ it "should call stop on the extension" do
183
+ expect(Appsignal.logger).to receive(:debug).with("Stopping appsignal")
184
+ expect(Appsignal::Extension).to receive(:stop)
185
+ Appsignal.stop
186
+ expect(Appsignal.active?).to be_falsy
187
+ end
188
+
189
+ context "with context specified" do
190
+ it "should log the context" do
191
+ expect(Appsignal.logger).to receive(:debug).with("Stopping appsignal (something)")
192
+ expect(Appsignal::Extension).to receive(:stop)
193
+ Appsignal.stop("something")
194
+ expect(Appsignal.active?).to be_falsy
195
+ end
196
+ end
197
+ end
198
+
199
+ describe ".active?" do
200
+ subject { Appsignal.active? }
201
+
202
+ context "without config" do
203
+ before do
204
+ Appsignal.config = nil
205
+ end
206
+
207
+ it { is_expected.to be_falsy }
208
+ end
209
+
210
+ context "with inactive config" do
211
+ before do
212
+ Appsignal.config = project_fixture_config("nonsense")
213
+ end
214
+
215
+ it { is_expected.to be_falsy }
216
+ end
217
+
218
+ context "with active config" do
219
+ before do
220
+ Appsignal.config = project_fixture_config
221
+ end
222
+
223
+ it { is_expected.to be_truthy }
224
+ end
225
+ end
226
+
227
+ describe ".add_exception" do
228
+ it "should alias this method" do
229
+ expect(Appsignal).to respond_to(:add_exception)
230
+ end
231
+ end
232
+
233
+ describe ".get_server_state" do
234
+ it "should call server state on the extension" do
235
+ expect(Appsignal::Extension).to receive(:get_server_state).with("key")
236
+
237
+ Appsignal.get_server_state("key")
238
+ end
239
+
240
+ it "should get nil by default" do
241
+ expect(Appsignal.get_server_state("key")).to be_nil
242
+ end
243
+ end
244
+
245
+ context "not active" do
246
+ describe ".monitor_transaction" do
247
+ it "should do nothing but still yield the block" do
248
+ expect(Appsignal::Transaction).to_not receive(:create)
249
+ expect(Appsignal).to_not receive(:instrument)
250
+ object = double
251
+ expect(object).to receive(:some_method).and_return(1)
252
+
253
+ expect do
254
+ expect(Appsignal.monitor_transaction("perform_job.nothing") do
255
+ object.some_method
256
+ end).to eq 1
257
+ end.to_not raise_error
258
+ end
259
+ end
260
+
261
+ describe ".listen_for_error" do
262
+ it "does not record anyhing" do
263
+ error = RuntimeError.new("specific error")
264
+ expect do
265
+ Appsignal.listen_for_error do
266
+ raise error
267
+ end
268
+ end.to raise_error(error)
269
+ end
270
+ end
271
+
272
+ describe ".send_error" do
273
+ it "should do nothing" do
274
+ expect do
275
+ Appsignal.send_error(RuntimeError.new)
276
+ end.to_not raise_error
277
+ end
278
+ end
279
+
280
+ describe ".set_error" do
281
+ it "should do nothing" do
282
+ expect do
283
+ Appsignal.set_error(RuntimeError.new)
284
+ end.to_not raise_error
285
+ end
286
+ end
287
+
288
+ describe ".set_namespace" do
289
+ it "should do nothing" do
290
+ expect do
291
+ Appsignal.set_namespace("custom")
292
+ end.to_not raise_error
293
+ end
294
+ end
295
+
296
+ describe ".tag_request" do
297
+ it "should do nothing" do
298
+ expect do
299
+ Appsignal.tag_request(:tag => "tag")
300
+ end.to_not raise_error
301
+ end
302
+ end
303
+ end
304
+
305
+ context "with config and started" do
306
+ before do
307
+ Appsignal.config = project_fixture_config
308
+ Appsignal.start
309
+ end
310
+
311
+ describe ".monitor_transaction" do
312
+ context "with a successful call" do
313
+ it "should instrument and complete for a background job" do
314
+ expect(Appsignal).to receive(:instrument).with(
315
+ "perform_job.something"
316
+ ).and_yield
317
+ expect(Appsignal::Transaction).to receive(:complete_current!)
318
+ object = double
319
+ expect(object).to receive(:some_method).and_return(1)
320
+
321
+ expect(Appsignal.monitor_transaction(
322
+ "perform_job.something",
323
+ background_env_with_data
324
+ ) do
325
+ current = Appsignal::Transaction.current
326
+ expect(current.namespace).to eq Appsignal::Transaction::BACKGROUND_JOB
327
+ expect(current.request).to be_a(Appsignal::Transaction::GenericRequest)
328
+ object.some_method
329
+ end).to eq 1
330
+ end
331
+
332
+ it "should instrument and complete for a http request" do
333
+ expect(Appsignal).to receive(:instrument).with(
334
+ "process_action.something"
335
+ ).and_yield
336
+ expect(Appsignal::Transaction).to receive(:complete_current!)
337
+ object = double
338
+ expect(object).to receive(:some_method)
339
+
340
+ Appsignal.monitor_transaction(
341
+ "process_action.something",
342
+ http_request_env_with_data
343
+ ) do
344
+ current = Appsignal::Transaction.current
345
+ expect(current.namespace).to eq Appsignal::Transaction::HTTP_REQUEST
346
+ expect(current.request).to be_a(::Rack::Request)
347
+ object.some_method
348
+ end
349
+ end
350
+ end
351
+
352
+ context "with an erroring call" do
353
+ let(:error) { ExampleException.new }
354
+
355
+ it "should add the error to the current transaction and complete" do
356
+ expect_any_instance_of(Appsignal::Transaction).to receive(:set_error).with(error)
357
+ expect(Appsignal::Transaction).to receive(:complete_current!)
358
+
359
+ expect do
360
+ Appsignal.monitor_transaction("perform_job.something") do
361
+ raise error
362
+ end
363
+ end.to raise_error(error)
364
+ end
365
+ end
366
+ end
367
+
368
+ describe ".monitor_single_transaction" do
369
+ context "with a successful call" do
370
+ it "should call monitor_transaction and stop" do
371
+ expect(Appsignal).to receive(:monitor_transaction).with(
372
+ "perform_job.something",
373
+ :key => :value
374
+ ).and_yield
375
+ expect(Appsignal).to receive(:stop)
376
+
377
+ Appsignal.monitor_single_transaction("perform_job.something", :key => :value) do
378
+ # nothing
379
+ end
380
+ end
381
+ end
382
+
383
+ context "with an erroring call" do
384
+ let(:error) { ExampleException.new }
385
+
386
+ it "should call monitor_transaction and stop and then raise the error" do
387
+ expect(Appsignal).to receive(:monitor_transaction).with(
388
+ "perform_job.something",
389
+ :key => :value
390
+ ).and_yield
391
+ expect(Appsignal).to receive(:stop)
392
+
393
+ expect do
394
+ Appsignal.monitor_single_transaction("perform_job.something", :key => :value) do
395
+ raise error
396
+ end
397
+ end.to raise_error(error)
398
+ end
399
+ end
400
+ end
401
+
402
+ describe ".tag_request" do
403
+ before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) }
404
+
405
+ context "with transaction" do
406
+ let(:transaction) { double }
407
+ it "should call set_tags on transaction" do
408
+ expect(transaction).to receive(:set_tags).with("a" => "b")
409
+ end
410
+
411
+ after { Appsignal.tag_request("a" => "b") }
412
+ end
413
+
414
+ context "without transaction" do
415
+ let(:transaction) { nil }
416
+
417
+ it "should call set_tags on transaction" do
418
+ expect(Appsignal.tag_request).to be_falsy
419
+ end
420
+ end
421
+
422
+ it "should also listen to tag_job" do
423
+ expect(Appsignal).to respond_to(:tag_job)
424
+ end
425
+ end
426
+
427
+ describe "custom stats" do
428
+ describe ".set_gauge" do
429
+ it "should call set_gauge on the extension with a string key and float" do
430
+ expect(Appsignal::Extension).to receive(:set_gauge).with("key", 0.1)
431
+ Appsignal.set_gauge("key", 0.1)
432
+ end
433
+
434
+ it "should call set_gauge on the extension with a symbol key and int" do
435
+ expect(Appsignal::Extension).to receive(:set_gauge).with("key", 1.0)
436
+ Appsignal.set_gauge(:key, 1)
437
+ end
438
+
439
+ it "should not raise an exception when out of range" do
440
+ expect(Appsignal::Extension).to receive(:set_gauge).with("key", 10).and_raise(RangeError)
441
+ expect(Appsignal.logger).to receive(:warn).with("Gauge value 10 for key 'key' is too big")
442
+ expect do
443
+ Appsignal.set_gauge("key", 10)
444
+ end.to_not raise_error
445
+ end
446
+ end
447
+
448
+ describe ".set_host_gauge" do
449
+ it "should call set_host_gauge on the extension with a string key and float" do
450
+ expect(Appsignal::Extension).to receive(:set_host_gauge).with("key", 0.1)
451
+ Appsignal.set_host_gauge("key", 0.1)
452
+ end
453
+
454
+ it "should call set_host_gauge on the extension with a symbol key and int" do
455
+ expect(Appsignal::Extension).to receive(:set_host_gauge).with("key", 1.0)
456
+ Appsignal.set_host_gauge(:key, 1)
457
+ end
458
+
459
+ it "should not raise an exception when out of range" do
460
+ expect(Appsignal::Extension).to receive(:set_host_gauge).with("key", 10).and_raise(RangeError)
461
+ expect(Appsignal.logger).to receive(:warn).with("Host gauge value 10 for key 'key' is too big")
462
+ expect do
463
+ Appsignal.set_host_gauge("key", 10)
464
+ end.to_not raise_error
465
+ end
466
+ end
467
+
468
+ describe ".set_process_gauge" do
469
+ it "should call set_process_gauge on the extension with a string key and float" do
470
+ expect(Appsignal::Extension).to receive(:set_process_gauge).with("key", 0.1)
471
+ Appsignal.set_process_gauge("key", 0.1)
472
+ end
473
+
474
+ it "should call set_process_gauge on the extension with a symbol key and int" do
475
+ expect(Appsignal::Extension).to receive(:set_process_gauge).with("key", 1.0)
476
+ Appsignal.set_process_gauge(:key, 1)
477
+ end
478
+
479
+ it "should not raise an exception when out of range" do
480
+ expect(Appsignal::Extension).to receive(:set_process_gauge).with("key", 10).and_raise(RangeError)
481
+ expect(Appsignal.logger).to receive(:warn).with("Process gauge value 10 for key 'key' is too big")
482
+ expect do
483
+ Appsignal.set_process_gauge("key", 10)
484
+ end.to_not raise_error
485
+ end
486
+ end
487
+
488
+ describe ".increment_counter" do
489
+ it "should call increment_counter on the extension with a string key" do
490
+ expect(Appsignal::Extension).to receive(:increment_counter).with("key", 1)
491
+ Appsignal.increment_counter("key")
492
+ end
493
+
494
+ it "should call increment_counter on the extension with a symbol key" do
495
+ expect(Appsignal::Extension).to receive(:increment_counter).with("key", 1)
496
+ Appsignal.increment_counter(:key)
497
+ end
498
+
499
+ it "should call increment_counter on the extension with a count" do
500
+ expect(Appsignal::Extension).to receive(:increment_counter).with("key", 5)
501
+ Appsignal.increment_counter("key", 5)
502
+ end
503
+
504
+ it "should not raise an exception when out of range" do
505
+ expect(Appsignal::Extension).to receive(:increment_counter).with("key", 10).and_raise(RangeError)
506
+ expect(Appsignal.logger).to receive(:warn).with("Counter value 10 for key 'key' is too big")
507
+ expect do
508
+ Appsignal.increment_counter("key", 10)
509
+ end.to_not raise_error
510
+ end
511
+ end
512
+
513
+ describe ".add_distribution_value" do
514
+ it "should call add_distribution_value on the extension with a string key and float" do
515
+ expect(Appsignal::Extension).to receive(:add_distribution_value).with("key", 0.1)
516
+ Appsignal.add_distribution_value("key", 0.1)
517
+ end
518
+
519
+ it "should call add_distribution_value on the extension with a symbol key and int" do
520
+ expect(Appsignal::Extension).to receive(:add_distribution_value).with("key", 1.0)
521
+ Appsignal.add_distribution_value(:key, 1)
522
+ end
523
+
524
+ it "should not raise an exception when out of range" do
525
+ expect(Appsignal::Extension).to receive(:add_distribution_value).with("key", 10).and_raise(RangeError)
526
+ expect(Appsignal.logger).to receive(:warn).with("Distribution value 10 for key 'key' is too big")
527
+ expect do
528
+ Appsignal.add_distribution_value("key", 10)
529
+ end.to_not raise_error
530
+ end
531
+ end
532
+ end
533
+
534
+ describe ".logger" do
535
+ subject { Appsignal.logger }
536
+
537
+ it { is_expected.to be_a Logger }
538
+ end
539
+
540
+ describe ".start_logger" do
541
+ let(:out_stream) { std_stream }
542
+ let(:output) { out_stream.read }
543
+ let(:log_path) { File.join(tmp_dir, "log") }
544
+ let(:log_file) { File.join(log_path, "appsignal.log") }
545
+
546
+ before do
547
+ FileUtils.mkdir_p(log_path)
548
+
549
+ Appsignal.logger.error("Log in memory")
550
+ Appsignal.config = project_fixture_config(
551
+ "production",
552
+ :log_path => log_path
553
+ )
554
+ end
555
+ after { FileUtils.rm_rf(log_path) }
556
+
557
+ context "when the log path is writable" do
558
+ context "when the log file is writable" do
559
+ let(:log_file_contents) { File.open(log_file).read }
560
+
561
+ before do
562
+ capture_stdout(out_stream) do
563
+ Appsignal.start_logger
564
+ Appsignal.logger.error("Log to file")
565
+ end
566
+ end
567
+
568
+ it "logs to file" do
569
+ expect(File.exist?(log_file)).to be_truthy
570
+ expect(log_file_contents).to include "[ERROR] Log to file"
571
+ expect(output).to be_empty
572
+ end
573
+
574
+ it "amends in memory log to log file" do
575
+ expect(log_file_contents).to include "[ERROR] appsignal: Log in memory"
576
+ end
577
+ end
578
+
579
+ context "when the log file is not writable" do
580
+ before do
581
+ FileUtils.touch log_file
582
+ FileUtils.chmod 0o444, log_file
583
+
584
+ capture_stdout(out_stream) do
585
+ Appsignal.start_logger
586
+ Appsignal.logger.error("Log to not writable log file")
587
+ end
588
+ end
589
+
590
+ it "logs to stdout" do
591
+ expect(File.writable?(log_file)).to be_falsy
592
+ expect(output).to include "[ERROR] appsignal: Log to not writable log file"
593
+ end
594
+
595
+ it "amends in memory log to stdout" do
596
+ expect(output).to include "[ERROR] appsignal: Log in memory"
597
+ end
598
+
599
+ it "outputs a warning" do
600
+ expect(output).to include \
601
+ "[WARN] appsignal: Unable to start logger with log path '#{log_file}'.",
602
+ "[WARN] appsignal: Permission denied"
603
+ end
604
+ end
605
+ end
606
+
607
+ context "when the log path and fallback path are not writable" do
608
+ before do
609
+ FileUtils.chmod 0o444, log_path
610
+ FileUtils.chmod 0o444, Appsignal::Config.system_tmp_dir
611
+
612
+ capture_stdout(out_stream) do
613
+ Appsignal.start_logger
614
+ Appsignal.logger.error("Log to not writable log path")
615
+ end
616
+ end
617
+ after do
618
+ FileUtils.chmod 0o755, Appsignal::Config.system_tmp_dir
619
+ end
620
+
621
+ it "logs to stdout" do
622
+ expect(File.writable?(log_path)).to be_falsy
623
+ expect(output).to include "[ERROR] appsignal: Log to not writable log path"
624
+ end
625
+
626
+ it "amends in memory log to stdout" do
627
+ expect(output).to include "[ERROR] appsignal: Log in memory"
628
+ end
629
+
630
+ it "outputs a warning" do
631
+ expect(output).to include \
632
+ "appsignal: Unable to log to '#{log_path}' "\
633
+ "or the '#{Appsignal::Config.system_tmp_dir}' fallback."
634
+ end
635
+ end
636
+
637
+ context "when on Heroku" do
638
+ before do
639
+ capture_stdout(out_stream) do
640
+ Appsignal.start_logger
641
+ Appsignal.logger.error("Log to stdout")
642
+ end
643
+ end
644
+ around { |example| recognize_as_heroku { example.run } }
645
+
646
+ it "logs to stdout" do
647
+ expect(output).to include "[ERROR] appsignal: Log to stdout"
648
+ end
649
+
650
+ it "amends in memory log to stdout" do
651
+ expect(output).to include "[ERROR] appsignal: Log in memory"
652
+ end
653
+ end
654
+
655
+ describe "#logger#level" do
656
+ subject { Appsignal.logger.level }
657
+
658
+ context "when there is no config" do
659
+ before do
660
+ Appsignal.config = nil
661
+ capture_stdout(out_stream) do
662
+ Appsignal.start_logger
663
+ end
664
+ end
665
+
666
+ it "sets the log level to info" do
667
+ expect(subject).to eq Logger::INFO
668
+ end
669
+ end
670
+
671
+ context "when there is a config" do
672
+ context "when log level is configured to debug" do
673
+ before do
674
+ Appsignal.config.config_hash[:debug] = true
675
+ capture_stdout(out_stream) do
676
+ Appsignal.start_logger
677
+ end
678
+ end
679
+
680
+ it "sets the log level to debug" do
681
+ expect(subject).to eq Logger::DEBUG
682
+ end
683
+ end
684
+ end
685
+ end
686
+ end
687
+
688
+ describe ".log_formatter" do
689
+ subject { Appsignal.log_formatter.call("Debug", Time.parse("2015-07-08"), nil, "log line") }
690
+
691
+ it "formats a log" do
692
+ expect(subject).to eq "[2015-07-08T00:00:00 (process) ##{Process.pid}][Debug] log line\n"
693
+ end
694
+
695
+ context "with prefix" do
696
+ subject do
697
+ Appsignal.log_formatter("prefix").call("Debug", Time.parse("2015-07-08"), nil, "log line")
698
+ end
699
+
700
+ it "adds a prefix" do
701
+ expect(subject)
702
+ .to eq "[2015-07-08T00:00:00 (process) ##{Process.pid}][Debug] prefix: log line\n"
703
+ end
704
+ end
705
+ end
706
+
707
+ describe ".config" do
708
+ subject { Appsignal.config }
709
+
710
+ it { is_expected.to be_a Appsignal::Config }
711
+ it "should return configuration" do
712
+ expect(subject[:endpoint]).to eq "https://push.appsignal.com"
713
+ end
714
+ end
715
+
716
+ describe ".send_error" do
717
+ let(:transaction) do
718
+ Appsignal::Transaction.new(
719
+ SecureRandom.uuid,
720
+ Appsignal::Transaction::HTTP_REQUEST,
721
+ Appsignal::Transaction::GenericRequest.new({})
722
+ )
723
+ end
724
+ let(:error) { ExampleException.new }
725
+
726
+ it "sends the error to AppSignal" do
727
+ expect(Appsignal::Transaction).to receive(:new).with(
728
+ kind_of(String),
729
+ Appsignal::Transaction::HTTP_REQUEST,
730
+ kind_of(Appsignal::Transaction::GenericRequest)
731
+ ).and_return(transaction)
732
+ expect(transaction).to receive(:set_error).with(error)
733
+ expect(transaction).to_not receive(:set_tags)
734
+ expect(transaction).to receive(:complete)
735
+
736
+ Appsignal.send_error(error)
737
+ end
738
+
739
+ context "when given error is not an Exception" do
740
+ let(:error) { double }
741
+
742
+ it "logs an error message" do
743
+ expect(Appsignal.logger).to receive(:error)
744
+ .with("Can't send error, given value is not an exception")
745
+ end
746
+
747
+ it "does not send the error" do
748
+ expect(Appsignal::Transaction).to_not receive(:create)
749
+ end
750
+
751
+ after { Appsignal.send_error(error) }
752
+ end
753
+
754
+ context "with tags" do
755
+ let(:tags) { { :a => "a", :b => "b" } }
756
+ before do
757
+ allow(Appsignal::Transaction).to receive(:new).and_return(transaction)
758
+ end
759
+
760
+ it "tags the request before sending it" do
761
+ expect(transaction).to receive(:set_tags).with(tags).and_call_original
762
+ expect(transaction).to receive(:complete)
763
+
764
+ Appsignal.send_error(error, tags)
765
+ end
766
+ end
767
+
768
+ context "with namespace" do
769
+ let(:namespace) { "admin" }
770
+
771
+ it "sets the namespace on the transaction" do
772
+ expect(Appsignal::Transaction).to receive(:new).with(
773
+ kind_of(String),
774
+ "admin",
775
+ kind_of(Appsignal::Transaction::GenericRequest)
776
+ ).and_call_original
777
+ end
778
+
779
+ after { Appsignal.send_error(error, nil, namespace) }
780
+ end
781
+ end
782
+
783
+ describe ".listen_for_error" do
784
+ it "records the error and re-raise it" do
785
+ expect(Appsignal).to receive(:send_error).with(
786
+ kind_of(ExampleException),
787
+ nil,
788
+ Appsignal::Transaction::HTTP_REQUEST
789
+ )
790
+ expect do
791
+ Appsignal.listen_for_error do
792
+ raise ExampleException, "I am an exception"
793
+ end
794
+ end.to raise_error(ExampleException, "I am an exception")
795
+ end
796
+
797
+ context "with tags" do
798
+ it "adds tags to the transaction" do
799
+ expect(Appsignal).to receive(:send_error).with(
800
+ kind_of(ExampleException),
801
+ { "foo" => "bar" },
802
+ Appsignal::Transaction::HTTP_REQUEST
803
+ )
804
+ expect do
805
+ Appsignal.listen_for_error("foo" => "bar") do
806
+ raise ExampleException, "I am an exception"
807
+ end
808
+ end.to raise_error(ExampleException, "I am an exception")
809
+ end
810
+ end
811
+
812
+ context "with a custom namespace" do
813
+ it "adds the namespace to the transaction" do
814
+ expect(Appsignal).to receive(:send_error).with(
815
+ kind_of(ExampleException),
816
+ nil,
817
+ "custom_namespace"
818
+ )
819
+ expect do
820
+ Appsignal.listen_for_error(nil, "custom_namespace") do
821
+ raise ExampleException, "I am an exception"
822
+ end
823
+ end.to raise_error(ExampleException, "I am an exception")
824
+ end
825
+ end
826
+ end
827
+
828
+ describe ".set_error" do
829
+ before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) }
830
+ let(:error) { RuntimeError.new("I am an exception") }
831
+
832
+ context "when there is an active transaction" do
833
+ it "adds the error to the active transaction" do
834
+ expect(transaction).to receive(:set_error).with(error)
835
+ expect(transaction).to_not receive(:set_tags)
836
+ expect(transaction).to_not receive(:set_namespace)
837
+
838
+ Appsignal.set_error(error)
839
+ end
840
+
841
+ context "when the error is nil" do
842
+ it "does nothing" do
843
+ expect(transaction).to_not receive(:set_error)
844
+ expect(transaction).to_not receive(:set_tags)
845
+ expect(transaction).to_not receive(:set_namespace)
846
+
847
+ Appsignal.set_error(nil)
848
+ end
849
+ end
850
+
851
+ context "with tags" do
852
+ let(:tags) { { "foo" => "bar" } }
853
+
854
+ it "sets the tags on the transaction" do
855
+ expect(transaction).to receive(:set_error).with(error)
856
+ expect(transaction).to receive(:set_tags).with(tags)
857
+ expect(transaction).to_not receive(:set_namespace)
858
+
859
+ Appsignal.set_error(error, tags)
860
+ end
861
+ end
862
+
863
+ context "with namespace" do
864
+ let(:namespace) { "admin" }
865
+
866
+ it "sets the namespace on the transaction" do
867
+ expect(transaction).to receive(:set_error).with(error)
868
+ expect(transaction).to_not receive(:set_tags)
869
+ expect(transaction).to receive(:set_namespace).with(namespace)
870
+
871
+ Appsignal.set_error(error, nil, namespace)
872
+ end
873
+ end
874
+ end
875
+
876
+ context "when there is no active transaction" do
877
+ it "does nothing" do
878
+ allow(Appsignal::Transaction).to receive(:current).and_return(nil)
879
+
880
+ expect(transaction).to_not receive(:set_error)
881
+
882
+ Appsignal.set_error(error)
883
+ end
884
+ end
885
+ end
886
+
887
+ describe ".set_action" do
888
+ before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) }
889
+
890
+ it "should set the namespace to the current transaction" do
891
+ expect(transaction).to receive(:set_action).with("custom")
892
+
893
+ Appsignal.set_action("custom")
894
+ end
895
+
896
+ it "should do nothing if there is no current transaction" do
897
+ allow(Appsignal::Transaction).to receive(:current).and_return(nil)
898
+
899
+ expect(transaction).to_not receive(:set_action)
900
+
901
+ Appsignal.set_action("custom")
902
+ end
903
+
904
+ it "should do nothing if the error is nil" do
905
+ expect(transaction).to_not receive(:set_action)
906
+
907
+ Appsignal.set_action(nil)
908
+ end
909
+ end
910
+
911
+ describe ".set_namespace" do
912
+ before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) }
913
+
914
+ it "should set the namespace to the current transaction" do
915
+ expect(transaction).to receive(:set_namespace).with("custom")
916
+
917
+ Appsignal.set_namespace("custom")
918
+ end
919
+
920
+ it "should do nothing if there is no current transaction" do
921
+ allow(Appsignal::Transaction).to receive(:current).and_return(nil)
922
+
923
+ expect(transaction).to_not receive(:set_namespace)
924
+
925
+ Appsignal.set_namespace("custom")
926
+ end
927
+
928
+ it "should do nothing if the error is nil" do
929
+ expect(transaction).to_not receive(:set_namespace)
930
+
931
+ Appsignal.set_namespace(nil)
932
+ end
933
+ end
934
+
935
+ describe ".instrument" do
936
+ it_behaves_like "instrument helper" do
937
+ let(:instrumenter) { Appsignal }
938
+ before do
939
+ expect(Appsignal::Transaction).to receive(:current).at_least(:once).and_return(transaction)
940
+ end
941
+ end
942
+ end
943
+
944
+ describe ".instrument_sql" do
945
+ before do
946
+ expect(Appsignal::Transaction).to receive(:current).at_least(:once).and_return(transaction)
947
+ end
948
+
949
+ it "creates an SQL event on the transaction" do
950
+ expect(transaction).to receive(:start_event)
951
+ expect(transaction).to receive(:finish_event)
952
+ .with("name", "title", "body", Appsignal::EventFormatter::SQL_BODY_FORMAT)
953
+
954
+ result = Appsignal.instrument_sql "name", "title", "body" do
955
+ "return value"
956
+ end
957
+ expect(result).to eq "return value"
958
+ end
959
+ end
960
+
961
+ describe ".without_instrumentation" do
962
+ let(:transaction) { double }
963
+ before { allow(Appsignal::Transaction).to receive(:current).and_return(transaction) }
964
+
965
+ it "should pause and unpause the transaction around the block" do
966
+ expect(transaction).to receive(:pause!)
967
+ expect(transaction).to receive(:resume!)
968
+ end
969
+
970
+ context "without transaction" do
971
+ let(:transaction) { nil }
972
+
973
+ it "should not crash" do
974
+ # just execute the after block
975
+ end
976
+ end
977
+
978
+ after do
979
+ Appsignal.without_instrumentation do
980
+ # nothing
981
+ end
982
+ end
983
+ end
984
+
985
+ describe ".is_ignored_error?" do
986
+ let(:error) { StandardError.new }
987
+ let(:err_stream) { std_stream }
988
+ let(:stderr) { err_stream.read }
989
+ before do
990
+ allow(Appsignal).to receive(:config).and_return(:ignore_errors => ["StandardError"])
991
+ end
992
+
993
+ subject do
994
+ capture_std_streams(std_stream, err_stream) do
995
+ Appsignal.is_ignored_error?(error)
996
+ end
997
+ end
998
+
999
+ it "should return true if it's in the ignored list" do
1000
+ is_expected.to be_truthy
1001
+ end
1002
+
1003
+ it "outputs deprecated warning" do
1004
+ subject
1005
+ expect(stderr).to include("Appsignal.is_ignored_error? is deprecated with no replacement.")
1006
+ end
1007
+
1008
+ context "when error is not in the ignored list" do
1009
+ let(:error) { Object.new }
1010
+
1011
+ it "should return false" do
1012
+ is_expected.to be_falsy
1013
+ end
1014
+ end
1015
+ end
1016
+
1017
+ describe ".is_ignored_action?" do
1018
+ let(:action) { "TestController#isup" }
1019
+ let(:err_stream) { std_stream }
1020
+ let(:stderr) { err_stream.read }
1021
+ before do
1022
+ allow(Appsignal).to receive(:config).and_return(:ignore_actions => "TestController#isup")
1023
+ end
1024
+
1025
+ subject do
1026
+ capture_std_streams(std_stream, err_stream) do
1027
+ Appsignal.is_ignored_action?(action)
1028
+ end
1029
+ end
1030
+
1031
+ it "should return true if it's in the ignored list" do
1032
+ is_expected.to be_truthy
1033
+ end
1034
+
1035
+ it "outputs deprecated warning" do
1036
+ subject
1037
+ expect(stderr).to include("Appsignal.is_ignored_action? is deprecated with no replacement.")
1038
+ end
1039
+
1040
+ context "when action is not in the ingore list" do
1041
+ let(:action) { "TestController#other_action" }
1042
+
1043
+ it "should return false" do
1044
+ is_expected.to be_falsy
1045
+ end
1046
+ end
1047
+ end
1048
+ end
1049
+ end