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,210 @@
1
+ require "sentry/breadcrumb_buffer"
2
+ require "etc"
3
+
4
+ module Sentry
5
+ class Scope
6
+ include ArgumentCheckingHelper
7
+
8
+ ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span]
9
+
10
+ attr_reader(*ATTRIBUTES)
11
+
12
+ def initialize
13
+ set_default_value
14
+ end
15
+
16
+ def clear
17
+ set_default_value
18
+ end
19
+
20
+ def apply_to_event(event, hint = nil)
21
+ event.tags = tags.merge(event.tags)
22
+ event.user = user.merge(event.user)
23
+ event.extra = extra.merge(event.extra)
24
+ event.contexts = contexts.merge(event.contexts)
25
+
26
+ if span
27
+ event.contexts[:trace] = span.get_trace_context
28
+ end
29
+
30
+ event.fingerprint = fingerprint
31
+ event.level = level
32
+ event.transaction = transaction_names.last
33
+ event.breadcrumbs = breadcrumbs
34
+ event.rack_env = rack_env if rack_env
35
+
36
+ unless @event_processors.empty?
37
+ @event_processors.each do |processor_block|
38
+ event = processor_block.call(event, hint)
39
+ end
40
+ end
41
+
42
+ event
43
+ end
44
+
45
+ def add_breadcrumb(breadcrumb)
46
+ breadcrumbs.record(breadcrumb)
47
+ end
48
+
49
+ def clear_breadcrumbs
50
+ @breadcrumbs = BreadcrumbBuffer.new
51
+ end
52
+
53
+ def dup
54
+ copy = super
55
+ copy.breadcrumbs = breadcrumbs.dup
56
+ copy.contexts = contexts.deep_dup
57
+ copy.extra = extra.deep_dup
58
+ copy.tags = tags.deep_dup
59
+ copy.user = user.deep_dup
60
+ copy.transaction_names = transaction_names.deep_dup
61
+ copy.fingerprint = fingerprint.deep_dup
62
+ copy.span = span.deep_dup
63
+ copy
64
+ end
65
+
66
+ def update_from_scope(scope)
67
+ self.breadcrumbs = scope.breadcrumbs
68
+ self.contexts = scope.contexts
69
+ self.extra = scope.extra
70
+ self.tags = scope.tags
71
+ self.user = scope.user
72
+ self.transaction_names = scope.transaction_names
73
+ self.fingerprint = scope.fingerprint
74
+ self.span = scope.span
75
+ end
76
+
77
+ def update_from_options(
78
+ contexts: nil,
79
+ extra: nil,
80
+ tags: nil,
81
+ user: nil,
82
+ level: nil,
83
+ fingerprint: nil
84
+ )
85
+ self.contexts.merge!(contexts) if contexts
86
+ self.extra.merge!(extra) if extra
87
+ self.tags.merge!(tags) if tags
88
+ self.user = user if user
89
+ self.level = level if level
90
+ self.fingerprint = fingerprint if fingerprint
91
+ end
92
+
93
+ def set_rack_env(env)
94
+ env = env || {}
95
+ @rack_env = env
96
+ end
97
+
98
+ def set_span(span)
99
+ check_argument_type!(span, Span)
100
+ @span = span
101
+ end
102
+
103
+ def set_user(user_hash)
104
+ check_argument_type!(user_hash, Hash)
105
+ @user = user_hash
106
+ end
107
+
108
+ def set_extras(extras_hash)
109
+ check_argument_type!(extras_hash, Hash)
110
+ @extra.merge!(extras_hash)
111
+ end
112
+
113
+ def set_extra(key, value)
114
+ @extra.merge!(key => value)
115
+ end
116
+
117
+ def set_tags(tags_hash)
118
+ check_argument_type!(tags_hash, Hash)
119
+ @tags.merge!(tags_hash)
120
+ end
121
+
122
+ def set_tag(key, value)
123
+ @tags.merge!(key => value)
124
+ end
125
+
126
+ def set_contexts(contexts_hash)
127
+ check_argument_type!(contexts_hash, Hash)
128
+ @contexts = contexts_hash
129
+ end
130
+
131
+ def set_context(key, value)
132
+ @contexts.merge!(key => value)
133
+ end
134
+
135
+ def set_level(level)
136
+ @level = level
137
+ end
138
+
139
+ def set_transaction_name(transaction_name)
140
+ @transaction_names << transaction_name
141
+ end
142
+
143
+ def transaction_name
144
+ @transaction_names.last
145
+ end
146
+
147
+ def get_transaction
148
+ # transaction will always be the first in the span_recorder
149
+ span.span_recorder.spans.first if span
150
+ end
151
+
152
+ def get_span
153
+ span
154
+ end
155
+
156
+ def set_fingerprint(fingerprint)
157
+ check_argument_type!(fingerprint, Array)
158
+
159
+ @fingerprint = fingerprint
160
+ end
161
+
162
+ def add_event_processor(&block)
163
+ @event_processors << block
164
+ end
165
+
166
+ protected
167
+
168
+ # for duplicating scopes internally
169
+ attr_writer(*ATTRIBUTES)
170
+
171
+ private
172
+
173
+ def set_default_value
174
+ @breadcrumbs = BreadcrumbBuffer.new
175
+ @contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
176
+ @extra = {}
177
+ @tags = {}
178
+ @user = {}
179
+ @level = :error
180
+ @fingerprint = []
181
+ @transaction_names = []
182
+ @event_processors = []
183
+ @rack_env = {}
184
+ @span = nil
185
+ end
186
+
187
+ class << self
188
+ def os_context
189
+ @os_context ||=
190
+ begin
191
+ uname = Etc.uname
192
+ {
193
+ name: uname[:sysname] || RbConfig::CONFIG["host_os"],
194
+ version: uname[:version],
195
+ build: uname[:release],
196
+ kernel_version: uname[:version]
197
+ }
198
+ end
199
+ end
200
+
201
+ def runtime_context
202
+ @runtime_context ||= {
203
+ name: RbConfig::CONFIG["ruby_install_name"],
204
+ version: RUBY_DESCRIPTION || Sentry.sys_command("ruby -v")
205
+ }
206
+ end
207
+ end
208
+
209
+ end
210
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+ require "securerandom"
3
+
4
+ module Sentry
5
+ class Span
6
+ STATUS_MAP = {
7
+ 400 => "invalid_argument",
8
+ 401 => "unauthenticated",
9
+ 403 => "permission_denied",
10
+ 404 => "not_found",
11
+ 409 => "already_exists",
12
+ 429 => "resource_exhausted",
13
+ 499 => "cancelled",
14
+ 500 => "internal_error",
15
+ 501 => "unimplemented",
16
+ 503 => "unavailable",
17
+ 504 => "deadline_exceeded"
18
+ }
19
+
20
+
21
+ attr_reader :trace_id, :span_id, :parent_span_id, :sampled, :start_timestamp, :timestamp, :description, :op, :status, :tags, :data
22
+ attr_accessor :span_recorder
23
+
24
+ def initialize(description: nil, op: nil, status: nil, trace_id: nil, parent_span_id: nil, sampled: nil, start_timestamp: nil, timestamp: nil)
25
+ @trace_id = trace_id || SecureRandom.uuid.delete("-")
26
+ @span_id = SecureRandom.hex(8)
27
+ @parent_span_id = parent_span_id
28
+ @sampled = sampled
29
+ @start_timestamp = start_timestamp || Sentry.utc_now.to_f
30
+ @timestamp = timestamp
31
+ @description = description
32
+ @op = op
33
+ @status = status
34
+ @data = {}
35
+ @tags = {}
36
+ end
37
+
38
+ def finish
39
+ # already finished
40
+ return if @timestamp
41
+
42
+ @timestamp = Sentry.utc_now.to_f
43
+ self
44
+ end
45
+
46
+ def to_sentry_trace
47
+ sampled_flag = ""
48
+ sampled_flag = @sampled ? 1 : 0 unless @sampled.nil?
49
+
50
+ "#{@trace_id}-#{@span_id}-#{sampled_flag}"
51
+ end
52
+
53
+ def to_hash
54
+ {
55
+ trace_id: @trace_id,
56
+ span_id: @span_id,
57
+ parent_span_id: @parent_span_id,
58
+ start_timestamp: @start_timestamp,
59
+ timestamp: @timestamp,
60
+ description: @description,
61
+ op: @op,
62
+ status: @status,
63
+ tags: @tags,
64
+ data: @data
65
+ }
66
+ end
67
+
68
+ def get_trace_context
69
+ {
70
+ trace_id: @trace_id,
71
+ span_id: @span_id,
72
+ parent_span_id: @parent_span_id,
73
+ description: @description,
74
+ op: @op,
75
+ status: @status
76
+ }
77
+ end
78
+
79
+ def start_child(**options)
80
+ options = options.dup.merge(trace_id: @trace_id, parent_span_id: @span_id, sampled: @sampled)
81
+ Span.new(**options)
82
+ end
83
+
84
+ def with_child_span(**options, &block)
85
+ child_span = start_child(**options)
86
+
87
+ yield(child_span)
88
+
89
+ child_span.finish
90
+ end
91
+
92
+ def deep_dup
93
+ dup
94
+ end
95
+
96
+ def set_op(op)
97
+ @op = op
98
+ end
99
+
100
+ def set_description(description)
101
+ @description = description
102
+ end
103
+
104
+ def set_status(status)
105
+ @status = status
106
+ end
107
+
108
+ def set_timestamp(timestamp)
109
+ @timestamp = timestamp
110
+ end
111
+
112
+ def set_http_status(status_code)
113
+ status_code = status_code.to_i
114
+ set_data("status_code", status_code)
115
+
116
+ status =
117
+ if status_code >= 200 && status_code < 299
118
+ "ok"
119
+ else
120
+ STATUS_MAP[status_code]
121
+ end
122
+ set_status(status)
123
+ end
124
+
125
+ def set_data(key, value)
126
+ @data[key] = value
127
+ end
128
+
129
+ def set_tag(key, value)
130
+ @tags[key] = value
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,157 @@
1
+ module Sentry
2
+ class Transaction < Span
3
+ SENTRY_TRACE_REGEXP = Regexp.new(
4
+ "^[ \t]*" + # whitespace
5
+ "([0-9a-f]{32})?" + # trace_id
6
+ "-?([0-9a-f]{16})?" + # span_id
7
+ "-?([01])?" + # sampled
8
+ "[ \t]*$" # whitespace
9
+ )
10
+ UNLABELD_NAME = "<unlabeled transaction>".freeze
11
+ MESSAGE_PREFIX = "[Tracing]"
12
+
13
+ attr_reader :name, :parent_sampled
14
+
15
+ def initialize(name: nil, parent_sampled: nil, **options)
16
+ super(**options)
17
+
18
+ @name = name
19
+ @parent_sampled = parent_sampled
20
+ set_span_recorder
21
+ end
22
+
23
+ def set_span_recorder
24
+ @span_recorder = SpanRecorder.new(1000)
25
+ @span_recorder.add(self)
26
+ end
27
+
28
+ def self.from_sentry_trace(sentry_trace, **options)
29
+ return unless sentry_trace
30
+
31
+ match = SENTRY_TRACE_REGEXP.match(sentry_trace)
32
+ trace_id, parent_span_id, sampled_flag = match[1..3]
33
+
34
+ sampled = sampled_flag != "0"
35
+
36
+ new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: sampled, **options)
37
+ end
38
+
39
+ def to_hash
40
+ hash = super
41
+ hash.merge!(name: @name, sampled: @sampled, parent_sampled: @parent_sampled)
42
+ hash
43
+ end
44
+
45
+ def start_child(**options)
46
+ child_span = super
47
+ child_span.span_recorder = @span_recorder
48
+
49
+ if @sampled
50
+ @span_recorder.add(child_span)
51
+ end
52
+
53
+ child_span
54
+ end
55
+
56
+ def deep_dup
57
+ copy = super
58
+ copy.set_span_recorder
59
+
60
+ @span_recorder.spans.each do |span|
61
+ # span_recorder's first span is the current span, which should not be added to the copy's spans
62
+ next if span == self
63
+ copy.span_recorder.add(span.dup)
64
+ end
65
+
66
+ copy
67
+ end
68
+
69
+ def set_initial_sample_desicion(sampling_context = {})
70
+ unless Sentry.configuration.tracing_enabled?
71
+ @sampled = false
72
+ return
73
+ end
74
+
75
+ return unless @sampled.nil?
76
+
77
+ transaction_description = generate_transaction_description
78
+
79
+ logger = Sentry.configuration.logger
80
+ sample_rate = Sentry.configuration.traces_sample_rate
81
+ traces_sampler = Sentry.configuration.traces_sampler
82
+
83
+ if traces_sampler.is_a?(Proc)
84
+ sampling_context = sampling_context.merge(
85
+ parent_sampled: @parent_sampled,
86
+ transaction_context: self.to_hash
87
+ )
88
+
89
+ sample_rate = traces_sampler.call(sampling_context)
90
+ end
91
+
92
+ unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Float) && sample_rate >= 0.0 && sample_rate <= 1.0)
93
+ @sampled = false
94
+ logger.warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
95
+ return
96
+ end
97
+
98
+ if sample_rate == 0.0 || sample_rate == false
99
+ @sampled = false
100
+ logger.debug("#{MESSAGE_PREFIX} Discarding #{transaction_description} because traces_sampler returned 0 or false")
101
+ return
102
+ end
103
+
104
+ if sample_rate == true
105
+ @sampled = true
106
+ else
107
+ @sampled = Random.rand < sample_rate
108
+ end
109
+
110
+ if @sampled
111
+ logger.debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
112
+ else
113
+ logger.debug(
114
+ "#{MESSAGE_PREFIX} Discarding #{transaction_description} because it's not included in the random sample (sampling rate = #{sample_rate})"
115
+ )
116
+ end
117
+ end
118
+
119
+ def finish(hub: nil)
120
+ super() # Span#finish doesn't take arguments
121
+
122
+ if @name.nil?
123
+ @name = UNLABELD_NAME
124
+ end
125
+
126
+ return unless @sampled || @parent_sampled
127
+
128
+ hub ||= Sentry.get_current_hub
129
+ event = hub.current_client.event_from_transaction(self)
130
+ hub.capture_event(event)
131
+ end
132
+
133
+ private
134
+
135
+ def generate_transaction_description
136
+ result = op.nil? ? "" : "<#{@op}> "
137
+ result += "transaction"
138
+ result += " <#{@name}>" if @name
139
+ result
140
+ end
141
+
142
+ class SpanRecorder
143
+ attr_reader :max_length, :spans
144
+
145
+ def initialize(max_length)
146
+ @max_length = max_length
147
+ @spans = []
148
+ end
149
+
150
+ def add(span)
151
+ if @spans.count < @max_length
152
+ @spans << span
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end