sentry-ruby 5.3.1 → 5.16.1

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +2 -0
  5. data/CHANGELOG.md +313 -0
  6. data/Gemfile +26 -0
  7. data/Makefile +4 -0
  8. data/README.md +11 -8
  9. data/Rakefile +20 -0
  10. data/bin/console +18 -0
  11. data/bin/setup +8 -0
  12. data/lib/sentry/background_worker.rb +79 -0
  13. data/lib/sentry/backpressure_monitor.rb +75 -0
  14. data/lib/sentry/backtrace.rb +124 -0
  15. data/lib/sentry/baggage.rb +70 -0
  16. data/lib/sentry/breadcrumb/sentry_logger.rb +90 -0
  17. data/lib/sentry/breadcrumb.rb +76 -0
  18. data/lib/sentry/breadcrumb_buffer.rb +64 -0
  19. data/lib/sentry/check_in_event.rb +60 -0
  20. data/lib/sentry/client.rb +248 -0
  21. data/lib/sentry/configuration.rb +650 -0
  22. data/lib/sentry/core_ext/object/deep_dup.rb +61 -0
  23. data/lib/sentry/core_ext/object/duplicable.rb +155 -0
  24. data/lib/sentry/cron/configuration.rb +23 -0
  25. data/lib/sentry/cron/monitor_check_ins.rb +75 -0
  26. data/lib/sentry/cron/monitor_config.rb +53 -0
  27. data/lib/sentry/cron/monitor_schedule.rb +42 -0
  28. data/lib/sentry/dsn.rb +53 -0
  29. data/lib/sentry/envelope.rb +93 -0
  30. data/lib/sentry/error_event.rb +38 -0
  31. data/lib/sentry/event.rb +156 -0
  32. data/lib/sentry/exceptions.rb +9 -0
  33. data/lib/sentry/hub.rb +316 -0
  34. data/lib/sentry/integrable.rb +32 -0
  35. data/lib/sentry/interface.rb +16 -0
  36. data/lib/sentry/interfaces/exception.rb +43 -0
  37. data/lib/sentry/interfaces/request.rb +134 -0
  38. data/lib/sentry/interfaces/single_exception.rb +67 -0
  39. data/lib/sentry/interfaces/stacktrace.rb +87 -0
  40. data/lib/sentry/interfaces/stacktrace_builder.rb +79 -0
  41. data/lib/sentry/interfaces/threads.rb +42 -0
  42. data/lib/sentry/linecache.rb +47 -0
  43. data/lib/sentry/logger.rb +20 -0
  44. data/lib/sentry/net/http.rb +106 -0
  45. data/lib/sentry/profiler.rb +233 -0
  46. data/lib/sentry/propagation_context.rb +134 -0
  47. data/lib/sentry/puma.rb +32 -0
  48. data/lib/sentry/rack/capture_exceptions.rb +79 -0
  49. data/lib/sentry/rack.rb +5 -0
  50. data/lib/sentry/rake.rb +28 -0
  51. data/lib/sentry/redis.rb +108 -0
  52. data/lib/sentry/release_detector.rb +39 -0
  53. data/lib/sentry/scope.rb +360 -0
  54. data/lib/sentry/session.rb +33 -0
  55. data/lib/sentry/session_flusher.rb +90 -0
  56. data/lib/sentry/span.rb +273 -0
  57. data/lib/sentry/test_helper.rb +84 -0
  58. data/lib/sentry/transaction.rb +359 -0
  59. data/lib/sentry/transaction_event.rb +80 -0
  60. data/lib/sentry/transport/configuration.rb +98 -0
  61. data/lib/sentry/transport/dummy_transport.rb +21 -0
  62. data/lib/sentry/transport/http_transport.rb +206 -0
  63. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  64. data/lib/sentry/transport.rb +225 -0
  65. data/lib/sentry/utils/argument_checking_helper.rb +19 -0
  66. data/lib/sentry/utils/custom_inspection.rb +14 -0
  67. data/lib/sentry/utils/encoding_helper.rb +22 -0
  68. data/lib/sentry/utils/exception_cause_chain.rb +20 -0
  69. data/lib/sentry/utils/logging_helper.rb +26 -0
  70. data/lib/sentry/utils/real_ip.rb +84 -0
  71. data/lib/sentry/utils/request_id.rb +18 -0
  72. data/lib/sentry/version.rb +5 -0
  73. data/lib/sentry-ruby.rb +580 -0
  74. data/sentry-ruby-core.gemspec +23 -0
  75. data/sentry-ruby.gemspec +24 -0
  76. metadata +75 -16
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # @api private
5
+ class Backtrace
6
+ # Handles backtrace parsing line by line
7
+ class Line
8
+ RB_EXTENSION = ".rb"
9
+ # regexp (optional leading X: on windows, or JRuby9000 class-prefix)
10
+ RUBY_INPUT_FORMAT = /
11
+ ^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
12
+ (\d+)
13
+ (?: :in \s `([^']+)')?$
14
+ /x.freeze
15
+
16
+ # org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:170)
17
+ JAVA_INPUT_FORMAT = /^(.+)\.([^\.]+)\(([^\:]+)\:(\d+)\)$/.freeze
18
+
19
+ # The file portion of the line (such as app/models/user.rb)
20
+ attr_reader :file
21
+
22
+ # The line number portion of the line
23
+ attr_reader :number
24
+
25
+ # The method of the line (such as index)
26
+ attr_reader :method
27
+
28
+ # The module name (JRuby)
29
+ attr_reader :module_name
30
+
31
+ attr_reader :in_app_pattern
32
+
33
+ # Parses a single line of a given backtrace
34
+ # @param [String] unparsed_line The raw line from +caller+ or some backtrace
35
+ # @return [Line] The parsed backtrace line
36
+ def self.parse(unparsed_line, in_app_pattern)
37
+ ruby_match = unparsed_line.match(RUBY_INPUT_FORMAT)
38
+ if ruby_match
39
+ _, file, number, method = ruby_match.to_a
40
+ file.sub!(/\.class$/, RB_EXTENSION)
41
+ module_name = nil
42
+ else
43
+ java_match = unparsed_line.match(JAVA_INPUT_FORMAT)
44
+ _, module_name, method, file, number = java_match.to_a
45
+ end
46
+ new(file, number, method, module_name, in_app_pattern)
47
+ end
48
+
49
+ def initialize(file, number, method, module_name, in_app_pattern)
50
+ @file = file
51
+ @module_name = module_name
52
+ @number = number.to_i
53
+ @method = method
54
+ @in_app_pattern = in_app_pattern
55
+ end
56
+
57
+ def in_app
58
+ if file =~ in_app_pattern
59
+ true
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ # Reconstructs the line in a readable fashion
66
+ def to_s
67
+ "#{file}:#{number}:in `#{method}'"
68
+ end
69
+
70
+ def ==(other)
71
+ to_s == other.to_s
72
+ end
73
+
74
+ def inspect
75
+ "<Line:#{self}>"
76
+ end
77
+ end
78
+
79
+ APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/.freeze
80
+
81
+ # holder for an Array of Backtrace::Line instances
82
+ attr_reader :lines
83
+
84
+ def self.parse(backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback)
85
+ ruby_lines = backtrace.is_a?(Array) ? backtrace : backtrace.split(/\n\s*/)
86
+
87
+ ruby_lines = backtrace_cleanup_callback.call(ruby_lines) if backtrace_cleanup_callback
88
+
89
+ in_app_pattern ||= begin
90
+ Regexp.new("^(#{project_root}/)?#{app_dirs_pattern || APP_DIRS_PATTERN}")
91
+ end
92
+
93
+ lines = ruby_lines.to_a.map do |unparsed_line|
94
+ Line.parse(unparsed_line, in_app_pattern)
95
+ end
96
+
97
+ new(lines)
98
+ end
99
+
100
+ def initialize(lines)
101
+ @lines = lines
102
+ end
103
+
104
+ def inspect
105
+ "<Backtrace: " + lines.map(&:inspect).join(", ") + ">"
106
+ end
107
+
108
+ def to_s
109
+ content = []
110
+ lines.each do |line|
111
+ content << line
112
+ end
113
+ content.join("\n")
114
+ end
115
+
116
+ def ==(other)
117
+ if other.respond_to?(:lines)
118
+ lines == other.lines
119
+ else
120
+ false
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+
5
+ module Sentry
6
+ # A {https://www.w3.org/TR/baggage W3C Baggage Header} implementation.
7
+ class Baggage
8
+ SENTRY_PREFIX = 'sentry-'
9
+ SENTRY_PREFIX_REGEX = /^sentry-/.freeze
10
+
11
+ # @return [Hash]
12
+ attr_reader :items
13
+
14
+ # @return [Boolean]
15
+ attr_reader :mutable
16
+
17
+ def initialize(items, mutable: true)
18
+ @items = items
19
+ @mutable = mutable
20
+ end
21
+
22
+ # Creates a Baggage object from an incoming W3C Baggage header string.
23
+ #
24
+ # Sentry items are identified with the 'sentry-' prefix and stored in a hash.
25
+ # The presence of a Sentry item makes the baggage object immutable.
26
+ #
27
+ # @param header [String] The incoming Baggage header string.
28
+ # @return [Baggage, nil]
29
+ def self.from_incoming_header(header)
30
+ items = {}
31
+ mutable = true
32
+
33
+ header.split(',').each do |item|
34
+ item = item.strip
35
+ key, val = item.split('=')
36
+
37
+ next unless key && val
38
+ next unless key =~ SENTRY_PREFIX_REGEX
39
+
40
+ baggage_key = key.split('-')[1]
41
+ next unless baggage_key
42
+
43
+ items[CGI.unescape(baggage_key)] = CGI.unescape(val)
44
+ mutable = false
45
+ end
46
+
47
+ new(items, mutable: mutable)
48
+ end
49
+
50
+ # Make the Baggage immutable.
51
+ # @return [void]
52
+ def freeze!
53
+ @mutable = false
54
+ end
55
+
56
+ # A {https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#envelope-header Dynamic Sampling Context}
57
+ # hash to be used in the trace envelope header.
58
+ # @return [Hash]
59
+ def dynamic_sampling_context
60
+ @items
61
+ end
62
+
63
+ # Serialize the Baggage object back to a string.
64
+ # @return [String]
65
+ def serialize
66
+ items = @items.map { |k, v| "#{SENTRY_PREFIX}#{CGI.escape(k)}=#{CGI.escape(v)}" }
67
+ items.join(',')
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Sentry
6
+ class Breadcrumb
7
+ module SentryLogger
8
+ LEVELS = {
9
+ ::Logger::DEBUG => 'debug',
10
+ ::Logger::INFO => 'info',
11
+ ::Logger::WARN => 'warn',
12
+ ::Logger::ERROR => 'error',
13
+ ::Logger::FATAL => 'fatal'
14
+ }.freeze
15
+
16
+ def add(*args, &block)
17
+ super
18
+ add_breadcrumb(*args, &block)
19
+ nil
20
+ end
21
+
22
+ def add_breadcrumb(severity, message = nil, progname = nil)
23
+ # because the breadcrumbs now belongs to different Hub's Scope in different threads
24
+ # we need to make sure the current thread's Hub has been set before adding breadcrumbs
25
+ return unless Sentry.initialized? && Sentry.get_current_hub
26
+
27
+ category = "logger"
28
+
29
+ # this is because the nature of Ruby Logger class:
30
+ #
31
+ # when given 1 argument, the argument will become both message and progname
32
+ #
33
+ # ```
34
+ # logger.info("foo")
35
+ # # message == progname == "foo"
36
+ # ```
37
+ #
38
+ # and to specify progname with a different message,
39
+ # we need to pass the progname as the argument and pass the message as a proc
40
+ #
41
+ # ```
42
+ # logger.info("progname") { "the message" }
43
+ # ```
44
+ #
45
+ # so the condition below is to replicate the similar behavior
46
+ if message.nil?
47
+ if block_given?
48
+ message = yield
49
+ category = progname
50
+ else
51
+ message = progname
52
+ end
53
+ end
54
+
55
+ return if ignored_logger?(progname) || message == ""
56
+
57
+ # some loggers will add leading/trailing space as they (incorrectly, mind you)
58
+ # think of logging as a shortcut to std{out,err}
59
+ message = message.to_s.strip
60
+
61
+ last_crumb = current_breadcrumbs.peek
62
+ # try to avoid dupes from logger broadcasts
63
+ if last_crumb.nil? || last_crumb.message != message
64
+ level = Sentry::Breadcrumb::SentryLogger::LEVELS.fetch(severity, nil)
65
+ crumb = Sentry::Breadcrumb.new(
66
+ level: level,
67
+ category: category,
68
+ message: message,
69
+ type: severity >= 3 ? "error" : level
70
+ )
71
+
72
+ Sentry.add_breadcrumb(crumb, hint: { severity: severity })
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def ignored_logger?(progname)
79
+ progname == LOGGER_PROGNAME ||
80
+ Sentry.configuration.exclude_loggers.include?(progname)
81
+ end
82
+
83
+ def current_breadcrumbs
84
+ Sentry.get_current_scope.breadcrumbs
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ ::Logger.send(:prepend, Sentry::Breadcrumb::SentryLogger)
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class Breadcrumb
5
+ DATA_SERIALIZATION_ERROR_MESSAGE = "[data were removed due to serialization issues]"
6
+
7
+ # @return [String, nil]
8
+ attr_accessor :category
9
+ # @return [Hash, nil]
10
+ attr_accessor :data
11
+ # @return [String, nil]
12
+ attr_reader :level
13
+ # @return [Time, Integer, nil]
14
+ attr_accessor :timestamp
15
+ # @return [String, nil]
16
+ attr_accessor :type
17
+ # @return [String, nil]
18
+ attr_reader :message
19
+
20
+ # @param category [String, nil]
21
+ # @param data [Hash, nil]
22
+ # @param message [String, nil]
23
+ # @param timestamp [Time, Integer, nil]
24
+ # @param level [String, nil]
25
+ # @param type [String, nil]
26
+ def initialize(category: nil, data: nil, message: nil, timestamp: nil, level: nil, type: nil)
27
+ @category = category
28
+ @data = data || {}
29
+ @timestamp = timestamp || Sentry.utc_now.to_i
30
+ @type = type
31
+ self.message = message
32
+ self.level = level
33
+ end
34
+
35
+ # @return [Hash]
36
+ def to_hash
37
+ {
38
+ category: @category,
39
+ data: serialized_data,
40
+ level: @level,
41
+ message: @message,
42
+ timestamp: @timestamp,
43
+ type: @type
44
+ }
45
+ end
46
+
47
+ # @param message [String]
48
+ # @return [void]
49
+ def message=(message)
50
+ @message = (message || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
51
+ end
52
+
53
+ # @param level [String]
54
+ # @return [void]
55
+ def level=(level) # needed to meet the Sentry spec
56
+ @level = level == "warn" ? "warning" : level
57
+ end
58
+
59
+ private
60
+
61
+ def serialized_data
62
+ begin
63
+ ::JSON.parse(::JSON.generate(@data))
64
+ rescue Exception => e
65
+ Sentry.logger.debug(LOGGER_PROGNAME) do
66
+ <<~MSG
67
+ can't serialize breadcrumb data because of error: #{e}
68
+ data: #{@data}
69
+ MSG
70
+ end
71
+
72
+ DATA_SERIALIZATION_ERROR_MESSAGE
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/breadcrumb"
4
+
5
+ module Sentry
6
+ class BreadcrumbBuffer
7
+ DEFAULT_SIZE = 100
8
+ include Enumerable
9
+
10
+ # @return [Array]
11
+ attr_accessor :buffer
12
+
13
+ # @param size [Integer, nil] If it's not provided, it'll fallback to DEFAULT_SIZE
14
+ def initialize(size = nil)
15
+ @buffer = Array.new(size || DEFAULT_SIZE)
16
+ end
17
+
18
+ # @param crumb [Breadcrumb]
19
+ # @return [void]
20
+ def record(crumb)
21
+ yield(crumb) if block_given?
22
+ @buffer.slice!(0)
23
+ @buffer << crumb
24
+ end
25
+
26
+ # @return [Array]
27
+ def members
28
+ @buffer.compact
29
+ end
30
+
31
+ # Returns the last breadcrumb stored in the buffer. If the buffer it's empty, it returns nil.
32
+ # @return [Breadcrumb, nil]
33
+ def peek
34
+ members.last
35
+ end
36
+
37
+ # Iterates through all breadcrumbs.
38
+ # @param block [Proc]
39
+ # @yieldparam crumb [Breadcrumb]
40
+ # @return [Array]
41
+ def each(&block)
42
+ members.each(&block)
43
+ end
44
+
45
+ # @return [Boolean]
46
+ def empty?
47
+ members.none?
48
+ end
49
+
50
+ # @return [Hash]
51
+ def to_hash
52
+ {
53
+ values: members.map(&:to_hash)
54
+ }
55
+ end
56
+
57
+ # @return [BreadcrumbBuffer]
58
+ def dup
59
+ copy = super
60
+ copy.buffer = buffer.deep_dup
61
+ copy
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'sentry/cron/monitor_config'
5
+
6
+ module Sentry
7
+ class CheckInEvent < Event
8
+ TYPE = 'check_in'
9
+
10
+ # uuid to identify this check-in.
11
+ # @return [String]
12
+ attr_accessor :check_in_id
13
+
14
+ # Identifier of the monitor for this check-in.
15
+ # @return [String]
16
+ attr_accessor :monitor_slug
17
+
18
+ # Duration of this check since it has started in seconds.
19
+ # @return [Integer, nil]
20
+ attr_accessor :duration
21
+
22
+ # Monitor configuration to support upserts.
23
+ # @return [Cron::MonitorConfig, nil]
24
+ attr_accessor :monitor_config
25
+
26
+ # Status of this check-in.
27
+ # @return [Symbol]
28
+ attr_accessor :status
29
+
30
+ VALID_STATUSES = %i(ok in_progress error)
31
+
32
+ def initialize(
33
+ slug:,
34
+ status:,
35
+ duration: nil,
36
+ monitor_config: nil,
37
+ check_in_id: nil,
38
+ **options
39
+ )
40
+ super(**options)
41
+
42
+ self.monitor_slug = slug
43
+ self.status = status
44
+ self.duration = duration
45
+ self.monitor_config = monitor_config
46
+ self.check_in_id = check_in_id || SecureRandom.uuid.delete('-')
47
+ end
48
+
49
+ # @return [Hash]
50
+ def to_hash
51
+ data = super
52
+ data[:check_in_id] = check_in_id
53
+ data[:monitor_slug] = monitor_slug
54
+ data[:status] = status
55
+ data[:duration] = duration if duration
56
+ data[:monitor_config] = monitor_config.to_hash if monitor_config
57
+ data
58
+ end
59
+ end
60
+ end