sentry-ruby 5.18.2 → 5.28.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.
- checksums.yaml +4 -4
- data/.rspec +3 -1
- data/Gemfile +9 -3
- data/README.md +12 -7
- data/Rakefile +9 -11
- data/bin/console +1 -0
- data/lib/sentry/attachment.rb +40 -0
- data/lib/sentry/background_worker.rb +2 -3
- data/lib/sentry/backpressure_monitor.rb +1 -1
- data/lib/sentry/backtrace.rb +7 -8
- data/lib/sentry/baggage.rb +7 -7
- data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
- data/lib/sentry/breadcrumb.rb +5 -4
- data/lib/sentry/check_in_event.rb +5 -4
- data/lib/sentry/client.rb +121 -19
- data/lib/sentry/configuration.rb +207 -38
- data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
- data/lib/sentry/cron/monitor_check_ins.rb +6 -4
- data/lib/sentry/cron/monitor_config.rb +1 -1
- data/lib/sentry/debug_structured_logger.rb +94 -0
- data/lib/sentry/dsn.rb +35 -3
- data/lib/sentry/envelope/item.rb +88 -0
- data/lib/sentry/envelope.rb +2 -85
- data/lib/sentry/event.rb +13 -8
- data/lib/sentry/excon/middleware.rb +77 -0
- data/lib/sentry/excon.rb +10 -0
- data/lib/sentry/faraday.rb +77 -0
- data/lib/sentry/graphql.rb +1 -1
- data/lib/sentry/hub.rb +57 -4
- data/lib/sentry/interfaces/mechanism.rb +1 -1
- data/lib/sentry/interfaces/request.rb +6 -6
- data/lib/sentry/interfaces/single_exception.rb +3 -3
- data/lib/sentry/interfaces/stacktrace.rb +3 -1
- data/lib/sentry/interfaces/stacktrace_builder.rb +15 -2
- data/lib/sentry/linecache.rb +3 -3
- data/lib/sentry/log_event.rb +219 -0
- data/lib/sentry/log_event_buffer.rb +75 -0
- data/lib/sentry/logger.rb +1 -1
- data/lib/sentry/metrics/aggregator.rb +13 -13
- data/lib/sentry/metrics/configuration.rb +12 -2
- data/lib/sentry/metrics/set_metric.rb +2 -2
- data/lib/sentry/metrics/timing.rb +8 -0
- data/lib/sentry/metrics.rb +27 -15
- data/lib/sentry/net/http.rb +17 -38
- data/lib/sentry/profiler/helpers.rb +46 -0
- data/lib/sentry/profiler.rb +27 -57
- data/lib/sentry/propagation_context.rb +60 -23
- data/lib/sentry/rack/capture_exceptions.rb +2 -2
- data/lib/sentry/rack.rb +2 -2
- data/lib/sentry/rake.rb +2 -2
- data/lib/sentry/release_detector.rb +4 -4
- data/lib/sentry/rspec.rb +91 -0
- data/lib/sentry/scope.rb +27 -2
- data/lib/sentry/session_flusher.rb +10 -6
- data/lib/sentry/span.rb +17 -4
- data/lib/sentry/std_lib_logger.rb +55 -0
- data/lib/sentry/structured_logger.rb +138 -0
- data/lib/sentry/test_helper.rb +45 -1
- data/lib/sentry/threaded_periodic_worker.rb +3 -3
- data/lib/sentry/transaction.rb +41 -13
- data/lib/sentry/transaction_event.rb +5 -3
- data/lib/sentry/transport/debug_transport.rb +70 -0
- data/lib/sentry/transport/dummy_transport.rb +1 -0
- data/lib/sentry/transport/http_transport.rb +21 -18
- data/lib/sentry/transport.rb +26 -11
- data/lib/sentry/utils/env_helper.rb +21 -0
- data/lib/sentry/utils/http_tracing.rb +74 -0
- data/lib/sentry/utils/logging_helper.rb +10 -3
- data/lib/sentry/utils/real_ip.rb +1 -1
- data/lib/sentry/utils/sample_rand.rb +97 -0
- data/lib/sentry/utils/uuid.rb +13 -0
- data/lib/sentry/vernier/output.rb +89 -0
- data/lib/sentry/vernier/profiler.rb +132 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +106 -12
- data/sentry-ruby-core.gemspec +3 -1
- data/sentry-ruby.gemspec +4 -2
- metadata +28 -12
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
# @api private
|
|
5
|
+
class Envelope::Item
|
|
6
|
+
STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
|
|
7
|
+
MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000
|
|
8
|
+
|
|
9
|
+
SIZE_LIMITS = Hash.new(MAX_SERIALIZED_PAYLOAD_SIZE).update(
|
|
10
|
+
"profile" => 1024 * 1000 * 50
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
attr_reader :size_limit, :headers, :payload, :type, :data_category
|
|
14
|
+
|
|
15
|
+
# rate limits and client reports use the data_category rather than envelope item type
|
|
16
|
+
def self.data_category(type)
|
|
17
|
+
case type
|
|
18
|
+
when "session", "attachment", "transaction", "profile", "span", "log" then type
|
|
19
|
+
when "sessions" then "session"
|
|
20
|
+
when "check_in" then "monitor"
|
|
21
|
+
when "statsd", "metric_meta" then "metric_bucket"
|
|
22
|
+
when "event" then "error"
|
|
23
|
+
when "client_report" then "internal"
|
|
24
|
+
else "default"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(headers, payload)
|
|
29
|
+
@headers = headers
|
|
30
|
+
@payload = payload
|
|
31
|
+
@type = headers[:type] || "event"
|
|
32
|
+
@data_category = self.class.data_category(type)
|
|
33
|
+
@size_limit = SIZE_LIMITS[type]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s
|
|
37
|
+
[JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def serialize
|
|
41
|
+
result = to_s
|
|
42
|
+
|
|
43
|
+
if result.bytesize > size_limit
|
|
44
|
+
remove_breadcrumbs!
|
|
45
|
+
result = to_s
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if result.bytesize > size_limit
|
|
49
|
+
reduce_stacktrace!
|
|
50
|
+
result = to_s
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
[result, result.bytesize > size_limit]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def size_breakdown
|
|
57
|
+
payload.map do |key, value|
|
|
58
|
+
"#{key}: #{JSON.generate(value).bytesize}"
|
|
59
|
+
end.join(", ")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def remove_breadcrumbs!
|
|
65
|
+
if payload.key?(:breadcrumbs)
|
|
66
|
+
payload.delete(:breadcrumbs)
|
|
67
|
+
elsif payload.key?("breadcrumbs")
|
|
68
|
+
payload.delete("breadcrumbs")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def reduce_stacktrace!
|
|
73
|
+
if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
|
|
74
|
+
exceptions.each do |exception|
|
|
75
|
+
# in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
|
|
76
|
+
traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
|
|
77
|
+
|
|
78
|
+
if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
|
|
79
|
+
size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
|
|
80
|
+
traces.replace(
|
|
81
|
+
traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/sentry/envelope.rb
CHANGED
|
@@ -3,91 +3,6 @@
|
|
|
3
3
|
module Sentry
|
|
4
4
|
# @api private
|
|
5
5
|
class Envelope
|
|
6
|
-
class Item
|
|
7
|
-
STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
|
|
8
|
-
MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000
|
|
9
|
-
|
|
10
|
-
attr_accessor :headers, :payload
|
|
11
|
-
|
|
12
|
-
def initialize(headers, payload)
|
|
13
|
-
@headers = headers
|
|
14
|
-
@payload = payload
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def type
|
|
18
|
-
@headers[:type] || 'event'
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# rate limits and client reports use the data_category rather than envelope item type
|
|
22
|
-
def self.data_category(type)
|
|
23
|
-
case type
|
|
24
|
-
when 'session', 'attachment', 'transaction', 'profile', 'span' then type
|
|
25
|
-
when 'sessions' then 'session'
|
|
26
|
-
when 'check_in' then 'monitor'
|
|
27
|
-
when 'statsd', 'metric_meta' then 'metric_bucket'
|
|
28
|
-
when 'event' then 'error'
|
|
29
|
-
when 'client_report' then 'internal'
|
|
30
|
-
else 'default'
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def data_category
|
|
35
|
-
self.class.data_category(type)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def to_s
|
|
39
|
-
[JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def serialize
|
|
43
|
-
result = to_s
|
|
44
|
-
|
|
45
|
-
if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
|
|
46
|
-
remove_breadcrumbs!
|
|
47
|
-
result = to_s
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
|
|
51
|
-
reduce_stacktrace!
|
|
52
|
-
result = to_s
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
[result, result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE]
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def size_breakdown
|
|
59
|
-
payload.map do |key, value|
|
|
60
|
-
"#{key}: #{JSON.generate(value).bytesize}"
|
|
61
|
-
end.join(", ")
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
private
|
|
65
|
-
|
|
66
|
-
def remove_breadcrumbs!
|
|
67
|
-
if payload.key?(:breadcrumbs)
|
|
68
|
-
payload.delete(:breadcrumbs)
|
|
69
|
-
elsif payload.key?("breadcrumbs")
|
|
70
|
-
payload.delete("breadcrumbs")
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def reduce_stacktrace!
|
|
75
|
-
if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
|
|
76
|
-
exceptions.each do |exception|
|
|
77
|
-
# in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
|
|
78
|
-
traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
|
|
79
|
-
|
|
80
|
-
if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
|
|
81
|
-
size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
|
|
82
|
-
traces.replace(
|
|
83
|
-
traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
|
|
84
|
-
)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
6
|
attr_accessor :headers, :items
|
|
92
7
|
|
|
93
8
|
def initialize(headers = {})
|
|
@@ -108,3 +23,5 @@ module Sentry
|
|
|
108
23
|
end
|
|
109
24
|
end
|
|
110
25
|
end
|
|
26
|
+
|
|
27
|
+
require_relative "envelope/item"
|
data/lib/sentry/event.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
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
|
+
require "sentry/utils/custom_inspection"
|
|
10
|
+
require "sentry/utils/uuid"
|
|
10
11
|
|
|
11
12
|
module Sentry
|
|
12
13
|
# This is an abstract class that defines the shared attributes of an event.
|
|
@@ -42,12 +43,15 @@ module Sentry
|
|
|
42
43
|
# @return [Hash, nil]
|
|
43
44
|
attr_accessor :dynamic_sampling_context
|
|
44
45
|
|
|
46
|
+
# @return [Array<Attachment>]
|
|
47
|
+
attr_accessor :attachments
|
|
48
|
+
|
|
45
49
|
# @param configuration [Configuration]
|
|
46
50
|
# @param integration_meta [Hash, nil]
|
|
47
51
|
# @param message [String, nil]
|
|
48
52
|
def initialize(configuration:, integration_meta: nil, message: nil)
|
|
49
53
|
# Set some simple default values
|
|
50
|
-
@event_id =
|
|
54
|
+
@event_id = Utils.uuid
|
|
51
55
|
@timestamp = Sentry.utc_now.iso8601
|
|
52
56
|
@platform = :ruby
|
|
53
57
|
@type = self.class::TYPE
|
|
@@ -57,6 +61,7 @@ module Sentry
|
|
|
57
61
|
@extra = {}
|
|
58
62
|
@contexts = {}
|
|
59
63
|
@tags = {}
|
|
64
|
+
@attachments = []
|
|
60
65
|
|
|
61
66
|
@fingerprint = []
|
|
62
67
|
@dynamic_sampling_context = nil
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Excon
|
|
5
|
+
OP_NAME = "http.client"
|
|
6
|
+
|
|
7
|
+
class Middleware < ::Excon::Middleware::Base
|
|
8
|
+
def initialize(stack)
|
|
9
|
+
super
|
|
10
|
+
@instrumenter = Instrumenter.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def request_call(datum)
|
|
14
|
+
@instrumenter.start_transaction(datum)
|
|
15
|
+
@stack.request_call(datum)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def response_call(datum)
|
|
19
|
+
@instrumenter.finish_transaction(datum)
|
|
20
|
+
@stack.response_call(datum)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Instrumenter
|
|
25
|
+
SPAN_ORIGIN = "auto.http.excon"
|
|
26
|
+
BREADCRUMB_CATEGORY = "http"
|
|
27
|
+
|
|
28
|
+
include Utils::HttpTracing
|
|
29
|
+
|
|
30
|
+
def start_transaction(env)
|
|
31
|
+
return unless Sentry.initialized?
|
|
32
|
+
|
|
33
|
+
current_span = Sentry.get_current_scope&.span
|
|
34
|
+
@span = current_span&.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN)
|
|
35
|
+
|
|
36
|
+
request_info = extract_request_info(env)
|
|
37
|
+
|
|
38
|
+
if propagate_trace?(request_info[:url])
|
|
39
|
+
set_propagation_headers(env[:headers])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def finish_transaction(response)
|
|
44
|
+
return unless @span
|
|
45
|
+
|
|
46
|
+
response_status = response[:response][:status]
|
|
47
|
+
request_info = extract_request_info(response)
|
|
48
|
+
|
|
49
|
+
if record_sentry_breadcrumb?
|
|
50
|
+
record_sentry_breadcrumb(request_info, response_status)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
set_span_info(@span, request_info, response_status)
|
|
54
|
+
ensure
|
|
55
|
+
@span&.finish
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def extract_request_info(env)
|
|
61
|
+
url = env[:scheme] + "://" + env[:hostname] + env[:path]
|
|
62
|
+
result = { method: env[:method].to_s.upcase, url: url }
|
|
63
|
+
|
|
64
|
+
if Sentry.configuration.send_default_pii
|
|
65
|
+
result[:query] = env[:query]
|
|
66
|
+
|
|
67
|
+
# Handle excon 1.0.0+
|
|
68
|
+
result[:query] = build_nested_query(result[:query]) unless result[:query].is_a?(String)
|
|
69
|
+
|
|
70
|
+
result[:body] = env[:body]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
result
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/sentry/excon.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sentry.register_patch(:excon) do
|
|
4
|
+
if defined?(::Excon)
|
|
5
|
+
require "sentry/excon/middleware"
|
|
6
|
+
if Excon.defaults[:middlewares]
|
|
7
|
+
Excon.defaults[:middlewares] << Sentry::Excon::Middleware unless Excon.defaults[:middlewares].include?(Sentry::Excon::Middleware)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Faraday
|
|
5
|
+
OP_NAME = "http.client"
|
|
6
|
+
|
|
7
|
+
module Connection
|
|
8
|
+
# Since there's no way to preconfigure Faraday connections and add our instrumentation
|
|
9
|
+
# by default, we need to extend the connection constructor and do it there
|
|
10
|
+
#
|
|
11
|
+
# @see https://lostisland.github.io/faraday/#/customization/index?id=configuration
|
|
12
|
+
def initialize(url = nil, options = nil)
|
|
13
|
+
super
|
|
14
|
+
|
|
15
|
+
# Ensure that we attach instrumentation only if the adapter is not net/http
|
|
16
|
+
# because if is is, then the net/http instrumentation will take care of it
|
|
17
|
+
if builder.adapter.name != "Faraday::Adapter::NetHttp"
|
|
18
|
+
# Make sure that it's going to be the first middleware so that it can capture
|
|
19
|
+
# the entire request processing involving other middlewares
|
|
20
|
+
builder.insert(0, ::Faraday::Request::Instrumentation, name: OP_NAME, instrumenter: Instrumenter.new)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Instrumenter
|
|
26
|
+
SPAN_ORIGIN = "auto.http.faraday"
|
|
27
|
+
BREADCRUMB_CATEGORY = "http"
|
|
28
|
+
|
|
29
|
+
include Utils::HttpTracing
|
|
30
|
+
|
|
31
|
+
def instrument(op_name, env, &block)
|
|
32
|
+
return block.call unless Sentry.initialized?
|
|
33
|
+
|
|
34
|
+
Sentry.with_child_span(op: op_name, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
|
|
35
|
+
request_info = extract_request_info(env)
|
|
36
|
+
|
|
37
|
+
if propagate_trace?(request_info[:url])
|
|
38
|
+
set_propagation_headers(env[:request_headers])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
res = block.call
|
|
42
|
+
response_status = res.status
|
|
43
|
+
|
|
44
|
+
if record_sentry_breadcrumb?
|
|
45
|
+
record_sentry_breadcrumb(request_info, response_status)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if sentry_span
|
|
49
|
+
set_span_info(sentry_span, request_info, response_status)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
res
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def extract_request_info(env)
|
|
59
|
+
url = env[:url].scheme + "://" + env[:url].host + env[:url].path
|
|
60
|
+
result = { method: env[:method].to_s.upcase, url: url }
|
|
61
|
+
|
|
62
|
+
if Sentry.configuration.send_default_pii
|
|
63
|
+
result[:query] = env[:url].query
|
|
64
|
+
result[:body] = env[:body]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Sentry.register_patch(:faraday) do
|
|
74
|
+
if defined?(::Faraday)
|
|
75
|
+
::Faraday::Connection.prepend(Sentry::Faraday::Connection)
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/sentry/graphql.rb
CHANGED
|
@@ -4,6 +4,6 @@ Sentry.register_patch(:graphql) do |config|
|
|
|
4
4
|
if defined?(::GraphQL::Schema) && defined?(::GraphQL::Tracing::SentryTrace) && ::GraphQL::Schema.respond_to?(:trace_with)
|
|
5
5
|
::GraphQL::Schema.trace_with(::GraphQL::Tracing::SentryTrace, set_transaction_name: true)
|
|
6
6
|
else
|
|
7
|
-
config.
|
|
7
|
+
config.sdk_logger.warn(Sentry::LOGGER_PROGNAME) { "You tried to enable the GraphQL integration but no GraphQL gem was detected. Make sure you have the `graphql` gem (>= 2.2.6) in your Gemfile." }
|
|
8
8
|
end
|
|
9
9
|
end
|
data/lib/sentry/hub.rb
CHANGED
|
@@ -8,12 +8,42 @@ module Sentry
|
|
|
8
8
|
class Hub
|
|
9
9
|
include ArgumentCheckingHelper
|
|
10
10
|
|
|
11
|
+
MUTEX = Mutex.new
|
|
12
|
+
|
|
11
13
|
attr_reader :last_event_id
|
|
12
14
|
|
|
15
|
+
attr_reader :current_profiler
|
|
16
|
+
|
|
13
17
|
def initialize(client, scope)
|
|
14
18
|
first_layer = Layer.new(client, scope)
|
|
15
19
|
@stack = [first_layer]
|
|
16
20
|
@last_event_id = nil
|
|
21
|
+
@current_profiler = {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# This is an internal private method
|
|
25
|
+
# @api private
|
|
26
|
+
def start_profiler!(transaction)
|
|
27
|
+
MUTEX.synchronize do
|
|
28
|
+
transaction.start_profiler!
|
|
29
|
+
@current_profiler[transaction.__id__] = transaction.profiler
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# This is an internal private method
|
|
34
|
+
# @api private
|
|
35
|
+
def stop_profiler!(transaction)
|
|
36
|
+
MUTEX.synchronize do
|
|
37
|
+
@current_profiler.delete(transaction.__id__)&.stop
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# This is an internal private method
|
|
42
|
+
# @api private
|
|
43
|
+
def profiler_running?
|
|
44
|
+
MUTEX.synchronize do
|
|
45
|
+
!@current_profiler.empty?
|
|
46
|
+
end
|
|
17
47
|
end
|
|
18
48
|
|
|
19
49
|
def new_from_top
|
|
@@ -73,7 +103,13 @@ module Sentry
|
|
|
73
103
|
end
|
|
74
104
|
|
|
75
105
|
def pop_scope
|
|
76
|
-
@stack.
|
|
106
|
+
if @stack.size > 1
|
|
107
|
+
@stack.pop
|
|
108
|
+
else
|
|
109
|
+
# We never want to enter a situation where we have no scope and no client
|
|
110
|
+
client = current_client
|
|
111
|
+
@stack = [Layer.new(client, Scope.new)]
|
|
112
|
+
end
|
|
77
113
|
end
|
|
78
114
|
|
|
79
115
|
def start_transaction(transaction: nil, custom_sampling_context: {}, instrumenter: :sentry, **options)
|
|
@@ -84,13 +120,14 @@ module Sentry
|
|
|
84
120
|
|
|
85
121
|
sampling_context = {
|
|
86
122
|
transaction_context: transaction.to_hash,
|
|
87
|
-
parent_sampled: transaction.parent_sampled
|
|
123
|
+
parent_sampled: transaction.parent_sampled,
|
|
124
|
+
parent_sample_rate: transaction.parent_sample_rate
|
|
88
125
|
}
|
|
89
126
|
|
|
90
127
|
sampling_context.merge!(custom_sampling_context)
|
|
91
128
|
transaction.set_initial_sample_decision(sampling_context: sampling_context)
|
|
92
129
|
|
|
93
|
-
|
|
130
|
+
start_profiler!(transaction)
|
|
94
131
|
|
|
95
132
|
transaction
|
|
96
133
|
end
|
|
@@ -180,6 +217,16 @@ module Sentry
|
|
|
180
217
|
event.check_in_id
|
|
181
218
|
end
|
|
182
219
|
|
|
220
|
+
def capture_log_event(message, **options)
|
|
221
|
+
return unless current_client
|
|
222
|
+
|
|
223
|
+
event = current_client.event_from_log(message, **options)
|
|
224
|
+
|
|
225
|
+
return unless event
|
|
226
|
+
|
|
227
|
+
current_client.buffer_log_event(event, current_scope)
|
|
228
|
+
end
|
|
229
|
+
|
|
183
230
|
def capture_event(event, **options, &block)
|
|
184
231
|
check_argument_type!(event, Sentry::Event)
|
|
185
232
|
|
|
@@ -214,6 +261,7 @@ module Sentry
|
|
|
214
261
|
end
|
|
215
262
|
|
|
216
263
|
def add_breadcrumb(breadcrumb, hint: {})
|
|
264
|
+
return unless current_client
|
|
217
265
|
return unless configuration.enabled_in_current_env?
|
|
218
266
|
|
|
219
267
|
if before_breadcrumb = current_client.configuration.before_breadcrumb
|
|
@@ -248,7 +296,11 @@ module Sentry
|
|
|
248
296
|
|
|
249
297
|
return unless session
|
|
250
298
|
session.close
|
|
251
|
-
|
|
299
|
+
|
|
300
|
+
# NOTE: Under some circumstances, session_flusher nilified out of sync
|
|
301
|
+
# See: https://github.com/getsentry/sentry-ruby/issues/2378
|
|
302
|
+
# See: https://github.com/getsentry/sentry-ruby/pull/2396
|
|
303
|
+
Sentry.session_flusher&.add_session(session)
|
|
252
304
|
end
|
|
253
305
|
|
|
254
306
|
def with_session_tracking(&block)
|
|
@@ -306,6 +358,7 @@ module Sentry
|
|
|
306
358
|
parent_span_id: propagation_context.parent_span_id,
|
|
307
359
|
parent_sampled: propagation_context.parent_sampled,
|
|
308
360
|
baggage: propagation_context.baggage,
|
|
361
|
+
sample_rand: propagation_context.sample_rand,
|
|
309
362
|
**options
|
|
310
363
|
)
|
|
311
364
|
end
|
|
@@ -59,7 +59,7 @@ module Sentry
|
|
|
59
59
|
self.query_string = request.query_string
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
self.url = request.scheme && request.url.split(
|
|
62
|
+
self.url = request.scheme && request.url.split("?").first
|
|
63
63
|
self.method = request.request_method
|
|
64
64
|
|
|
65
65
|
self.headers = filter_and_format_headers(env, send_default_pii)
|
|
@@ -85,21 +85,21 @@ module Sentry
|
|
|
85
85
|
env.each_with_object({}) do |(key, value), memo|
|
|
86
86
|
begin
|
|
87
87
|
key = key.to_s # rack env can contain symbols
|
|
88
|
-
next memo[
|
|
88
|
+
next memo["X-Request-Id"] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
|
|
89
89
|
next if is_server_protocol?(key, value, env["SERVER_PROTOCOL"])
|
|
90
90
|
next if is_skippable_header?(key)
|
|
91
91
|
next if key == "HTTP_AUTHORIZATION" && !send_default_pii
|
|
92
92
|
|
|
93
93
|
# Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
|
|
94
94
|
key = key.sub(/^HTTP_/, "")
|
|
95
|
-
key = key.split(
|
|
95
|
+
key = key.split("_").map(&:capitalize).join("-")
|
|
96
96
|
|
|
97
97
|
memo[key] = Utils::EncodingHelper.encode_to_utf_8(value.to_s)
|
|
98
98
|
rescue StandardError => e
|
|
99
99
|
# Rails adds objects to the Rack env that can sometimes raise exceptions
|
|
100
100
|
# when `to_s` is called.
|
|
101
101
|
# See: https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/remote_ip.rb#L134
|
|
102
|
-
Sentry.
|
|
102
|
+
Sentry.sdk_logger.warn(LOGGER_PROGNAME) { "Error raised while formatting headers: #{e.message}" }
|
|
103
103
|
next
|
|
104
104
|
end
|
|
105
105
|
end
|
|
@@ -108,7 +108,7 @@ module Sentry
|
|
|
108
108
|
def is_skippable_header?(key)
|
|
109
109
|
key.upcase != key || # lower-case envs aren't real http headers
|
|
110
110
|
key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
|
|
111
|
-
!(key.start_with?(
|
|
111
|
+
!(key.start_with?("HTTP_") || CONTENT_HEADERS.include?(key))
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
# In versions < 3, Rack adds in an incorrect HTTP_VERSION key, which causes downstream
|
|
@@ -120,7 +120,7 @@ module Sentry
|
|
|
120
120
|
rack_version = Gem::Version.new(::Rack.release)
|
|
121
121
|
return false if rack_version >= Gem::Version.new("3.0")
|
|
122
122
|
|
|
123
|
-
key ==
|
|
123
|
+
key == "HTTP_VERSION" && value == protocol_version
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
def filter_and_format_env(env, rack_env_whitelist)
|
|
@@ -7,8 +7,8 @@ module Sentry
|
|
|
7
7
|
include CustomInspection
|
|
8
8
|
|
|
9
9
|
SKIP_INSPECTION_ATTRIBUTES = [:@stacktrace]
|
|
10
|
-
PROBLEMATIC_LOCAL_VALUE_REPLACEMENT = "[ignored due to error]"
|
|
11
|
-
OMISSION_MARK = "..."
|
|
10
|
+
PROBLEMATIC_LOCAL_VALUE_REPLACEMENT = "[ignored due to error]"
|
|
11
|
+
OMISSION_MARK = "..."
|
|
12
12
|
MAX_LOCAL_BYTES = 1024
|
|
13
13
|
|
|
14
14
|
attr_reader :type, :module, :thread_id, :stacktrace, :mechanism
|
|
@@ -26,7 +26,7 @@ module Sentry
|
|
|
26
26
|
|
|
27
27
|
@value = Utils::EncodingHelper.encode_to_utf_8(exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES))
|
|
28
28
|
|
|
29
|
-
@module = exception.class.to_s.split(
|
|
29
|
+
@module = exception.class.to_s.split("::")[0...-1].join("::")
|
|
30
30
|
@thread_id = Thread.current.object_id
|
|
31
31
|
@stacktrace = stacktrace
|
|
32
32
|
@mechanism = mechanism
|
|
@@ -27,8 +27,9 @@ module Sentry
|
|
|
27
27
|
attr_accessor :abs_path, :context_line, :function, :in_app, :filename,
|
|
28
28
|
:lineno, :module, :pre_context, :post_context, :vars
|
|
29
29
|
|
|
30
|
-
def initialize(project_root, line)
|
|
30
|
+
def initialize(project_root, line, strip_backtrace_load_path = true)
|
|
31
31
|
@project_root = project_root
|
|
32
|
+
@strip_backtrace_load_path = strip_backtrace_load_path
|
|
32
33
|
|
|
33
34
|
@abs_path = line.file
|
|
34
35
|
@function = line.method if line.method
|
|
@@ -44,6 +45,7 @@ module Sentry
|
|
|
44
45
|
|
|
45
46
|
def compute_filename
|
|
46
47
|
return if abs_path.nil?
|
|
48
|
+
return abs_path unless @strip_backtrace_load_path
|
|
47
49
|
|
|
48
50
|
prefix =
|
|
49
51
|
if under_project_root? && in_app
|
|
@@ -17,22 +17,35 @@ module Sentry
|
|
|
17
17
|
# @return [Proc, nil]
|
|
18
18
|
attr_reader :backtrace_cleanup_callback
|
|
19
19
|
|
|
20
|
+
# @return [Boolean]
|
|
21
|
+
attr_reader :strip_backtrace_load_path
|
|
22
|
+
|
|
20
23
|
# @param project_root [String]
|
|
21
24
|
# @param app_dirs_pattern [Regexp, nil]
|
|
22
25
|
# @param linecache [LineCache]
|
|
23
26
|
# @param context_lines [Integer, nil]
|
|
24
27
|
# @param backtrace_cleanup_callback [Proc, nil]
|
|
28
|
+
# @param strip_backtrace_load_path [Boolean]
|
|
25
29
|
# @see Configuration#project_root
|
|
26
30
|
# @see Configuration#app_dirs_pattern
|
|
27
31
|
# @see Configuration#linecache
|
|
28
32
|
# @see Configuration#context_lines
|
|
29
33
|
# @see Configuration#backtrace_cleanup_callback
|
|
30
|
-
|
|
34
|
+
# @see Configuration#strip_backtrace_load_path
|
|
35
|
+
def initialize(
|
|
36
|
+
project_root:,
|
|
37
|
+
app_dirs_pattern:,
|
|
38
|
+
linecache:,
|
|
39
|
+
context_lines:,
|
|
40
|
+
backtrace_cleanup_callback: nil,
|
|
41
|
+
strip_backtrace_load_path: true
|
|
42
|
+
)
|
|
31
43
|
@project_root = project_root
|
|
32
44
|
@app_dirs_pattern = app_dirs_pattern
|
|
33
45
|
@linecache = linecache
|
|
34
46
|
@context_lines = context_lines
|
|
35
47
|
@backtrace_cleanup_callback = backtrace_cleanup_callback
|
|
48
|
+
@strip_backtrace_load_path = strip_backtrace_load_path
|
|
36
49
|
end
|
|
37
50
|
|
|
38
51
|
# Generates a StacktraceInterface with the given backtrace.
|
|
@@ -73,7 +86,7 @@ module Sentry
|
|
|
73
86
|
private
|
|
74
87
|
|
|
75
88
|
def convert_parsed_line_into_frame(line)
|
|
76
|
-
frame = StacktraceInterface::Frame.new(project_root, line)
|
|
89
|
+
frame = StacktraceInterface::Frame.new(project_root, line, strip_backtrace_load_path)
|
|
77
90
|
frame.set_context(linecache, context_lines) if context_lines
|
|
78
91
|
frame
|
|
79
92
|
end
|