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

Sign up to get free protection for your applications and to get access to all the features.
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,57 @@
1
+ require 'sentry/core_ext/object/duplicable'
2
+
3
+ #########################################
4
+ # This file was copied from Rails 5.2 #
5
+ #########################################
6
+
7
+ class Object
8
+ # Returns a deep copy of object if it's duplicable. If it's
9
+ # not duplicable, returns +self+.
10
+ #
11
+ # object = Object.new
12
+ # dup = object.deep_dup
13
+ # dup.instance_variable_set(:@a, 1)
14
+ #
15
+ # object.instance_variable_defined?(:@a) # => false
16
+ # dup.instance_variable_defined?(:@a) # => true
17
+ def deep_dup
18
+ duplicable? ? dup : self
19
+ end
20
+ end
21
+
22
+ class Array
23
+ # Returns a deep copy of array.
24
+ #
25
+ # array = [1, [2, 3]]
26
+ # dup = array.deep_dup
27
+ # dup[1][2] = 4
28
+ #
29
+ # array[1][2] # => nil
30
+ # dup[1][2] # => 4
31
+ def deep_dup
32
+ map(&:deep_dup)
33
+ end
34
+ end
35
+
36
+ class Hash
37
+ # Returns a deep copy of hash.
38
+ #
39
+ # hash = { a: { b: 'b' } }
40
+ # dup = hash.deep_dup
41
+ # dup[:a][:c] = 'c'
42
+ #
43
+ # hash[:a][:c] # => nil
44
+ # dup[:a][:c] # => "c"
45
+ def deep_dup
46
+ hash = dup
47
+ each_pair do |key, value|
48
+ if key.frozen? && ::String === key
49
+ hash[key] = value.deep_dup
50
+ else
51
+ hash.delete(key)
52
+ hash[key.deep_dup] = value.deep_dup
53
+ end
54
+ end
55
+ hash
56
+ end
57
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ #########################################
4
+ # This file was copied from Rails 5.2 #
5
+ #########################################
6
+
7
+ #--
8
+ # Most objects are cloneable, but not all. For example you can't dup methods:
9
+ #
10
+ # method(:puts).dup # => TypeError: allocator undefined for Method
11
+ #
12
+ # Classes may signal their instances are not duplicable removing +dup+/+clone+
13
+ # or raising exceptions from them. So, to dup an arbitrary object you normally
14
+ # use an optimistic approach and are ready to catch an exception, say:
15
+ #
16
+ # arbitrary_object.dup rescue object
17
+ #
18
+ # Rails dups objects in a few critical spots where they are not that arbitrary.
19
+ # That rescue is very expensive (like 40 times slower than a predicate), and it
20
+ # is often triggered.
21
+ #
22
+ # That's why we hardcode the following cases and check duplicable? instead of
23
+ # using that rescue idiom.
24
+ #++
25
+ class Object
26
+ # Can you safely dup this object?
27
+ #
28
+ # False for method objects;
29
+ # true otherwise.
30
+ def duplicable?
31
+ true
32
+ end
33
+ end
34
+
35
+ class NilClass
36
+ begin
37
+ nil.dup
38
+ rescue TypeError
39
+ # +nil+ is not duplicable:
40
+ #
41
+ # nil.duplicable? # => false
42
+ # nil.dup # => TypeError: can't dup NilClass
43
+ def duplicable?
44
+ false
45
+ end
46
+ end
47
+ end
48
+
49
+ class FalseClass
50
+ begin
51
+ false.dup
52
+ rescue TypeError
53
+ # +false+ is not duplicable:
54
+ #
55
+ # false.duplicable? # => false
56
+ # false.dup # => TypeError: can't dup FalseClass
57
+ def duplicable?
58
+ false
59
+ end
60
+ end
61
+ end
62
+
63
+ class TrueClass
64
+ begin
65
+ true.dup
66
+ rescue TypeError
67
+ # +true+ is not duplicable:
68
+ #
69
+ # true.duplicable? # => false
70
+ # true.dup # => TypeError: can't dup TrueClass
71
+ def duplicable?
72
+ false
73
+ end
74
+ end
75
+ end
76
+
77
+ class Symbol
78
+ begin
79
+ :symbol.dup # Ruby 2.4.x.
80
+ "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
81
+ rescue TypeError
82
+ # Symbols are not duplicable:
83
+ #
84
+ # :my_symbol.duplicable? # => false
85
+ # :my_symbol.dup # => TypeError: can't dup Symbol
86
+ def duplicable?
87
+ false
88
+ end
89
+ end
90
+ end
91
+
92
+ class Numeric
93
+ begin
94
+ 1.dup
95
+ rescue TypeError
96
+ # Numbers are not duplicable:
97
+ #
98
+ # 3.duplicable? # => false
99
+ # 3.dup # => TypeError: can't dup Integer
100
+ def duplicable?
101
+ false
102
+ end
103
+ end
104
+ end
105
+
106
+ require "bigdecimal"
107
+ class BigDecimal
108
+ # BigDecimals are duplicable:
109
+ #
110
+ # BigDecimal("1.2").duplicable? # => true
111
+ # BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
112
+ def duplicable?
113
+ true
114
+ end
115
+ end
116
+
117
+ class Method
118
+ # Methods are not duplicable:
119
+ #
120
+ # method(:puts).duplicable? # => false
121
+ # method(:puts).dup # => TypeError: allocator undefined for Method
122
+ def duplicable?
123
+ false
124
+ end
125
+ end
126
+
127
+ class Complex
128
+ begin
129
+ Complex(1).dup
130
+ rescue TypeError
131
+ # Complexes are not duplicable:
132
+ #
133
+ # Complex(1).duplicable? # => false
134
+ # Complex(1).dup # => TypeError: can't copy Complex
135
+ def duplicable?
136
+ false
137
+ end
138
+ end
139
+ end
140
+
141
+ class Rational
142
+ begin
143
+ Rational(1).dup
144
+ rescue TypeError
145
+ # Rationals are not duplicable:
146
+ #
147
+ # Rational(1).duplicable? # => false
148
+ # Rational(1).dup # => TypeError: can't copy Rational
149
+ def duplicable?
150
+ false
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,48 @@
1
+ require "uri"
2
+
3
+ module Sentry
4
+ class DSN
5
+ PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
6
+ REQUIRED_ATTRIBUTES = %w(host path public_key project_id).freeze
7
+
8
+ attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
9
+
10
+ def initialize(dsn_string)
11
+ @raw_value = dsn_string
12
+
13
+ uri = URI.parse(dsn_string)
14
+ uri_path = uri.path.split('/')
15
+
16
+ if uri.user
17
+ # DSN-style string
18
+ @project_id = uri_path.pop
19
+ @public_key = uri.user
20
+ @secret_key = !(uri.password.nil? || uri.password.empty?) ? uri.password : nil
21
+ end
22
+
23
+ @scheme = uri.scheme
24
+ @host = uri.host
25
+ @port = uri.port if uri.port
26
+ @path = uri_path.join('/')
27
+ end
28
+
29
+ def valid?
30
+ REQUIRED_ATTRIBUTES.all? { |k| public_send(k) }
31
+ end
32
+
33
+ def to_s
34
+ @raw_value
35
+ end
36
+
37
+ def server
38
+ server = "#{scheme}://#{host}"
39
+ server += ":#{port}" unless port == PORT_MAP[scheme]
40
+ server += path
41
+ server
42
+ end
43
+
44
+ def envelope_endpoint
45
+ "#{path}/api/#{project_id}/envelope/"
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'securerandom'
5
+ require 'sentry/interface'
6
+ require 'sentry/backtrace'
7
+ require 'sentry/utils/real_ip'
8
+ require 'sentry/utils/request_id'
9
+
10
+ module Sentry
11
+ class Event
12
+ ATTRIBUTES = %i(
13
+ event_id level timestamp
14
+ release environment server_name modules
15
+ message user tags contexts extra
16
+ fingerprint breadcrumbs backtrace transaction
17
+ platform sdk type
18
+ )
19
+
20
+ MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
21
+
22
+ attr_accessor(*ATTRIBUTES)
23
+ attr_reader :configuration, :request, :exception, :stacktrace
24
+
25
+ def initialize(configuration:, integration_meta: nil, message: nil)
26
+ # this needs to go first because some setters rely on configuration
27
+ @configuration = configuration
28
+
29
+ # Set some simple default values
30
+ @event_id = SecureRandom.uuid.delete("-")
31
+ @timestamp = Sentry.utc_now.iso8601
32
+ @platform = :ruby
33
+ @sdk = integration_meta || Sentry.sdk_meta
34
+
35
+ @user = {}
36
+ @extra = {}
37
+ @contexts = {}
38
+ @tags = {}
39
+
40
+ @fingerprint = []
41
+
42
+ @server_name = configuration.server_name
43
+ @environment = configuration.environment
44
+ @release = configuration.release
45
+ @modules = configuration.gem_specs if configuration.send_modules
46
+
47
+ @message = (message || "").byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
48
+
49
+ self.level = :error
50
+ end
51
+
52
+ class << self
53
+ def get_log_message(event_hash)
54
+ message = event_hash[:message] || event_hash['message']
55
+ message = get_message_from_exception(event_hash) if message.nil? || message.empty?
56
+ message = '<no message value>' if message.nil? || message.empty?
57
+ message
58
+ end
59
+
60
+ def get_message_from_exception(event_hash)
61
+ (
62
+ event_hash &&
63
+ event_hash[:exception] &&
64
+ event_hash[:exception][:values] &&
65
+ event_hash[:exception][:values][0] &&
66
+ "#{event_hash[:exception][:values][0][:type]}: #{event_hash[:exception][:values][0][:value]}"
67
+ )
68
+ end
69
+ end
70
+
71
+ def timestamp=(time)
72
+ @timestamp = time.is_a?(Time) ? time.to_f : time
73
+ end
74
+
75
+ def level=(new_level) # needed to meet the Sentry spec
76
+ @level = new_level.to_s == "warn" ? :warning : new_level
77
+ end
78
+
79
+ def rack_env=(env)
80
+ unless request || env.empty?
81
+ env = env.dup
82
+
83
+ add_request_interface(env)
84
+
85
+ if configuration.send_default_pii
86
+ user[:ip_address] = calculate_real_ip_from_rack(env)
87
+ end
88
+
89
+ if request_id = Utils::RequestId.read_from(env)
90
+ tags[:request_id] = request_id
91
+ end
92
+ end
93
+ end
94
+
95
+ def type
96
+ "event"
97
+ end
98
+
99
+ def to_hash
100
+ data = serialize_attributes
101
+ data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
102
+ data[:stacktrace] = stacktrace.to_hash if stacktrace
103
+ data[:request] = request.to_hash if request
104
+ data[:exception] = exception.to_hash if exception
105
+
106
+ data
107
+ end
108
+
109
+ def to_json_compatible
110
+ JSON.parse(JSON.generate(to_hash))
111
+ end
112
+
113
+ def add_request_interface(env)
114
+ @request = Sentry::RequestInterface.from_rack(env)
115
+ end
116
+
117
+ def add_exception_interface(exc)
118
+ if exc.respond_to?(:sentry_context)
119
+ @extra.merge!(exc.sentry_context)
120
+ end
121
+
122
+ @exception = Sentry::ExceptionInterface.new.tap do |exc_int|
123
+ exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
124
+ backtraces = Set.new
125
+ exc_int.values = exceptions.map do |e|
126
+ SingleExceptionInterface.new.tap do |int|
127
+ int.type = e.class.to_s
128
+ int.value = e.message.byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
129
+ int.module = e.class.to_s.split('::')[0...-1].join('::')
130
+
131
+ int.stacktrace =
132
+ if e.backtrace && !backtraces.include?(e.backtrace.object_id)
133
+ backtraces << e.backtrace.object_id
134
+ initialize_stacktrace_interface(e.backtrace)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def initialize_stacktrace_interface(backtrace)
142
+ StacktraceInterface.new(
143
+ backtrace: backtrace,
144
+ project_root: configuration.project_root.to_s,
145
+ app_dirs_pattern: configuration.app_dirs_pattern,
146
+ linecache: configuration.linecache,
147
+ context_lines: configuration.context_lines,
148
+ backtrace_cleanup_callback: configuration.backtrace_cleanup_callback
149
+ )
150
+ end
151
+
152
+ private
153
+
154
+ def serialize_attributes
155
+ self.class::ATTRIBUTES.each_with_object({}) do |att, memo|
156
+ if value = public_send(att)
157
+ memo[att] = value
158
+ end
159
+ end
160
+ end
161
+
162
+ # When behind a proxy (or if the user is using a proxy), we can't use
163
+ # REMOTE_ADDR to determine the Event IP, and must use other headers instead.
164
+ def calculate_real_ip_from_rack(env)
165
+ Utils::RealIp.new(
166
+ :remote_addr => env["REMOTE_ADDR"],
167
+ :client_ip => env["HTTP_CLIENT_IP"],
168
+ :real_ip => env["HTTP_X_REAL_IP"],
169
+ :forwarded_for => env["HTTP_X_FORWARDED_FOR"]
170
+ ).calculate_ip
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,143 @@
1
+ require "sentry/scope"
2
+ require "sentry/client"
3
+
4
+ module Sentry
5
+ class Hub
6
+ include ArgumentCheckingHelper
7
+
8
+ attr_reader :last_event_id
9
+
10
+ def initialize(client, scope)
11
+ first_layer = Layer.new(client, scope)
12
+ @stack = [first_layer]
13
+ @last_event_id = nil
14
+ end
15
+
16
+ def new_from_top
17
+ Hub.new(current_client, current_scope)
18
+ end
19
+
20
+ def current_client
21
+ current_layer&.client
22
+ end
23
+
24
+ def current_scope
25
+ current_layer&.scope
26
+ end
27
+
28
+ def clone
29
+ layer = current_layer
30
+
31
+ if layer
32
+ scope = layer.scope&.dup
33
+
34
+ Hub.new(layer.client, scope)
35
+ end
36
+ end
37
+
38
+ def bind_client(client)
39
+ layer = current_layer
40
+
41
+ if layer
42
+ layer.client = client
43
+ end
44
+ end
45
+
46
+ def configure_scope(&block)
47
+ block.call(current_scope)
48
+ end
49
+
50
+ def with_scope(&block)
51
+ push_scope
52
+ yield(current_scope)
53
+ ensure
54
+ pop_scope
55
+ end
56
+
57
+ def push_scope
58
+ new_scope =
59
+ if current_scope
60
+ current_scope.dup
61
+ else
62
+ Scope.new
63
+ end
64
+
65
+ @stack << Layer.new(current_client, new_scope)
66
+ end
67
+
68
+ def pop_scope
69
+ @stack.pop
70
+ end
71
+
72
+ def start_transaction(transaction: nil, **options)
73
+ transaction ||= Transaction.new(**options)
74
+ transaction.set_initial_sample_desicion
75
+ transaction
76
+ end
77
+
78
+ def capture_exception(exception, **options, &block)
79
+ return unless current_client
80
+
81
+ check_argument_type!(exception, ::Exception)
82
+
83
+ options[:hint] ||= {}
84
+ options[:hint][:exception] = exception
85
+ event = current_client.event_from_exception(exception, options[:hint])
86
+
87
+ return unless event
88
+
89
+ capture_event(event, **options, &block)
90
+ end
91
+
92
+ def capture_message(message, **options, &block)
93
+ return unless current_client
94
+
95
+ options[:hint] ||= {}
96
+ options[:hint][:message] = message
97
+ event = current_client.event_from_message(message, options[:hint])
98
+ capture_event(event, **options, &block)
99
+ end
100
+
101
+ def capture_event(event, **options, &block)
102
+ return unless current_client
103
+
104
+ check_argument_type!(event, Sentry::Event)
105
+
106
+ hint = options.delete(:hint) || {}
107
+ scope = current_scope.dup
108
+
109
+ if block
110
+ block.call(scope)
111
+ elsif custom_scope = options[:scope]
112
+ scope.update_from_scope(custom_scope)
113
+ elsif !options.empty?
114
+ scope.update_from_options(**options)
115
+ end
116
+
117
+ event = current_client.capture_event(event, scope, hint)
118
+
119
+ @last_event_id = event&.event_id
120
+ event
121
+ end
122
+
123
+ def add_breadcrumb(breadcrumb)
124
+ current_scope.add_breadcrumb(breadcrumb)
125
+ end
126
+
127
+ private
128
+
129
+ def current_layer
130
+ @stack.last
131
+ end
132
+
133
+ class Layer
134
+ attr_accessor :client
135
+ attr_reader :scope
136
+
137
+ def initialize(client, scope)
138
+ @client = client
139
+ @scope = scope
140
+ end
141
+ end
142
+ end
143
+ end