sentry-ruby 5.16.1 → 5.21.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.
- checksums.yaml +4 -4
- data/Gemfile +6 -0
- data/README.md +20 -10
- data/Rakefile +3 -1
- data/bin/console +2 -0
- data/lib/sentry/attachment.rb +40 -0
- data/lib/sentry/background_worker.rb +1 -1
- data/lib/sentry/backpressure_monitor.rb +2 -32
- data/lib/sentry/backtrace.rb +10 -8
- data/lib/sentry/baggage.rb +7 -7
- data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
- data/lib/sentry/check_in_event.rb +5 -5
- data/lib/sentry/client.rb +61 -11
- data/lib/sentry/configuration.rb +77 -31
- data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
- data/lib/sentry/cron/monitor_check_ins.rb +3 -1
- data/lib/sentry/cron/monitor_config.rb +1 -1
- data/lib/sentry/cron/monitor_schedule.rb +1 -1
- data/lib/sentry/dsn.rb +4 -4
- data/lib/sentry/envelope/item.rb +88 -0
- data/lib/sentry/envelope.rb +2 -68
- data/lib/sentry/error_event.rb +2 -2
- data/lib/sentry/event.rb +20 -18
- data/lib/sentry/faraday.rb +77 -0
- data/lib/sentry/graphql.rb +9 -0
- data/lib/sentry/hub.rb +23 -3
- data/lib/sentry/integrable.rb +4 -0
- data/lib/sentry/interface.rb +1 -0
- data/lib/sentry/interfaces/exception.rb +5 -3
- data/lib/sentry/interfaces/mechanism.rb +20 -0
- data/lib/sentry/interfaces/request.rb +7 -7
- data/lib/sentry/interfaces/single_exception.rb +9 -7
- data/lib/sentry/interfaces/stacktrace.rb +3 -1
- data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
- data/lib/sentry/logger.rb +1 -1
- data/lib/sentry/metrics/aggregator.rb +248 -0
- data/lib/sentry/metrics/configuration.rb +47 -0
- data/lib/sentry/metrics/counter_metric.rb +25 -0
- data/lib/sentry/metrics/distribution_metric.rb +25 -0
- data/lib/sentry/metrics/gauge_metric.rb +35 -0
- data/lib/sentry/metrics/local_aggregator.rb +53 -0
- data/lib/sentry/metrics/metric.rb +19 -0
- data/lib/sentry/metrics/set_metric.rb +28 -0
- data/lib/sentry/metrics/timing.rb +43 -0
- data/lib/sentry/metrics.rb +56 -0
- data/lib/sentry/net/http.rb +18 -39
- data/lib/sentry/profiler/helpers.rb +46 -0
- data/lib/sentry/profiler.rb +25 -56
- data/lib/sentry/propagation_context.rb +10 -9
- data/lib/sentry/puma.rb +1 -1
- data/lib/sentry/rack/capture_exceptions.rb +16 -4
- data/lib/sentry/rack.rb +2 -2
- data/lib/sentry/rake.rb +4 -2
- data/lib/sentry/redis.rb +2 -1
- data/lib/sentry/release_detector.rb +4 -4
- data/lib/sentry/scope.rb +36 -26
- data/lib/sentry/session.rb +2 -2
- data/lib/sentry/session_flusher.rb +7 -39
- data/lib/sentry/span.rb +46 -5
- data/lib/sentry/test_helper.rb +5 -2
- data/lib/sentry/threaded_periodic_worker.rb +39 -0
- data/lib/sentry/transaction.rb +19 -17
- data/lib/sentry/transaction_event.rb +6 -2
- data/lib/sentry/transport/configuration.rb +0 -1
- data/lib/sentry/transport/http_transport.rb +12 -12
- data/lib/sentry/transport.rb +18 -26
- data/lib/sentry/utils/argument_checking_helper.rb +6 -0
- data/lib/sentry/utils/env_helper.rb +21 -0
- data/lib/sentry/utils/http_tracing.rb +41 -0
- data/lib/sentry/utils/logging_helper.rb +0 -4
- data/lib/sentry/utils/real_ip.rb +2 -2
- data/lib/sentry/utils/request_id.rb +1 -1
- data/lib/sentry/vernier/output.rb +89 -0
- data/lib/sentry/vernier/profiler.rb +125 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +38 -6
- data/sentry-ruby-core.gemspec +3 -1
- data/sentry-ruby.gemspec +15 -6
- metadata +44 -7
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class GaugeMetric < Metric
|
6
|
+
attr_reader :last, :min, :max, :sum, :count
|
7
|
+
|
8
|
+
def initialize(value)
|
9
|
+
value = value.to_f
|
10
|
+
@last = value
|
11
|
+
@min = value
|
12
|
+
@max = value
|
13
|
+
@sum = value
|
14
|
+
@count = 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(value)
|
18
|
+
value = value.to_f
|
19
|
+
@last = value
|
20
|
+
@min = [@min, value].min
|
21
|
+
@max = [@max, value].max
|
22
|
+
@sum += value
|
23
|
+
@count += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def serialize
|
27
|
+
[last, min, max, sum, count]
|
28
|
+
end
|
29
|
+
|
30
|
+
def weight
|
31
|
+
5
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class LocalAggregator
|
6
|
+
# exposed only for testing
|
7
|
+
attr_reader :buckets
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@buckets = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(key, value)
|
14
|
+
if @buckets[key]
|
15
|
+
@buckets[key].add(value)
|
16
|
+
else
|
17
|
+
@buckets[key] = GaugeMetric.new(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
return nil if @buckets.empty?
|
23
|
+
|
24
|
+
@buckets.map do |bucket_key, metric|
|
25
|
+
type, key, unit, tags = bucket_key
|
26
|
+
|
27
|
+
payload_key = "#{type}:#{key}@#{unit}"
|
28
|
+
payload_value = {
|
29
|
+
tags: deserialize_tags(tags),
|
30
|
+
min: metric.min,
|
31
|
+
max: metric.max,
|
32
|
+
count: metric.count,
|
33
|
+
sum: metric.sum
|
34
|
+
}
|
35
|
+
|
36
|
+
[payload_key, payload_value]
|
37
|
+
end.to_h
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def deserialize_tags(tags)
|
43
|
+
tags.inject({}) do |h, tag|
|
44
|
+
k, v = tag
|
45
|
+
old = h[k]
|
46
|
+
# make it an array if key repeats
|
47
|
+
h[k] = old ? (old.is_a?(Array) ? old << v : [old, v]) : v
|
48
|
+
h
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class Metric
|
6
|
+
def add(value)
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def weight
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "zlib"
|
5
|
+
|
6
|
+
module Sentry
|
7
|
+
module Metrics
|
8
|
+
class SetMetric < Metric
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@value = Set[value]
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(value)
|
16
|
+
@value << value
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize
|
20
|
+
value.map { |x| x.is_a?(String) ? Zlib.crc32(x) : x.to_i }
|
21
|
+
end
|
22
|
+
|
23
|
+
def weight
|
24
|
+
value.size
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
module Timing
|
6
|
+
class << self
|
7
|
+
def nanosecond
|
8
|
+
time = Sentry.utc_now
|
9
|
+
time.to_i * (10 ** 9) + time.nsec
|
10
|
+
end
|
11
|
+
|
12
|
+
def microsecond
|
13
|
+
time = Sentry.utc_now
|
14
|
+
time.to_i * (10 ** 6) + time.usec
|
15
|
+
end
|
16
|
+
|
17
|
+
def millisecond
|
18
|
+
Sentry.utc_now.to_i * (10 ** 3)
|
19
|
+
end
|
20
|
+
|
21
|
+
def second
|
22
|
+
Sentry.utc_now.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def minute
|
26
|
+
Sentry.utc_now.to_i / 60.0
|
27
|
+
end
|
28
|
+
|
29
|
+
def hour
|
30
|
+
Sentry.utc_now.to_i / 3600.0
|
31
|
+
end
|
32
|
+
|
33
|
+
def day
|
34
|
+
Sentry.utc_now.to_i / (3600.0 * 24.0)
|
35
|
+
end
|
36
|
+
|
37
|
+
def week
|
38
|
+
Sentry.utc_now.to_i / (3600.0 * 24.0 * 7.0)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sentry/metrics/metric"
|
4
|
+
require "sentry/metrics/counter_metric"
|
5
|
+
require "sentry/metrics/distribution_metric"
|
6
|
+
require "sentry/metrics/gauge_metric"
|
7
|
+
require "sentry/metrics/set_metric"
|
8
|
+
require "sentry/metrics/timing"
|
9
|
+
require "sentry/metrics/aggregator"
|
10
|
+
|
11
|
+
module Sentry
|
12
|
+
module Metrics
|
13
|
+
DURATION_UNITS = %w[nanosecond microsecond millisecond second minute hour day week]
|
14
|
+
INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
|
15
|
+
FRACTIONAL_UNITS = %w[ratio percent]
|
16
|
+
|
17
|
+
OP_NAME = "metric.timing"
|
18
|
+
SPAN_ORIGIN = "auto.metric.timing"
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def increment(key, value = 1.0, unit: "none", tags: {}, timestamp: nil)
|
22
|
+
Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
23
|
+
end
|
24
|
+
|
25
|
+
def distribution(key, value, unit: "none", tags: {}, timestamp: nil)
|
26
|
+
Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set(key, value, unit: "none", tags: {}, timestamp: nil)
|
30
|
+
Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
31
|
+
end
|
32
|
+
|
33
|
+
def gauge(key, value, unit: "none", tags: {}, timestamp: nil)
|
34
|
+
Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
35
|
+
end
|
36
|
+
|
37
|
+
def timing(key, unit: "second", tags: {}, timestamp: nil, &block)
|
38
|
+
return unless block_given?
|
39
|
+
return yield unless DURATION_UNITS.include?(unit)
|
40
|
+
|
41
|
+
result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
|
42
|
+
tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
|
43
|
+
|
44
|
+
start = Timing.send(unit.to_sym)
|
45
|
+
result = yield
|
46
|
+
value = Timing.send(unit.to_sym) - start
|
47
|
+
|
48
|
+
[result, value]
|
49
|
+
end
|
50
|
+
|
51
|
+
Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/sentry/net/http.rb
CHANGED
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
require "net/http"
|
4
4
|
require "resolv"
|
5
|
+
require "sentry/utils/http_tracing"
|
5
6
|
|
6
7
|
module Sentry
|
7
8
|
# @api private
|
8
9
|
module Net
|
9
10
|
module HTTP
|
11
|
+
include Utils::HttpTracing
|
12
|
+
|
10
13
|
OP_NAME = "http.client"
|
14
|
+
SPAN_ORIGIN = "auto.http.net_http"
|
11
15
|
BREADCRUMB_CATEGORY = "net.http"
|
12
16
|
|
13
17
|
# To explain how the entire thing works, we need to know how the original Net::HTTP#request works
|
@@ -20,8 +24,7 @@ module Sentry
|
|
20
24
|
# req['connection'] ||= 'close'
|
21
25
|
# return request(req, body, &block) # <- request will be called for the second time from the first call
|
22
26
|
# }
|
23
|
-
# end
|
24
|
-
# # .....
|
27
|
+
# end # .....
|
25
28
|
# end
|
26
29
|
# ```
|
27
30
|
#
|
@@ -30,47 +33,29 @@ module Sentry
|
|
30
33
|
return super unless started? && Sentry.initialized?
|
31
34
|
return super if from_sentry_sdk?
|
32
35
|
|
33
|
-
Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |sentry_span|
|
36
|
+
Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
|
34
37
|
request_info = extract_request_info(req)
|
35
38
|
|
36
|
-
if propagate_trace?(request_info[:url]
|
39
|
+
if propagate_trace?(request_info[:url])
|
37
40
|
set_propagation_headers(req)
|
38
41
|
end
|
39
42
|
|
40
|
-
|
41
|
-
|
43
|
+
res = super
|
44
|
+
response_status = res.code.to_i
|
42
45
|
|
43
|
-
|
44
|
-
|
45
|
-
sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
|
46
|
-
sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
|
47
|
-
sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
|
48
|
-
sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, res.code.to_i)
|
49
|
-
end
|
46
|
+
if record_sentry_breadcrumb?
|
47
|
+
record_sentry_breadcrumb(request_info, response_status)
|
50
48
|
end
|
51
|
-
end
|
52
|
-
end
|
53
49
|
|
54
|
-
|
50
|
+
if sentry_span
|
51
|
+
set_span_info(sentry_span, request_info, response_status)
|
52
|
+
end
|
55
53
|
|
56
|
-
|
57
|
-
|
54
|
+
res
|
55
|
+
end
|
58
56
|
end
|
59
57
|
|
60
|
-
|
61
|
-
return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
62
|
-
|
63
|
-
crumb = Sentry::Breadcrumb.new(
|
64
|
-
level: :info,
|
65
|
-
category: BREADCRUMB_CATEGORY,
|
66
|
-
type: :info,
|
67
|
-
data: {
|
68
|
-
status: res.code.to_i,
|
69
|
-
**request_info
|
70
|
-
}
|
71
|
-
)
|
72
|
-
Sentry.add_breadcrumb(crumb)
|
73
|
-
end
|
58
|
+
private
|
74
59
|
|
75
60
|
def from_sentry_sdk?
|
76
61
|
dsn = Sentry.configuration.dsn
|
@@ -81,7 +66,7 @@ module Sentry
|
|
81
66
|
# IPv6 url could look like '::1/path', and that won't parse without
|
82
67
|
# wrapping it in square brackets.
|
83
68
|
hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
|
84
|
-
uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}")
|
69
|
+
uri = req.uri || URI.parse(URI::DEFAULT_PARSER.escape("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}"))
|
85
70
|
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
|
86
71
|
|
87
72
|
result = { method: req.method, url: url }
|
@@ -93,12 +78,6 @@ module Sentry
|
|
93
78
|
|
94
79
|
result
|
95
80
|
end
|
96
|
-
|
97
|
-
def propagate_trace?(url, configuration)
|
98
|
-
url &&
|
99
|
-
configuration.propagate_traces &&
|
100
|
-
configuration.trace_propagation_targets.any? { |target| url.match?(target) }
|
101
|
-
end
|
102
81
|
end
|
103
82
|
end
|
104
83
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Sentry
|
6
|
+
class Profiler
|
7
|
+
module Helpers
|
8
|
+
def in_app?(abs_path)
|
9
|
+
abs_path.match?(@in_app_pattern)
|
10
|
+
end
|
11
|
+
|
12
|
+
# copied from stacktrace.rb since I don't want to touch existing code
|
13
|
+
# TODO-neel-profiler try to fetch this from stackprof once we patch
|
14
|
+
# the native extension
|
15
|
+
def compute_filename(abs_path, in_app)
|
16
|
+
return nil if abs_path.nil?
|
17
|
+
|
18
|
+
under_project_root = @project_root && abs_path.start_with?(@project_root)
|
19
|
+
|
20
|
+
prefix =
|
21
|
+
if under_project_root && in_app
|
22
|
+
@project_root
|
23
|
+
else
|
24
|
+
longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
|
25
|
+
|
26
|
+
if under_project_root
|
27
|
+
longest_load_path || @project_root
|
28
|
+
else
|
29
|
+
longest_load_path
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
34
|
+
end
|
35
|
+
|
36
|
+
def split_module(name)
|
37
|
+
# last module plus class/instance method
|
38
|
+
i = name.rindex("::")
|
39
|
+
function = i ? name[(i + 2)..-1] : name
|
40
|
+
mod = i ? name[0...i] : nil
|
41
|
+
|
42
|
+
[function, mod]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/sentry/profiler.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "securerandom"
|
4
|
+
require_relative "profiler/helpers"
|
4
5
|
|
5
6
|
module Sentry
|
6
7
|
class Profiler
|
7
|
-
|
8
|
-
|
8
|
+
include Profiler::Helpers
|
9
|
+
|
10
|
+
VERSION = "1"
|
11
|
+
PLATFORM = "ruby"
|
9
12
|
# 101 Hz in microseconds
|
10
13
|
DEFAULT_INTERVAL = 1e6 / 101
|
11
14
|
MICRO_TO_NANO_SECONDS = 1e3
|
@@ -14,14 +17,14 @@ module Sentry
|
|
14
17
|
attr_reader :sampled, :started, :event_id
|
15
18
|
|
16
19
|
def initialize(configuration)
|
17
|
-
@event_id = SecureRandom.uuid.delete(
|
20
|
+
@event_id = SecureRandom.uuid.delete("-")
|
18
21
|
@started = false
|
19
22
|
@sampled = nil
|
20
23
|
|
21
24
|
@profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
|
22
25
|
@profiles_sample_rate = configuration.profiles_sample_rate
|
23
26
|
@project_root = configuration.project_root
|
24
|
-
@app_dirs_pattern = configuration.app_dirs_pattern
|
27
|
+
@app_dirs_pattern = configuration.app_dirs_pattern
|
25
28
|
@in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
|
26
29
|
end
|
27
30
|
|
@@ -33,7 +36,7 @@ module Sentry
|
|
33
36
|
raw: true,
|
34
37
|
aggregate: false)
|
35
38
|
|
36
|
-
@started ? log(
|
39
|
+
@started ? log("Started") : log("Not started since running elsewhere")
|
37
40
|
end
|
38
41
|
|
39
42
|
def stop
|
@@ -41,7 +44,11 @@ module Sentry
|
|
41
44
|
return unless @started
|
42
45
|
|
43
46
|
StackProf.stop
|
44
|
-
log(
|
47
|
+
log("Stopped")
|
48
|
+
end
|
49
|
+
|
50
|
+
def active_thread_id
|
51
|
+
"0"
|
45
52
|
end
|
46
53
|
|
47
54
|
# Sets initial sampling decision of the profile.
|
@@ -54,14 +61,14 @@ module Sentry
|
|
54
61
|
|
55
62
|
unless transaction_sampled
|
56
63
|
@sampled = false
|
57
|
-
log(
|
64
|
+
log("Discarding profile because transaction not sampled")
|
58
65
|
return
|
59
66
|
end
|
60
67
|
|
61
68
|
case @profiles_sample_rate
|
62
69
|
when 0.0
|
63
70
|
@sampled = false
|
64
|
-
log(
|
71
|
+
log("Discarding profile because sample_rate is 0")
|
65
72
|
return
|
66
73
|
when 1.0
|
67
74
|
@sampled = true
|
@@ -70,7 +77,7 @@ module Sentry
|
|
70
77
|
@sampled = Random.rand < @profiles_sample_rate
|
71
78
|
end
|
72
79
|
|
73
|
-
log(
|
80
|
+
log("Discarding profile due to sampling decision") unless @sampled
|
74
81
|
end
|
75
82
|
|
76
83
|
def to_hash
|
@@ -90,13 +97,12 @@ module Sentry
|
|
90
97
|
|
91
98
|
frame_map = {}
|
92
99
|
|
93
|
-
frames = results[:frames].
|
94
|
-
frame_id, frame_data = frame
|
95
|
-
|
100
|
+
frames = results[:frames].map.with_index do |(frame_id, frame_data), idx|
|
96
101
|
# need to map over stackprof frame ids to ours
|
97
102
|
frame_map[frame_id] = idx
|
98
103
|
|
99
104
|
file_path = frame_data[:file]
|
105
|
+
lineno = frame_data[:line]
|
100
106
|
in_app = in_app?(file_path)
|
101
107
|
filename = compute_filename(file_path, in_app)
|
102
108
|
function, mod = split_module(frame_data[:name])
|
@@ -109,7 +115,7 @@ module Sentry
|
|
109
115
|
}
|
110
116
|
|
111
117
|
frame_hash[:module] = mod if mod
|
112
|
-
frame_hash[:lineno] =
|
118
|
+
frame_hash[:lineno] = lineno if lineno && lineno >= 0
|
113
119
|
|
114
120
|
frame_hash
|
115
121
|
end
|
@@ -130,7 +136,7 @@ module Sentry
|
|
130
136
|
num_seen << results[:raw][idx + len]
|
131
137
|
idx += len + 1
|
132
138
|
|
133
|
-
log(
|
139
|
+
log("Unknown frame in stack") if stack.size != len
|
134
140
|
end
|
135
141
|
|
136
142
|
idx = 0
|
@@ -155,16 +161,16 @@ module Sentry
|
|
155
161
|
# Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
|
156
162
|
# we're profiling is idle/sleeping/waiting for IO etc.
|
157
163
|
# https://bugs.ruby-lang.org/issues/10602
|
158
|
-
thread_id:
|
164
|
+
thread_id: "0",
|
159
165
|
elapsed_since_start_ns: elapsed_since_start_ns.to_s
|
160
166
|
}
|
161
167
|
end
|
162
168
|
end
|
163
169
|
|
164
|
-
log(
|
170
|
+
log("Some samples thrown away") if samples.size != results[:samples]
|
165
171
|
|
166
172
|
if samples.size <= MIN_SAMPLES_REQUIRED
|
167
|
-
log(
|
173
|
+
log("Not enough samples, discarding profiler")
|
168
174
|
record_lost_event(:insufficient_data)
|
169
175
|
return {}
|
170
176
|
end
|
@@ -189,45 +195,8 @@ module Sentry
|
|
189
195
|
Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
|
190
196
|
end
|
191
197
|
|
192
|
-
def in_app?(abs_path)
|
193
|
-
abs_path.match?(@in_app_pattern)
|
194
|
-
end
|
195
|
-
|
196
|
-
# copied from stacktrace.rb since I don't want to touch existing code
|
197
|
-
# TODO-neel-profiler try to fetch this from stackprof once we patch
|
198
|
-
# the native extension
|
199
|
-
def compute_filename(abs_path, in_app)
|
200
|
-
return nil if abs_path.nil?
|
201
|
-
|
202
|
-
under_project_root = @project_root && abs_path.start_with?(@project_root)
|
203
|
-
|
204
|
-
prefix =
|
205
|
-
if under_project_root && in_app
|
206
|
-
@project_root
|
207
|
-
else
|
208
|
-
longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
|
209
|
-
|
210
|
-
if under_project_root
|
211
|
-
longest_load_path || @project_root
|
212
|
-
else
|
213
|
-
longest_load_path
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
218
|
-
end
|
219
|
-
|
220
|
-
def split_module(name)
|
221
|
-
# last module plus class/instance method
|
222
|
-
i = name.rindex('::')
|
223
|
-
function = i ? name[(i + 2)..-1] : name
|
224
|
-
mod = i ? name[0...i] : nil
|
225
|
-
|
226
|
-
[function, mod]
|
227
|
-
end
|
228
|
-
|
229
198
|
def record_lost_event(reason)
|
230
|
-
Sentry.get_current_client&.transport&.record_lost_event(reason,
|
199
|
+
Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
|
231
200
|
end
|
232
201
|
end
|
233
202
|
end
|
@@ -50,14 +50,15 @@ module Sentry
|
|
50
50
|
if sentry_trace_data
|
51
51
|
@trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
|
52
52
|
|
53
|
-
@baggage =
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
53
|
+
@baggage =
|
54
|
+
if baggage_header && !baggage_header.empty?
|
55
|
+
Baggage.from_incoming_header(baggage_header)
|
56
|
+
else
|
57
|
+
# If there's an incoming sentry-trace but no incoming baggage header,
|
58
|
+
# for instance in traces coming from older SDKs,
|
59
|
+
# baggage will be empty and frozen and won't be populated as head SDK.
|
60
|
+
Baggage.new({})
|
61
|
+
end
|
61
62
|
|
62
63
|
@baggage.freeze!
|
63
64
|
@incoming_trace = true
|
@@ -107,7 +108,7 @@ module Sentry
|
|
107
108
|
end
|
108
109
|
|
109
110
|
# Returns the Dynamic Sampling Context from the baggage.
|
110
|
-
# @return [
|
111
|
+
# @return [Hash, nil]
|
111
112
|
def get_dynamic_sampling_context
|
112
113
|
get_baggage&.dynamic_sampling_context
|
113
114
|
end
|
data/lib/sentry/puma.rb
CHANGED
@@ -4,6 +4,8 @@ module Sentry
|
|
4
4
|
module Rack
|
5
5
|
class CaptureExceptions
|
6
6
|
ERROR_EVENT_ID_KEY = "sentry.error_event_id"
|
7
|
+
MECHANISM_TYPE = "rack"
|
8
|
+
SPAN_ORIGIN = "auto.http.rack"
|
7
9
|
|
8
10
|
def initialize(app)
|
9
11
|
@app = app
|
@@ -48,21 +50,27 @@ module Sentry
|
|
48
50
|
private
|
49
51
|
|
50
52
|
def collect_exception(env)
|
51
|
-
env[
|
53
|
+
env["rack.exception"] || env["sinatra.error"]
|
52
54
|
end
|
53
55
|
|
54
56
|
def transaction_op
|
55
|
-
"http.server"
|
57
|
+
"http.server"
|
56
58
|
end
|
57
59
|
|
58
60
|
def capture_exception(exception, env)
|
59
|
-
Sentry.capture_exception(exception).tap do |event|
|
61
|
+
Sentry.capture_exception(exception, hint: { mechanism: mechanism }).tap do |event|
|
60
62
|
env[ERROR_EVENT_ID_KEY] = event.event_id if event
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
66
|
def start_transaction(env, scope)
|
65
|
-
options = {
|
67
|
+
options = {
|
68
|
+
name: scope.transaction_name,
|
69
|
+
source: scope.transaction_source,
|
70
|
+
op: transaction_op,
|
71
|
+
origin: SPAN_ORIGIN
|
72
|
+
}
|
73
|
+
|
66
74
|
transaction = Sentry.continue_trace(env, **options)
|
67
75
|
Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
|
68
76
|
end
|
@@ -74,6 +82,10 @@ module Sentry
|
|
74
82
|
transaction.set_http_status(status_code)
|
75
83
|
transaction.finish
|
76
84
|
end
|
85
|
+
|
86
|
+
def mechanism
|
87
|
+
Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
|
88
|
+
end
|
77
89
|
end
|
78
90
|
end
|
79
91
|
end
|
data/lib/sentry/rack.rb
CHANGED