sentry-ruby-core 4.1.5.pre.beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.craft.yml +33 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +6 -0
  6. data/CHANGELOG.md +125 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +16 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +258 -0
  11. data/Rakefile +13 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/sentry-ruby.rb +191 -0
  15. data/lib/sentry/background_worker.rb +37 -0
  16. data/lib/sentry/backtrace.rb +126 -0
  17. data/lib/sentry/benchmarks/benchmark_transport.rb +14 -0
  18. data/lib/sentry/breadcrumb.rb +25 -0
  19. data/lib/sentry/breadcrumb/sentry_logger.rb +87 -0
  20. data/lib/sentry/breadcrumb_buffer.rb +47 -0
  21. data/lib/sentry/client.rb +96 -0
  22. data/lib/sentry/configuration.rb +396 -0
  23. data/lib/sentry/core_ext/object/deep_dup.rb +57 -0
  24. data/lib/sentry/core_ext/object/duplicable.rb +153 -0
  25. data/lib/sentry/dsn.rb +48 -0
  26. data/lib/sentry/event.rb +173 -0
  27. data/lib/sentry/hub.rb +143 -0
  28. data/lib/sentry/integrable.rb +24 -0
  29. data/lib/sentry/interface.rb +22 -0
  30. data/lib/sentry/interfaces/exception.rb +11 -0
  31. data/lib/sentry/interfaces/request.rb +113 -0
  32. data/lib/sentry/interfaces/single_exception.rb +14 -0
  33. data/lib/sentry/interfaces/stacktrace.rb +90 -0
  34. data/lib/sentry/linecache.rb +44 -0
  35. data/lib/sentry/logger.rb +20 -0
  36. data/lib/sentry/rack.rb +4 -0
  37. data/lib/sentry/rack/capture_exceptions.rb +68 -0
  38. data/lib/sentry/rack/deprecations.rb +19 -0
  39. data/lib/sentry/rake.rb +17 -0
  40. data/lib/sentry/scope.rb +210 -0
  41. data/lib/sentry/span.rb +133 -0
  42. data/lib/sentry/transaction.rb +157 -0
  43. data/lib/sentry/transaction_event.rb +29 -0
  44. data/lib/sentry/transport.rb +88 -0
  45. data/lib/sentry/transport/configuration.rb +21 -0
  46. data/lib/sentry/transport/dummy_transport.rb +14 -0
  47. data/lib/sentry/transport/http_transport.rb +62 -0
  48. data/lib/sentry/utils/argument_checking_helper.rb +11 -0
  49. data/lib/sentry/utils/exception_cause_chain.rb +20 -0
  50. data/lib/sentry/utils/real_ip.rb +70 -0
  51. data/lib/sentry/utils/request_id.rb +16 -0
  52. data/lib/sentry/version.rb +3 -0
  53. data/sentry-ruby-core.gemspec +27 -0
  54. data/sentry-ruby.gemspec +23 -0
  55. metadata +128 -0
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class TransactionEvent < Event
5
+ ATTRIBUTES = %i(
6
+ event_id level timestamp start_timestamp
7
+ release environment server_name modules
8
+ user tags contexts extra
9
+ transaction platform sdk type
10
+ )
11
+
12
+ attr_accessor(*ATTRIBUTES)
13
+ attr_accessor :spans
14
+
15
+ def start_timestamp=(time)
16
+ @start_timestamp = time.is_a?(Time) ? time.to_f : time
17
+ end
18
+
19
+ def type
20
+ "transaction"
21
+ end
22
+
23
+ def to_hash
24
+ data = super
25
+ data[:spans] = @spans.map(&:to_hash) if @spans
26
+ data
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,88 @@
1
+ require "json"
2
+ require "base64"
3
+
4
+ module Sentry
5
+ class Transport
6
+ PROTOCOL_VERSION = '5'
7
+ USER_AGENT = "sentry-ruby/#{Sentry::VERSION}"
8
+
9
+ attr_accessor :configuration
10
+
11
+ def initialize(configuration)
12
+ @configuration = configuration
13
+ @transport_configuration = configuration.transport
14
+ @dsn = configuration.dsn
15
+ end
16
+
17
+ def send_data(data, options = {})
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def send_event(event)
22
+ unless configuration.sending_allowed?
23
+ configuration.logger.debug(LOGGER_PROGNAME) { "Event not sent: #{configuration.error_messages}" }
24
+ return
25
+ end
26
+
27
+ encoded_data = prepare_encoded_event(event)
28
+
29
+ return nil unless encoded_data
30
+
31
+ send_data(encoded_data)
32
+
33
+ event
34
+ rescue => e
35
+ failed_for_exception(e, event)
36
+ nil
37
+ end
38
+
39
+ def generate_auth_header
40
+ now = Sentry.utc_now.to_i
41
+ fields = {
42
+ 'sentry_version' => PROTOCOL_VERSION,
43
+ 'sentry_client' => USER_AGENT,
44
+ 'sentry_timestamp' => now,
45
+ 'sentry_key' => @dsn.public_key
46
+ }
47
+ fields['sentry_secret'] = @dsn.secret_key if @dsn.secret_key
48
+ 'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
49
+ end
50
+
51
+ def encode(event_hash)
52
+ event_id = event_hash[:event_id] || event_hash['event_id']
53
+ event_type = event_hash[:type] || event_hash['type']
54
+
55
+ envelope = <<~ENVELOPE
56
+ {"event_id":"#{event_id}","dsn":"#{configuration.dsn.to_s}","sdk":#{Sentry.sdk_meta.to_json},"sent_at":"#{Sentry.utc_now.iso8601}"}
57
+ {"type":"#{event_type}","content_type":"application/json"}
58
+ #{JSON.generate(event_hash)}
59
+ ENVELOPE
60
+
61
+ envelope
62
+ end
63
+
64
+ private
65
+
66
+ def prepare_encoded_event(event)
67
+ # Convert to hash
68
+ event_hash = event.to_hash
69
+
70
+ event_id = event_hash[:event_id] || event_hash["event_id"]
71
+ event_type = event_hash[:type] || event_hash["type"]
72
+ configuration.logger.info(LOGGER_PROGNAME) { "Sending #{event_type} #{event_id} to Sentry" }
73
+ encode(event_hash)
74
+ end
75
+
76
+ def failed_for_exception(e, event)
77
+ configuration.logger.warn(LOGGER_PROGNAME) { "Unable to record event with remote Sentry server (#{e.class} - #{e.message}):\n#{e.backtrace[0..10].join("\n")}" }
78
+ log_not_sending(event)
79
+ end
80
+
81
+ def log_not_sending(event)
82
+ configuration.logger.warn(LOGGER_PROGNAME) { "Failed to submit event. Unreported Event: #{Event.get_log_message(event.to_hash)}" }
83
+ end
84
+ end
85
+ end
86
+
87
+ require "sentry/transport/dummy_transport"
88
+ require "sentry/transport/http_transport"
@@ -0,0 +1,21 @@
1
+ module Sentry
2
+ class Transport
3
+ class Configuration
4
+ attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :http_adapter, :faraday_builder, :transport_class
5
+
6
+ def initialize
7
+ @ssl_verification = true
8
+ @open_timeout = 1
9
+ @timeout = 2
10
+ end
11
+
12
+ def transport_class=(klass)
13
+ unless klass.is_a?(Class)
14
+ raise Sentry::Error.new("config.transport.transport_class must a class. got: #{klass.class}")
15
+ end
16
+
17
+ @transport_class = klass
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Sentry
2
+ class DummyTransport < Transport
3
+ attr_accessor :events
4
+
5
+ def initialize(*)
6
+ super
7
+ @events = []
8
+ end
9
+
10
+ def send_event(event)
11
+ @events << event
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,62 @@
1
+ require 'faraday'
2
+
3
+ module Sentry
4
+ class HTTPTransport < Transport
5
+ CONTENT_TYPE = 'application/json'
6
+ attr_reader :conn, :adapter
7
+
8
+ def initialize(*args)
9
+ super
10
+ @adapter = @transport_configuration.http_adapter || Faraday.default_adapter
11
+ @conn = set_conn
12
+ @endpoint = @dsn.envelope_endpoint
13
+ end
14
+
15
+ def send_data(data)
16
+ conn.post @endpoint do |req|
17
+ req.headers['Content-Type'] = CONTENT_TYPE
18
+ req.headers['X-Sentry-Auth'] = generate_auth_header
19
+ req.body = data
20
+ end
21
+ rescue Faraday::Error => e
22
+ error_info = e.message
23
+
24
+ if e.response
25
+ error_info += "\nbody: #{e.response[:body]}"
26
+ error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}" if e.response[:headers]['x-sentry-error']
27
+ end
28
+
29
+ raise Sentry::Error, error_info
30
+ end
31
+
32
+ private
33
+
34
+ def set_conn
35
+ server = @dsn.server
36
+
37
+ configuration.logger.debug(LOGGER_PROGNAME) { "Sentry HTTP Transport connecting to #{server}" }
38
+
39
+ Faraday.new(server, :ssl => ssl_configuration, :proxy => @transport_configuration.proxy) do |builder|
40
+ @transport_configuration.faraday_builder&.call(builder)
41
+ builder.response :raise_error
42
+ builder.options.merge! faraday_opts
43
+ builder.headers[:user_agent] = "sentry-ruby/#{Sentry::VERSION}"
44
+ builder.adapter(*adapter)
45
+ end
46
+ end
47
+
48
+ # TODO: deprecate and replace where possible w/Faraday Builder
49
+ def faraday_opts
50
+ [:timeout, :open_timeout].each_with_object({}) do |opt, memo|
51
+ memo[opt] = @transport_configuration.public_send(opt) if @transport_configuration.public_send(opt)
52
+ end
53
+ end
54
+
55
+ def ssl_configuration
56
+ (@transport_configuration.ssl || {}).merge(
57
+ :verify => @transport_configuration.ssl_verification,
58
+ :ca_file => @transport_configuration.ssl_ca_file
59
+ )
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,11 @@
1
+ module Sentry
2
+ module ArgumentCheckingHelper
3
+ private
4
+
5
+ def check_argument_type!(argument, expected_type)
6
+ unless argument.is_a?(expected_type)
7
+ raise ArgumentError, "expect the argument to be a #{expected_type}, got #{argument.class} (#{argument.inspect})"
8
+ end
9
+ end
10
+ end
11
+ 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,70 @@
1
+ require 'ipaddr'
2
+
3
+ # Based on ActionDispatch::RemoteIp. All security-related precautions from that
4
+ # middleware have been removed, because the Event IP just needs to be accurate,
5
+ # and spoofing an IP here only makes data inaccurate, not insecure. Don't re-use
6
+ # this module if you have to *trust* the IP address.
7
+ module Sentry
8
+ module Utils
9
+ class RealIp
10
+ LOCAL_ADDRESSES = [
11
+ "127.0.0.1", # localhost IPv4
12
+ "::1", # localhost IPv6
13
+ "fc00::/7", # private IPv6 range fc00::/7
14
+ "10.0.0.0/8", # private IPv4 range 10.x.x.x
15
+ "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
16
+ "192.168.0.0/16" # private IPv4 range 192.168.x.x
17
+ ].map { |proxy| IPAddr.new(proxy) }
18
+
19
+ attr_reader :ip
20
+
21
+ def initialize(
22
+ remote_addr: nil,
23
+ client_ip: nil,
24
+ real_ip: nil,
25
+ forwarded_for: nil
26
+ )
27
+ @remote_addr = remote_addr
28
+ @client_ip = client_ip
29
+ @real_ip = real_ip
30
+ @forwarded_for = forwarded_for
31
+ end
32
+
33
+ def calculate_ip
34
+ # CGI environment variable set by Rack
35
+ remote_addr = ips_from(@remote_addr).last
36
+
37
+ # Could be a CSV list and/or repeated headers that were concatenated.
38
+ client_ips = ips_from(@client_ip)
39
+ real_ips = ips_from(@real_ip)
40
+ forwarded_ips = ips_from(@forwarded_for)
41
+
42
+ ips = [client_ips, real_ips, forwarded_ips, remote_addr].flatten.compact
43
+
44
+ # If every single IP option is in the trusted list, just return REMOTE_ADDR
45
+ @ip = filter_local_addresses(ips).first || remote_addr
46
+ end
47
+
48
+ protected
49
+
50
+ def ips_from(header)
51
+ # Split the comma-separated list into an array of strings
52
+ ips = header ? header.strip.split(/[,\s]+/) : []
53
+ ips.select do |ip|
54
+ begin
55
+ # Only return IPs that are valid according to the IPAddr#new method
56
+ range = IPAddr.new(ip).to_range
57
+ # we want to make sure nobody is sneaking a netmask in
58
+ range.begin == range.end
59
+ rescue ArgumentError
60
+ nil
61
+ end
62
+ end
63
+ end
64
+
65
+ def filter_local_addresses(ips)
66
+ ips.reject { |ip| LOCAL_ADDRESSES.any? { |proxy| proxy === ip } }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,16 @@
1
+ module Sentry
2
+ module Utils
3
+ module RequestId
4
+ REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
5
+
6
+ # Request ID based on ActionDispatch::RequestId
7
+ def self.read_from(env)
8
+ REQUEST_ID_HEADERS.each do |key|
9
+ request_id = env[key]
10
+ return request_id if request_id
11
+ end
12
+ nil
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Sentry
2
+ VERSION = "4.1.5-beta.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ require_relative "lib/sentry/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "sentry-ruby-core"
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/sentry-ruby"
11
+
12
+ spec.platform = Gem::Platform::RUBY
13
+ spec.required_ruby_version = '>= 2.4'
14
+ spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
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"
26
+ spec.add_dependency "concurrent-ruby"
27
+ end
@@ -0,0 +1,23 @@
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/sentry-ruby"
11
+
12
+ spec.platform = Gem::Platform::RUBY
13
+ spec.required_ruby_version = '>= 2.4'
14
+ spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
19
+
20
+ spec.add_dependency "sentry-ruby-core", Sentry::VERSION
21
+ spec.add_dependency "faraday", ">= 1.0"
22
+ spec.add_dependency "concurrent-ruby", '~> 1.0', '>= 1.0.2'
23
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sentry-ruby-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 4.1.5.pre.beta.0
5
+ platform: ruby
6
+ authors:
7
+ - Sentry Team
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A gem that provides a client interface for the Sentry error logger
42
+ email: accounts@sentry.io
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files:
46
+ - README.md
47
+ - LICENSE.txt
48
+ files:
49
+ - ".craft.yml"
50
+ - ".gitignore"
51
+ - ".rspec"
52
+ - ".travis.yml"
53
+ - CHANGELOG.md
54
+ - CODE_OF_CONDUCT.md
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - bin/console
60
+ - bin/setup
61
+ - lib/sentry-ruby.rb
62
+ - lib/sentry/background_worker.rb
63
+ - lib/sentry/backtrace.rb
64
+ - lib/sentry/benchmarks/benchmark_transport.rb
65
+ - lib/sentry/breadcrumb.rb
66
+ - lib/sentry/breadcrumb/sentry_logger.rb
67
+ - lib/sentry/breadcrumb_buffer.rb
68
+ - lib/sentry/client.rb
69
+ - lib/sentry/configuration.rb
70
+ - lib/sentry/core_ext/object/deep_dup.rb
71
+ - lib/sentry/core_ext/object/duplicable.rb
72
+ - lib/sentry/dsn.rb
73
+ - lib/sentry/event.rb
74
+ - lib/sentry/hub.rb
75
+ - lib/sentry/integrable.rb
76
+ - lib/sentry/interface.rb
77
+ - lib/sentry/interfaces/exception.rb
78
+ - lib/sentry/interfaces/request.rb
79
+ - lib/sentry/interfaces/single_exception.rb
80
+ - lib/sentry/interfaces/stacktrace.rb
81
+ - lib/sentry/linecache.rb
82
+ - lib/sentry/logger.rb
83
+ - lib/sentry/rack.rb
84
+ - lib/sentry/rack/capture_exceptions.rb
85
+ - lib/sentry/rack/deprecations.rb
86
+ - lib/sentry/rake.rb
87
+ - lib/sentry/scope.rb
88
+ - lib/sentry/span.rb
89
+ - lib/sentry/transaction.rb
90
+ - lib/sentry/transaction_event.rb
91
+ - lib/sentry/transport.rb
92
+ - lib/sentry/transport/configuration.rb
93
+ - lib/sentry/transport/dummy_transport.rb
94
+ - lib/sentry/transport/http_transport.rb
95
+ - lib/sentry/utils/argument_checking_helper.rb
96
+ - lib/sentry/utils/exception_cause_chain.rb
97
+ - lib/sentry/utils/real_ip.rb
98
+ - lib/sentry/utils/request_id.rb
99
+ - lib/sentry/version.rb
100
+ - sentry-ruby-core.gemspec
101
+ - sentry-ruby.gemspec
102
+ homepage: https://github.com/getsentry/sentry-ruby
103
+ licenses:
104
+ - Apache-2.0
105
+ metadata:
106
+ homepage_uri: https://github.com/getsentry/sentry-ruby
107
+ source_code_uri: https://github.com/getsentry/sentry-ruby
108
+ changelog_uri: https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '2.4'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">"
121
+ - !ruby/object:Gem::Version
122
+ version: 1.3.1
123
+ requirements: []
124
+ rubygems_version: 3.0.3
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: A gem that provides a client interface for the Sentry error logger
128
+ test_files: []