sentry-ruby 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.craft.yml +18 -0
  3. data/.gitignore +11 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +6 -0
  6. data/CHANGELOG.md +10 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +44 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/sentry.rb +97 -0
  15. data/lib/sentry/backtrace.rb +128 -0
  16. data/lib/sentry/breadcrumb.rb +25 -0
  17. data/lib/sentry/breadcrumb/sentry_logger.rb +103 -0
  18. data/lib/sentry/breadcrumb_buffer.rb +50 -0
  19. data/lib/sentry/client.rb +85 -0
  20. data/lib/sentry/configuration.rb +401 -0
  21. data/lib/sentry/core_ext/object/deep_dup.rb +57 -0
  22. data/lib/sentry/core_ext/object/duplicable.rb +153 -0
  23. data/lib/sentry/dsn.rb +45 -0
  24. data/lib/sentry/event.rb +175 -0
  25. data/lib/sentry/event/options.rb +31 -0
  26. data/lib/sentry/hub.rb +126 -0
  27. data/lib/sentry/interface.rb +22 -0
  28. data/lib/sentry/interfaces/exception.rb +11 -0
  29. data/lib/sentry/interfaces/request.rb +104 -0
  30. data/lib/sentry/interfaces/single_exception.rb +14 -0
  31. data/lib/sentry/interfaces/stacktrace.rb +57 -0
  32. data/lib/sentry/linecache.rb +44 -0
  33. data/lib/sentry/logger.rb +20 -0
  34. data/lib/sentry/rack.rb +4 -0
  35. data/lib/sentry/rack/capture_exception.rb +45 -0
  36. data/lib/sentry/ruby.rb +1 -0
  37. data/lib/sentry/scope.rb +192 -0
  38. data/lib/sentry/transport.rb +110 -0
  39. data/lib/sentry/transport/configuration.rb +28 -0
  40. data/lib/sentry/transport/dummy_transport.rb +14 -0
  41. data/lib/sentry/transport/http_transport.rb +62 -0
  42. data/lib/sentry/transport/state.rb +40 -0
  43. data/lib/sentry/utils/deep_merge.rb +22 -0
  44. data/lib/sentry/utils/exception_cause_chain.rb +20 -0
  45. data/lib/sentry/utils/real_ip.rb +70 -0
  46. data/lib/sentry/version.rb +3 -0
  47. data/sentry-ruby.gemspec +26 -0
  48. metadata +107 -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,45 @@
1
+ require "uri"
2
+
3
+ module Sentry
4
+ class DSN
5
+ attr_reader :scheme, :project_id, :public_key, :secret_key, :host, :port, :path
6
+
7
+ def initialize(dsn_string)
8
+ @raw_value = dsn_string
9
+
10
+ uri = URI.parse(dsn_string)
11
+ uri_path = uri.path.split('/')
12
+
13
+ if uri.user
14
+ # DSN-style string
15
+ @project_id = uri_path.pop
16
+ @public_key = uri.user
17
+ @secret_key = !(uri.password.nil? || uri.password.empty?) ? uri.password : nil
18
+ end
19
+
20
+ @scheme = uri.scheme
21
+ @host = uri.host
22
+ @port = uri.port if uri.port
23
+ @path = uri_path.join('/')
24
+ end
25
+
26
+ def valid?
27
+ %w(host path public_key project_id).all? { |k| public_send(k) }
28
+ end
29
+
30
+ def to_s
31
+ @raw_value
32
+ end
33
+
34
+ def server
35
+ server = "#{scheme}://#{host}"
36
+ server += ":#{port}" unless port == { 'http' => 80, 'https' => 443 }[scheme]
37
+ server += path
38
+ server
39
+ end
40
+
41
+ def envelope_endpoint
42
+ "#{path}/api/#{project_id}/envelope/"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'securerandom'
5
+ require 'sentry/event/options'
6
+ require 'sentry/interface'
7
+ require 'sentry/backtrace'
8
+ require 'sentry/utils/deep_merge'
9
+ require 'sentry/utils/real_ip'
10
+
11
+ module Sentry
12
+ class Event
13
+ ATTRIBUTES = %i(
14
+ event_id level timestamp
15
+ release environment server_name modules
16
+ message user tags contexts extra
17
+ fingerprint breadcrumbs backtrace transaction
18
+ platform sdk
19
+ )
20
+
21
+ attr_accessor(*ATTRIBUTES)
22
+ attr_reader :id, :configuration
23
+
24
+ alias event_id id
25
+
26
+ def initialize(options:, configuration:)
27
+ # this needs to go first because some setters rely on configuration
28
+ @configuration = configuration
29
+
30
+ # Set some simple default values
31
+ @id = SecureRandom.uuid.delete("-")
32
+ @timestamp = Time.now.utc
33
+ @platform = :ruby
34
+ @sdk = Sentry.sdk_meta
35
+
36
+ @user = options.user
37
+ @extra = options.extra
38
+ @contexts = options.contexts
39
+ @tags = configuration.tags.merge(options.tags)
40
+
41
+ @fingerprint = options.fingerprint
42
+
43
+ @server_name = options.server_name || configuration.server_name
44
+ @environment = options.environment || configuration.current_environment
45
+ @release = options.release || configuration.release
46
+ @modules = list_gem_specs if configuration.send_modules
47
+
48
+ @message = options.message if options.message
49
+
50
+ self.level = options.level
51
+
52
+ if !options.backtrace.empty?
53
+ @stacktrace = Sentry::StacktraceInterface.new.tap do |int|
54
+ int.frames = stacktrace_interface_from(options.backtrace)
55
+ end
56
+ end
57
+ end
58
+
59
+ class << self
60
+ def get_log_message(event_hash)
61
+ message = event_hash[:message] || event_hash['message']
62
+ message = get_message_from_exception(event_hash) if message.empty?
63
+ message = '<no message value>' if message.empty?
64
+ message
65
+ end
66
+
67
+ def get_message_from_exception(event_hash)
68
+ (
69
+ event_hash &&
70
+ event_hash[:exception] &&
71
+ event_hash[:exception][:values] &&
72
+ event_hash[:exception][:values][0] &&
73
+ "#{event_hash[:exception][:values][0][:type]}: #{event_hash[:exception][:values][0][:value]}"
74
+ )
75
+ end
76
+ end
77
+
78
+ def timestamp=(time)
79
+ @timestamp = time.is_a?(Time) ? time.strftime('%Y-%m-%dT%H:%M:%S') : time
80
+ end
81
+
82
+ def level=(new_level) # needed to meet the Sentry spec
83
+ @level = new_level.to_s == "warn" ? :warning : new_level
84
+ end
85
+
86
+ def rack_env=(env)
87
+ unless @request || env.empty?
88
+ @request = Sentry::RequestInterface.new.tap do |int|
89
+ int.from_rack(env)
90
+ end
91
+
92
+ if configuration.send_default_pii && ip = calculate_real_ip_from_rack(env.dup)
93
+ user[:ip_address] = ip
94
+ end
95
+ end
96
+ end
97
+
98
+ def to_hash
99
+ data = ATTRIBUTES.each_with_object({}) do |att, memo|
100
+ memo[att] = public_send(att) if public_send(att)
101
+ end
102
+
103
+ data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
104
+ data[:stacktrace] = @stacktrace.to_hash if @stacktrace
105
+ data[:request] = @request.to_hash if @request
106
+ data[:exception] = @exception.to_hash if @exception
107
+
108
+ data
109
+ end
110
+
111
+ def to_json_compatible
112
+ JSON.parse(JSON.generate(to_hash))
113
+ end
114
+
115
+ def add_exception_interface(exc)
116
+ @exception = Sentry::ExceptionInterface.new.tap do |exc_int|
117
+ exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
118
+ backtraces = Set.new
119
+ exc_int.values = exceptions.map do |e|
120
+ SingleExceptionInterface.new.tap do |int|
121
+ int.type = e.class.to_s
122
+ int.value = e.to_s
123
+ int.module = e.class.to_s.split('::')[0...-1].join('::')
124
+
125
+ int.stacktrace =
126
+ if e.backtrace && !backtraces.include?(e.backtrace.object_id)
127
+ backtraces << e.backtrace.object_id
128
+ StacktraceInterface.new.tap do |stacktrace|
129
+ stacktrace.frames = stacktrace_interface_from(e.backtrace)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ def stacktrace_interface_from(backtrace)
138
+ project_root = configuration.project_root.to_s
139
+
140
+ Backtrace.parse(backtrace, configuration: configuration).lines.reverse.each_with_object([]) do |line, memo|
141
+ frame = StacktraceInterface::Frame.new(project_root)
142
+ frame.abs_path = line.file if line.file
143
+ frame.function = line.method if line.method
144
+ frame.lineno = line.number
145
+ frame.in_app = line.in_app
146
+ frame.module = line.module_name if line.module_name
147
+
148
+ if configuration[:context_lines] && frame.abs_path
149
+ frame.pre_context, frame.context_line, frame.post_context = \
150
+ configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration[:context_lines])
151
+ end
152
+
153
+ memo << frame if frame.filename
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ # When behind a proxy (or if the user is using a proxy), we can't use
160
+ # REMOTE_ADDR to determine the Event IP, and must use other headers instead.
161
+ def calculate_real_ip_from_rack(env)
162
+ Utils::RealIp.new(
163
+ :remote_addr => env["REMOTE_ADDR"],
164
+ :client_ip => env["HTTP_CLIENT_IP"],
165
+ :real_ip => env["HTTP_X_REAL_IP"],
166
+ :forwarded_for => env["HTTP_X_FORWARDED_FOR"]
167
+ ).calculate_ip
168
+ end
169
+
170
+ def list_gem_specs
171
+ # Older versions of Rubygems don't support iterating over all specs
172
+ Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,31 @@
1
+ module Sentry
2
+ class Event
3
+ class Options
4
+ attr_reader :message,
5
+ :user, :extra, :tags, :contexts,
6
+ :backtrace, :level, :fingerprint,
7
+ :server_name, :release, :environment
8
+
9
+ def initialize(
10
+ message: "",
11
+ user: {}, extra: {}, tags: {}, contexts: {},
12
+ backtrace: [], level: :error, fingerprint: [],
13
+ # nilable attributes because we'll fallback to the configuration's values
14
+ server_name: nil, release: nil, environment: nil
15
+ )
16
+ @message = message || ""
17
+ @user = user || {}
18
+ @extra = extra || {}
19
+ @tags = tags || {}
20
+ @contexts = contexts || {}
21
+ @backtrace = backtrace || []
22
+ @fingerprint = fingerprint || []
23
+ @level = level || :error
24
+ @server_name = server_name
25
+ @environment = environment
26
+ @release = release
27
+ end
28
+ end
29
+ end
30
+ end
31
+