sentry-raven 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +6 -0
  4. data/lib/raven/configuration.rb +10 -0
  5. data/lib/raven/instance.rb +5 -0
  6. data/lib/raven/integrations/delayed_job.rb +2 -1
  7. data/lib/raven/integrations/rack.rb +15 -2
  8. data/lib/raven/integrations/sidekiq/error_handler.rb +2 -2
  9. data/lib/raven/transports/http.rb +0 -2
  10. data/lib/raven/{integrations/sidekiq → utils}/context_filter.rb +3 -3
  11. data/lib/raven/version.rb +1 -1
  12. data/sentry-ruby/.gitignore +11 -0
  13. data/sentry-ruby/.rspec +3 -0
  14. data/sentry-ruby/.travis.yml +6 -0
  15. data/sentry-ruby/CODE_OF_CONDUCT.md +74 -0
  16. data/sentry-ruby/Gemfile +9 -0
  17. data/sentry-ruby/LICENSE.txt +21 -0
  18. data/sentry-ruby/README.md +44 -0
  19. data/sentry-ruby/Rakefile +6 -0
  20. data/sentry-ruby/bin/console +14 -0
  21. data/sentry-ruby/bin/setup +8 -0
  22. data/sentry-ruby/examples/rails-6.0/.browserslistrc +1 -0
  23. data/sentry-ruby/examples/rails-6.0/.gitignore +35 -0
  24. data/sentry-ruby/examples/rails-6.0/Gemfile +58 -0
  25. data/sentry-ruby/examples/rails-6.0/README.md +23 -0
  26. data/sentry-ruby/examples/rails-6.0/Rakefile +6 -0
  27. data/sentry-ruby/examples/rails-6.0/app/assets/config/manifest.js +2 -0
  28. data/sentry-ruby/examples/rails-6.0/app/assets/images/.keep +0 -0
  29. data/sentry-ruby/examples/rails-6.0/app/assets/stylesheets/application.css +15 -0
  30. data/sentry-ruby/examples/rails-6.0/app/channels/application_cable/channel.rb +4 -0
  31. data/sentry-ruby/examples/rails-6.0/app/channels/application_cable/connection.rb +4 -0
  32. data/sentry-ruby/examples/rails-6.0/app/controllers/application_controller.rb +2 -0
  33. data/sentry-ruby/examples/rails-6.0/app/controllers/concerns/.keep +0 -0
  34. data/sentry-ruby/examples/rails-6.0/app/controllers/welcome_controller.rb +23 -0
  35. data/sentry-ruby/examples/rails-6.0/app/helpers/application_helper.rb +2 -0
  36. data/sentry-ruby/examples/rails-6.0/app/javascript/channels/consumer.js +6 -0
  37. data/sentry-ruby/examples/rails-6.0/app/javascript/channels/index.js +5 -0
  38. data/sentry-ruby/examples/rails-6.0/app/javascript/packs/application.js +17 -0
  39. data/sentry-ruby/examples/rails-6.0/app/jobs/application_job.rb +7 -0
  40. data/sentry-ruby/examples/rails-6.0/app/mailers/application_mailer.rb +4 -0
  41. data/sentry-ruby/examples/rails-6.0/app/models/application_record.rb +3 -0
  42. data/sentry-ruby/examples/rails-6.0/app/models/concerns/.keep +0 -0
  43. data/sentry-ruby/examples/rails-6.0/app/views/layouts/application.html.erb +15 -0
  44. data/sentry-ruby/examples/rails-6.0/app/views/layouts/mailer.html.erb +13 -0
  45. data/sentry-ruby/examples/rails-6.0/app/views/layouts/mailer.text.erb +1 -0
  46. data/sentry-ruby/examples/rails-6.0/app/views/welcome/report_demo.html.erb +22 -0
  47. data/sentry-ruby/examples/rails-6.0/app/views/welcome/view_error.html.erb +1 -0
  48. data/sentry-ruby/examples/rails-6.0/app/workers/error_worker.rb +7 -0
  49. data/sentry-ruby/examples/rails-6.0/babel.config.js +72 -0
  50. data/sentry-ruby/examples/rails-6.0/bin/bundle +114 -0
  51. data/sentry-ruby/examples/rails-6.0/bin/rails +9 -0
  52. data/sentry-ruby/examples/rails-6.0/bin/rake +9 -0
  53. data/sentry-ruby/examples/rails-6.0/bin/setup +36 -0
  54. data/sentry-ruby/examples/rails-6.0/bin/spring +17 -0
  55. data/sentry-ruby/examples/rails-6.0/bin/webpack +18 -0
  56. data/sentry-ruby/examples/rails-6.0/bin/webpack-dev-server +18 -0
  57. data/sentry-ruby/examples/rails-6.0/bin/yarn +11 -0
  58. data/sentry-ruby/examples/rails-6.0/config.ru +5 -0
  59. data/sentry-ruby/examples/rails-6.0/config/application.rb +28 -0
  60. data/sentry-ruby/examples/rails-6.0/config/boot.rb +4 -0
  61. data/sentry-ruby/examples/rails-6.0/config/cable.yml +10 -0
  62. data/sentry-ruby/examples/rails-6.0/config/credentials.yml.enc +1 -0
  63. data/sentry-ruby/examples/rails-6.0/config/database.yml +25 -0
  64. data/sentry-ruby/examples/rails-6.0/config/environment.rb +5 -0
  65. data/sentry-ruby/examples/rails-6.0/config/environments/development.rb +62 -0
  66. data/sentry-ruby/examples/rails-6.0/config/environments/production.rb +112 -0
  67. data/sentry-ruby/examples/rails-6.0/config/environments/test.rb +48 -0
  68. data/sentry-ruby/examples/rails-6.0/config/initializers/application_controller_renderer.rb +8 -0
  69. data/sentry-ruby/examples/rails-6.0/config/initializers/assets.rb +14 -0
  70. data/sentry-ruby/examples/rails-6.0/config/initializers/backtrace_silencers.rb +7 -0
  71. data/sentry-ruby/examples/rails-6.0/config/initializers/content_security_policy.rb +30 -0
  72. data/sentry-ruby/examples/rails-6.0/config/initializers/cookies_serializer.rb +5 -0
  73. data/sentry-ruby/examples/rails-6.0/config/initializers/filter_parameter_logging.rb +4 -0
  74. data/sentry-ruby/examples/rails-6.0/config/initializers/inflections.rb +16 -0
  75. data/sentry-ruby/examples/rails-6.0/config/initializers/mime_types.rb +4 -0
  76. data/sentry-ruby/examples/rails-6.0/config/initializers/wrap_parameters.rb +14 -0
  77. data/sentry-ruby/examples/rails-6.0/config/locales/en.yml +33 -0
  78. data/sentry-ruby/examples/rails-6.0/config/puma.rb +38 -0
  79. data/sentry-ruby/examples/rails-6.0/config/routes.rb +10 -0
  80. data/sentry-ruby/examples/rails-6.0/config/spring.rb +6 -0
  81. data/sentry-ruby/examples/rails-6.0/config/storage.yml +34 -0
  82. data/sentry-ruby/examples/rails-6.0/config/webpack/development.js +5 -0
  83. data/sentry-ruby/examples/rails-6.0/config/webpack/environment.js +3 -0
  84. data/sentry-ruby/examples/rails-6.0/config/webpack/production.js +5 -0
  85. data/sentry-ruby/examples/rails-6.0/config/webpack/test.js +5 -0
  86. data/sentry-ruby/examples/rails-6.0/config/webpacker.yml +96 -0
  87. data/sentry-ruby/examples/rails-6.0/db/seeds.rb +7 -0
  88. data/sentry-ruby/examples/rails-6.0/lib/assets/.keep +0 -0
  89. data/sentry-ruby/examples/rails-6.0/lib/tasks/.keep +0 -0
  90. data/sentry-ruby/examples/rails-6.0/package.json +15 -0
  91. data/sentry-ruby/examples/rails-6.0/postcss.config.js +12 -0
  92. data/sentry-ruby/examples/rails-6.0/public/404.html +67 -0
  93. data/sentry-ruby/examples/rails-6.0/public/422.html +67 -0
  94. data/sentry-ruby/examples/rails-6.0/public/500.html +66 -0
  95. data/sentry-ruby/examples/rails-6.0/public/apple-touch-icon-precomposed.png +0 -0
  96. data/sentry-ruby/examples/rails-6.0/public/apple-touch-icon.png +0 -0
  97. data/sentry-ruby/examples/rails-6.0/public/favicon.ico +0 -0
  98. data/sentry-ruby/examples/rails-6.0/public/robots.txt +1 -0
  99. data/sentry-ruby/examples/rails-6.0/storage/.keep +0 -0
  100. data/sentry-ruby/examples/rails-6.0/test/application_system_test_case.rb +5 -0
  101. data/sentry-ruby/examples/rails-6.0/test/channels/application_cable/connection_test.rb +11 -0
  102. data/sentry-ruby/examples/rails-6.0/test/controllers/.keep +0 -0
  103. data/sentry-ruby/examples/rails-6.0/test/fixtures/.keep +0 -0
  104. data/sentry-ruby/examples/rails-6.0/test/fixtures/files/.keep +0 -0
  105. data/sentry-ruby/examples/rails-6.0/test/helpers/.keep +0 -0
  106. data/sentry-ruby/examples/rails-6.0/test/integration/.keep +0 -0
  107. data/sentry-ruby/examples/rails-6.0/test/mailers/.keep +0 -0
  108. data/sentry-ruby/examples/rails-6.0/test/models/.keep +0 -0
  109. data/sentry-ruby/examples/rails-6.0/test/system/.keep +0 -0
  110. data/sentry-ruby/examples/rails-6.0/test/test_helper.rb +13 -0
  111. data/sentry-ruby/examples/rails-6.0/vendor/.keep +0 -0
  112. data/sentry-ruby/examples/rails-6.0/yarn.lock +7508 -0
  113. data/sentry-ruby/lib/sentry.rb +16 -0
  114. data/sentry-ruby/lib/sentry/backtrace.rb +128 -0
  115. data/sentry-ruby/lib/sentry/client.rb +162 -0
  116. data/sentry-ruby/lib/sentry/client/state.rb +40 -0
  117. data/sentry-ruby/lib/sentry/configuration.rb +533 -0
  118. data/sentry-ruby/lib/sentry/event.rb +209 -0
  119. data/sentry-ruby/lib/sentry/interface.rb +31 -0
  120. data/sentry-ruby/lib/sentry/interfaces/exception.rb +15 -0
  121. data/sentry-ruby/lib/sentry/interfaces/http.rb +16 -0
  122. data/sentry-ruby/lib/sentry/interfaces/message.rb +18 -0
  123. data/sentry-ruby/lib/sentry/interfaces/single_exception.rb +14 -0
  124. data/sentry-ruby/lib/sentry/interfaces/stack_trace.rb +69 -0
  125. data/sentry-ruby/lib/sentry/linecache.rb +44 -0
  126. data/sentry-ruby/lib/sentry/logger.rb +20 -0
  127. data/sentry-ruby/lib/sentry/transports.rb +19 -0
  128. data/sentry-ruby/lib/sentry/transports/dummy.rb +16 -0
  129. data/sentry-ruby/lib/sentry/transports/http.rb +66 -0
  130. data/sentry-ruby/lib/sentry/transports/stdout.rb +20 -0
  131. data/sentry-ruby/lib/sentry/utils/deep_merge.rb +22 -0
  132. data/sentry-ruby/lib/sentry/utils/exception_cause_chain.rb +20 -0
  133. data/sentry-ruby/lib/sentry/version.rb +3 -0
  134. data/sentry-ruby/sentry-ruby.gemspec +26 -0
  135. data/sentry-ruby/spec/sentry/backtrace_spec.rb +38 -0
  136. data/sentry-ruby/spec/sentry/client_spec.rb +443 -0
  137. data/sentry-ruby/spec/sentry/configuration_spec.rb +400 -0
  138. data/sentry-ruby/spec/sentry/event_spec.rb +238 -0
  139. data/sentry-ruby/spec/sentry/interface_spec.rb +38 -0
  140. data/sentry-ruby/spec/sentry/interfaces/stack_trace_spec.rb +11 -0
  141. data/sentry-ruby/spec/sentry/linecache_spec.rb +40 -0
  142. data/sentry-ruby/spec/sentry/transports/http_spec.rb +57 -0
  143. data/sentry-ruby/spec/sentry/transports/stdout_spec.rb +11 -0
  144. data/sentry-ruby/spec/sentry_spec.rb +9 -0
  145. data/sentry-ruby/spec/spec_helper.rb +49 -0
  146. data/sentry-ruby/spec/support/linecache.txt +6 -0
  147. metadata +138 -3
@@ -0,0 +1,19 @@
1
+ module Sentry
2
+ module Transports
3
+ class Transport
4
+ attr_accessor :configuration
5
+
6
+ def initialize(configuration)
7
+ @configuration = configuration
8
+ end
9
+
10
+ def send_event # (auth_header, data, options = {})
11
+ raise NotImplementedError, 'Abstract method not implemented'
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ require "sentry/transports/dummy"
18
+ require "sentry/transports/http"
19
+ require "sentry/transports/stdout"
@@ -0,0 +1,16 @@
1
+ module Sentry
2
+ module Transports
3
+ class Dummy < Transport
4
+ attr_accessor :events
5
+
6
+ def initialize(*)
7
+ super
8
+ @events = []
9
+ end
10
+
11
+ def send_event(auth_header, data, options = {})
12
+ @events << [auth_header, data, options]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,66 @@
1
+ require 'faraday'
2
+
3
+ module Sentry
4
+ module Transports
5
+ class HTTP < Transport
6
+ attr_accessor :conn, :adapter
7
+
8
+ def initialize(*args)
9
+ super
10
+ self.adapter = configuration.http_adapter || Faraday.default_adapter
11
+ self.conn = set_conn
12
+ end
13
+
14
+ def send_event(auth_header, data, options = {})
15
+ unless configuration.sending_allowed?
16
+ logger.debug("Event not sent: #{configuration.error_messages}")
17
+ end
18
+
19
+ project_id = configuration[:project_id]
20
+ path = configuration[:path] + "/"
21
+
22
+ conn.post "#{path}api/#{project_id}/store/" do |req|
23
+ req.headers['Content-Type'] = options[:content_type]
24
+ req.headers['X-Sentry-Auth'] = auth_header
25
+ req.body = data
26
+ end
27
+ rescue Faraday::Error => e
28
+ error_info = e.message
29
+ if e.response && e.response[:headers]['x-sentry-error']
30
+ error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}"
31
+ end
32
+ raise Sentry::Error, error_info
33
+ end
34
+
35
+ private
36
+
37
+ def set_conn
38
+ configuration.logger.debug "Sentry HTTP Transport connecting to #{configuration.server}"
39
+
40
+ proxy = configuration.public_send(:proxy)
41
+
42
+ Faraday.new(configuration.server, :ssl => ssl_configuration, :proxy => proxy) do |builder|
43
+ configuration.faraday_builder&.call(builder)
44
+ builder.response :raise_error
45
+ builder.options.merge! faraday_opts
46
+ builder.headers[:user_agent] = "sentry-ruby/#{Sentry::VERSION}"
47
+ builder.adapter(*adapter)
48
+ end
49
+ end
50
+
51
+ # TODO: deprecate and replace where possible w/Faraday Builder
52
+ def faraday_opts
53
+ [:timeout, :open_timeout].each_with_object({}) do |opt, memo|
54
+ memo[opt] = configuration.public_send(opt) if configuration.public_send(opt)
55
+ end
56
+ end
57
+
58
+ def ssl_configuration
59
+ (configuration.ssl || {}).merge(
60
+ :verify => configuration.ssl_verification,
61
+ :ca_file => configuration.ssl_ca_file
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,20 @@
1
+ module Sentry
2
+ module Transports
3
+ class Stdout < Transport
4
+ attr_accessor :events
5
+
6
+ def initialize(*)
7
+ super
8
+ end
9
+
10
+ def send_event(_auth_header, data, _options = {})
11
+ unless configuration.sending_allowed?
12
+ logger.debug("Event not sent: #{configuration.error_messages}")
13
+ end
14
+
15
+ $stdout.puts data
16
+ $stdout.flush
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Sentry
2
+ module Utils
3
+ # ported from ActiveSupport
4
+ module DeepMergeHash
5
+ def self.deep_merge(hash, other_hash, &block)
6
+ deep_merge!(hash, other_hash, &block)
7
+ end
8
+
9
+ def self.deep_merge!(hash, other_hash, &block)
10
+ hash.merge!(other_hash) do |key, this_val, other_val|
11
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
12
+ deep_merge(this_val, other_val, &block)
13
+ elsif block_given?
14
+ block.call(key, this_val, other_val)
15
+ else
16
+ other_val
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ module Sentry
2
+ module Utils
3
+ module ExceptionCauseChain
4
+ def self.exception_to_array(exception)
5
+ if exception.respond_to?(:cause) && exception.cause
6
+ exceptions = [exception]
7
+ while exception.cause
8
+ exception = exception.cause
9
+ break if exceptions.any? { |e| e.object_id == exception.object_id }
10
+
11
+ exceptions << exception
12
+ end
13
+ exceptions
14
+ else
15
+ [exception]
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Sentry
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'lib/sentry/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "sentry-ruby"
5
+ spec.version = Sentry::VERSION
6
+ spec.authors = ["Sentry Team"]
7
+ spec.description = spec.summary = "A gem that provides a client interface for the Sentry error logger"
8
+ spec.email = "accounts@sentry.io"
9
+ spec.license = 'Apache-2.0'
10
+ spec.homepage = "https://github.com/getsentry/raven-ruby"
11
+
12
+ spec.platform = Gem::Platform::RUBY
13
+ spec.required_ruby_version = '>= 2.4'
14
+ spec.extra_rdoc_files = ["README.md", "LICENSE"]
15
+ spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples)'`.split("\n")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
20
+
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_dependency "faraday", ">= 1.0"
26
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Sentry::Backtrace do
4
+ let(:configuration) { Sentry::Configuration.new }
5
+
6
+ before(:each) do
7
+ @backtrace = Sentry::Backtrace.parse(Thread.current.backtrace, configuration: configuration)
8
+ end
9
+
10
+ it "calls backtrace_cleanup_callback if it's present in the configuration" do
11
+ called = false
12
+ callback = proc do |backtrace|
13
+ called = true
14
+ backtrace
15
+ end
16
+ configuration.backtrace_cleanup_callback = callback
17
+ Sentry::Backtrace.parse(Thread.current.backtrace, configuration: configuration)
18
+
19
+ expect(called).to eq(true)
20
+ end
21
+
22
+ it "#lines" do
23
+ expect(@backtrace.lines.first).to be_a(Sentry::Backtrace::Line)
24
+ end
25
+
26
+ it "#inspect" do
27
+ expect(@backtrace.inspect).to match(/Backtrace: .*>$/)
28
+ end
29
+
30
+ it "#to_s" do
31
+ expect(@backtrace.to_s).to match(/backtrace_spec.rb:\d/)
32
+ end
33
+
34
+ it "==" do
35
+ @backtrace2 = Sentry::Backtrace.new(@backtrace.lines)
36
+ expect(@backtrace).to be == @backtrace2
37
+ end
38
+ end
@@ -0,0 +1,443 @@
1
+ require 'spec_helper'
2
+
3
+ class ExceptionWithContext < StandardError
4
+ def sentry_context
5
+ { extra: {
6
+ 'context_event_key' => 'context_value',
7
+ 'context_key' => 'context_value'
8
+ } }
9
+ end
10
+ end
11
+
12
+ RSpec.describe Sentry::Client do
13
+ let(:configuration) do
14
+ Sentry::Configuration.new.tap do |config|
15
+ config.server = 'http://12345:67890@sentry.localdomain/sentry/42'
16
+ end
17
+ end
18
+ let(:fake_time) { Time.now }
19
+
20
+ subject { Sentry::Client.new(configuration) }
21
+
22
+ before do
23
+ allow(Time).to receive(:now).and_return fake_time
24
+ end
25
+
26
+ describe "#generate_auth_header" do
27
+ it "generates an auth header" do
28
+ expect(subject.send(:generate_auth_header)).to eq(
29
+ "Sentry sentry_version=5, sentry_client=sentry-ruby/#{Sentry::VERSION}, sentry_timestamp=#{fake_time.to_i}, " \
30
+ "sentry_key=12345, sentry_secret=67890"
31
+ )
32
+ end
33
+
34
+ it "generates an auth header without a secret (Sentry 9)" do
35
+ configuration.server = "https://66260460f09b5940498e24bb7ce093a0@sentry.io/42"
36
+
37
+ expect(subject.send(:generate_auth_header)).to eq(
38
+ "Sentry sentry_version=5, sentry_client=sentry-ruby/#{Sentry::VERSION}, sentry_timestamp=#{fake_time.to_i}, " \
39
+ "sentry_key=66260460f09b5940498e24bb7ce093a0"
40
+ )
41
+ end
42
+ end
43
+
44
+ describe "#send_event" do
45
+ let(:event) { subject.event_from_exception(ZeroDivisionError.new("divided by 0")) }
46
+
47
+ context "when success" do
48
+ before do
49
+ allow(subject.transport).to receive(:send_event)
50
+ end
51
+
52
+ it "sends Event object" do
53
+ expect(subject).not_to receive(:failed_send)
54
+
55
+ expect(subject.send_event(event)).to eq(event.to_hash)
56
+ end
57
+
58
+ it "sends Event hash" do
59
+ expect(subject).not_to receive(:failed_send)
60
+
61
+ expect(subject.send_event(event.to_json_compatible)).to eq(event.to_json_compatible)
62
+ end
63
+ end
64
+
65
+ context "when failed" do
66
+ let(:logger) { spy }
67
+
68
+ before do
69
+ configuration.logger = logger
70
+ allow(subject.transport).to receive(:send_event).and_raise(StandardError)
71
+
72
+ expect(logger).to receive(:warn).exactly(2)
73
+ end
74
+
75
+ it "sends Event object" do
76
+ expect(subject.send_event(event)).to eq(nil)
77
+ end
78
+
79
+ it "sends Event hash" do
80
+ expect(subject.send_event(event.to_json_compatible)).to eq(nil)
81
+ end
82
+ end
83
+ end
84
+
85
+ describe "#transport" do
86
+ context "when scheme is not set" do
87
+ it "returns HTTP transport object" do
88
+ expect(subject.transport).to be_a(Sentry::Transports::HTTP)
89
+ end
90
+ end
91
+
92
+ context "when scheme is http" do
93
+ before do
94
+ configuration.scheme = "http"
95
+ end
96
+
97
+ it "returns HTTP transport object" do
98
+ expect(subject.transport).to be_a(Sentry::Transports::HTTP)
99
+ end
100
+ end
101
+
102
+ context "when scheme is https" do
103
+ before do
104
+ configuration.scheme = "https"
105
+ end
106
+
107
+ it "returns HTTP transport object" do
108
+ expect(subject.transport).to be_a(Sentry::Transports::HTTP)
109
+ end
110
+ end
111
+
112
+ context "when scheme is dummy" do
113
+ before do
114
+ configuration.scheme = "dummy"
115
+ end
116
+
117
+ it "returns Dummy transport object" do
118
+ expect(subject.transport).to be_a(Sentry::Transports::Dummy)
119
+ end
120
+ end
121
+
122
+ context "when scheme is stdout" do
123
+ before do
124
+ configuration.scheme = "stdout"
125
+ end
126
+
127
+ it "returns Stdout transport object" do
128
+ expect(subject.transport).to be_a(Sentry::Transports::Stdout)
129
+ end
130
+ end
131
+ end
132
+
133
+ shared_examples "options" do
134
+ let(:options) do
135
+ {
136
+ checksum: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
137
+ release: '1.0',
138
+ fingerprint: ['{{ default }}', 'foo'],
139
+ backtrace: ["/path/to/some/file:22:in `function_name'", "/some/other/path:1412:in `other_function'"]
140
+ }
141
+ end
142
+
143
+ let(:event) do
144
+ subject.event_from_exception(Exception.new, **options)
145
+ end
146
+
147
+ it 'takes and sets all available options' do
148
+ expect(event.checksum).to eq('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
149
+ expect(event.release).to eq('1.0')
150
+ expect(event.fingerprint).to eq(['{{ default }}', 'foo'])
151
+ end
152
+
153
+ it "contains given backtrace" do
154
+ expect(event[:stacktrace]).to be_a(Sentry::StacktraceInterface)
155
+
156
+ frames = event[:stacktrace].to_hash[:frames]
157
+ expect(frames.length).to eq(2)
158
+ expect(frames[0][:lineno]).to eq(1412)
159
+ expect(frames[0][:function]).to eq('other_function')
160
+ expect(frames[0][:filename]).to eq('/some/other/path')
161
+
162
+ expect(frames[1][:lineno]).to eq(22)
163
+ expect(frames[1][:function]).to eq('function_name')
164
+ expect(frames[1][:filename]).to eq('/path/to/some/file')
165
+ end
166
+ end
167
+
168
+ describe '#event_from_message' do
169
+ let(:message) { 'This is a message' }
170
+
171
+ it 'returns an event' do
172
+ event = subject.event_from_message(message)
173
+ hash = event.to_hash
174
+
175
+ expect(event).to be_a(Sentry::Event)
176
+ expect(hash[:message]).to eq(message)
177
+ expect(hash[:level]).to eq(:error)
178
+ end
179
+
180
+ it "doesn't change the option hash" do
181
+ h_int = { abc: :abc }
182
+ h = { k1: h_int, k2: h_int }
183
+ subject.event_from_message "Test extra", extra: { h1: h, h2: h_int }
184
+
185
+ expect(h).to eq({ k1: h_int, k2: h_int })
186
+ end
187
+
188
+ it_behaves_like "options"
189
+ end
190
+
191
+ describe "#event_from_exception" do
192
+ let(:message) { 'This is a message' }
193
+ let(:exception) { Exception.new(message) }
194
+ let(:event) { subject.event_from_exception(exception) }
195
+ let(:hash) { event.to_hash }
196
+
197
+ before do
198
+ configuration.scheme = "dummy"
199
+ end
200
+
201
+ it "sets the message to the exception's value and type" do
202
+ expect(hash[:exception][:values][0][:type]).to eq("Exception")
203
+ expect(hash[:exception][:values][0][:value]).to eq(message)
204
+ end
205
+
206
+ it 'has level ERROR' do
207
+ expect(hash[:level]).to eq(:error)
208
+ end
209
+
210
+ it 'does not belong to a module' do
211
+ expect(hash[:exception][:values][0][:module]).to eq('')
212
+ end
213
+
214
+ it 'returns an event' do
215
+ event = subject.event_from_exception(ZeroDivisionError.new("divided by 0"))
216
+ expect(event).to be_a(Sentry::Event)
217
+ expect(subject.send(:get_message_from_exception, event.to_hash)).to eq("ZeroDivisionError: divided by 0")
218
+ end
219
+
220
+ it_behaves_like "options"
221
+
222
+ describe "options - message" do
223
+ it "proceses string message correctly" do
224
+ event = subject.event_from_exception(ExceptionWithContext.new, message: "MSG")
225
+ expect(event.message).to eq("MSG")
226
+ end
227
+
228
+ it "slices long string message" do
229
+ event = subject.event_from_exception(ExceptionWithContext.new, message: "MSG" * 3000)
230
+ expect(event.message.length).to eq(8192)
231
+ end
232
+
233
+ it "converts non-string message into string" do
234
+ expect(configuration.logger).to receive(:debug).with("You're passing a non-string message")
235
+
236
+ event = subject.event_from_exception(ExceptionWithContext.new, message: { foo: "bar" })
237
+ expect(event.message).to eq("{:foo=>\"bar\"}")
238
+ end
239
+ end
240
+
241
+ context 'for a nested exception type' do
242
+ module Sentry::Test
243
+ class Exception < RuntimeError; end
244
+ end
245
+ let(:exception) { Sentry::Test::Exception.new(message) }
246
+
247
+ it 'sends the module name as part of the exception info' do
248
+ expect(hash[:exception][:values][0][:module]).to eq('Sentry::Test')
249
+ end
250
+ end
251
+
252
+ describe "exception types test" do
253
+ context 'for a Sentry::Error' do
254
+ let(:exception) { Sentry::Error.new }
255
+ it 'does not create an event' do
256
+ expect(subject.event_from_exception(exception)).to be_nil
257
+ end
258
+ end
259
+
260
+ context 'for an excluded exception type' do
261
+ module Sentry::Test
262
+ class BaseExc < RuntimeError; end
263
+ class SubExc < BaseExc; end
264
+ module ExcTag; end
265
+ end
266
+
267
+ let(:config) { subject.configuration }
268
+
269
+ context "invalid exclusion type" do
270
+ it 'returns Sentry::Event' do
271
+ config.excluded_exceptions << nil
272
+ config.excluded_exceptions << 1
273
+ config.excluded_exceptions << {}
274
+ expect(subject.event_from_exception(Sentry::Test::BaseExc.new)).to be_a(Sentry::Event)
275
+ end
276
+ end
277
+
278
+ context "defined by string type" do
279
+ it 'returns nil for a class match' do
280
+ config.excluded_exceptions << 'Sentry::Test::BaseExc'
281
+ expect(subject.event_from_exception(Sentry::Test::BaseExc.new)).to be_nil
282
+ end
283
+
284
+ it 'returns nil for a top class match' do
285
+ config.excluded_exceptions << '::Sentry::Test::BaseExc'
286
+ expect(subject.event_from_exception(Sentry::Test::BaseExc.new)).to be_nil
287
+ end
288
+
289
+ it 'returns nil for a sub class match' do
290
+ config.excluded_exceptions << 'Sentry::Test::BaseExc'
291
+ expect(subject.event_from_exception(Sentry::Test::SubExc.new)).to be_nil
292
+ end
293
+
294
+ it 'returns nil for a tagged class match' do
295
+ config.excluded_exceptions << 'Sentry::Test::ExcTag'
296
+ expect(
297
+ subject.event_from_exception(Sentry::Test::SubExc.new.tap { |x| x.extend(Sentry::Test::ExcTag) })
298
+ ).to be_nil
299
+ end
300
+
301
+ it 'returns Sentry::Event for an undefined exception class' do
302
+ config.excluded_exceptions << 'Sentry::Test::NonExistentExc'
303
+ expect(subject.event_from_exception(Sentry::Test::BaseExc.new)).to be_a(Sentry::Event)
304
+ end
305
+ end
306
+
307
+ context "defined by class type" do
308
+ it 'returns nil for a class match' do
309
+ config.excluded_exceptions << Sentry::Test::BaseExc
310
+ expect(subject.event_from_exception(Sentry::Test::BaseExc.new)).to be_nil
311
+ end
312
+
313
+ it 'returns nil for a sub class match' do
314
+ config.excluded_exceptions << Sentry::Test::BaseExc
315
+ expect(subject.event_from_exception(Sentry::Test::SubExc.new)).to be_nil
316
+ end
317
+
318
+ it 'returns nil for a tagged class match' do
319
+ config.excluded_exceptions << Sentry::Test::ExcTag
320
+ expect(subject.event_from_exception(Sentry::Test::SubExc.new.tap { |x| x.extend(Sentry::Test::ExcTag) })).to be_nil
321
+ end
322
+ end
323
+ end
324
+
325
+ # Only check causes when they're supported
326
+ if Exception.new.respond_to? :cause
327
+ context 'when the exception has a cause' do
328
+ let(:exception) { build_exception_with_cause }
329
+
330
+ it 'captures the cause' do
331
+ expect(hash[:exception][:values].length).to eq(2)
332
+ end
333
+ end
334
+
335
+ context 'when the exception has nested causes' do
336
+ let(:exception) { build_exception_with_two_causes }
337
+
338
+ it 'captures nested causes' do
339
+ expect(hash[:exception][:values].length).to eq(3)
340
+ end
341
+ end
342
+ end
343
+
344
+ context 'when the exception has a recursive cause' do
345
+ let(:exception) { build_exception_with_recursive_cause }
346
+
347
+ it 'should handle it gracefully' do
348
+ expect(hash[:exception][:values].length).to eq(1)
349
+ end
350
+ end
351
+
352
+ if RUBY_PLATFORM == "java"
353
+ context 'when running under jRuby' do
354
+ let(:exception) do
355
+ begin
356
+ raise java.lang.OutOfMemoryError, "A Java error"
357
+ rescue Exception => e
358
+ return e
359
+ end
360
+ end
361
+
362
+ it 'should have a backtrace' do
363
+ frames = hash[:exception][:values][0][:stacktrace][:frames]
364
+ expect(frames.length).not_to eq(0)
365
+ end
366
+ end
367
+ end
368
+
369
+ context 'when the exception has a backtrace' do
370
+ let(:exception) do
371
+ e = Exception.new(message)
372
+ allow(e).to receive(:backtrace).and_return [
373
+ "/path/to/some/file:22:in `function_name'",
374
+ "/some/other/path:1412:in `other_function'"
375
+ ]
376
+ e
377
+ end
378
+
379
+ it 'parses the backtrace' do
380
+ frames = hash[:exception][:values][0][:stacktrace][:frames]
381
+ expect(frames.length).to eq(2)
382
+ expect(frames[0][:lineno]).to eq(1412)
383
+ expect(frames[0][:function]).to eq('other_function')
384
+ expect(frames[0][:filename]).to eq('/some/other/path')
385
+
386
+ expect(frames[1][:lineno]).to eq(22)
387
+ expect(frames[1][:function]).to eq('function_name')
388
+ expect(frames[1][:filename]).to eq('/path/to/some/file')
389
+ end
390
+
391
+ context 'with internal backtrace' do
392
+ let(:exception) do
393
+ e = Exception.new(message)
394
+ allow(e).to receive(:backtrace).and_return(["<internal:prelude>:10:in `synchronize'"])
395
+ e
396
+ end
397
+
398
+ it 'marks filename and in_app correctly' do
399
+ frames = hash[:exception][:values][0][:stacktrace][:frames]
400
+ expect(frames[0][:lineno]).to eq(10)
401
+ expect(frames[0][:function]).to eq("synchronize")
402
+ expect(frames[0][:filename]).to eq("<internal:prelude>")
403
+ end
404
+ end
405
+
406
+ context 'when a path in the stack trace is on the load path' do
407
+ before do
408
+ $LOAD_PATH << '/some'
409
+ end
410
+
411
+ after do
412
+ $LOAD_PATH.delete('/some')
413
+ end
414
+
415
+ it 'strips prefixes in the load path from frame filenames' do
416
+ frames = hash[:exception][:values][0][:stacktrace][:frames]
417
+ expect(frames[0][:filename]).to eq('other/path')
418
+ end
419
+ end
420
+ end
421
+
422
+ context 'merging exception context' do
423
+ let(:hash) do
424
+ event = subject.event_from_exception(
425
+ ExceptionWithContext.new,
426
+ message: "MSG",
427
+ extra: {
428
+ 'context_event_key' => 'event_value',
429
+ 'event_key' => 'event_value'
430
+ }
431
+ )
432
+ event.to_hash
433
+ end
434
+
435
+ it 'prioritizes event context over request context' do
436
+ expect(hash[:extra]['context_event_key']).to eq('event_value')
437
+ expect(hash[:extra]['context_key']).to eq('context_value')
438
+ expect(hash[:extra]['event_key']).to eq('event_value')
439
+ end
440
+ end
441
+ end
442
+ end
443
+ end