sentry-ruby 5.26.0 → 6.4.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 +27 -5
- data/README.md +3 -3
- data/lib/sentry/background_worker.rb +1 -4
- data/lib/sentry/backtrace/line.rb +99 -0
- data/lib/sentry/backtrace.rb +44 -76
- data/lib/sentry/baggage.rb +1 -1
- data/lib/sentry/breadcrumb.rb +1 -1
- data/lib/sentry/breadcrumb_buffer.rb +2 -2
- data/lib/sentry/check_in_event.rb +2 -2
- data/lib/sentry/client.rb +59 -136
- data/lib/sentry/configuration.rb +176 -78
- data/lib/sentry/cron/monitor_check_ins.rb +3 -3
- data/lib/sentry/cron/monitor_config.rb +2 -2
- data/lib/sentry/cron/monitor_schedule.rb +2 -2
- data/lib/sentry/debug_structured_logger.rb +94 -0
- data/lib/sentry/dsn.rb +52 -0
- data/lib/sentry/envelope/item.rb +3 -3
- data/lib/sentry/error_event.rb +3 -3
- data/lib/sentry/event.rb +4 -10
- data/lib/sentry/graphql.rb +1 -1
- data/lib/sentry/hub.rb +29 -5
- data/lib/sentry/interface.rb +1 -1
- data/lib/sentry/interfaces/exception.rb +2 -2
- data/lib/sentry/interfaces/request.rb +2 -0
- data/lib/sentry/interfaces/single_exception.rb +4 -4
- data/lib/sentry/interfaces/stacktrace.rb +3 -3
- data/lib/sentry/interfaces/stacktrace_builder.rb +0 -8
- data/lib/sentry/interfaces/threads.rb +2 -2
- data/lib/sentry/log_event.rb +33 -138
- data/lib/sentry/log_event_buffer.rb +13 -60
- data/lib/sentry/metric_event.rb +49 -0
- data/lib/sentry/metric_event_buffer.rb +28 -0
- data/lib/sentry/metrics.rb +47 -42
- data/lib/sentry/profiler.rb +4 -5
- data/lib/sentry/propagation_context.rb +55 -18
- data/lib/sentry/rack/capture_exceptions.rb +88 -2
- data/lib/sentry/rspec.rb +1 -1
- data/lib/sentry/scope.rb +50 -18
- data/lib/sentry/sequel.rb +35 -0
- data/lib/sentry/span.rb +5 -17
- data/lib/sentry/std_lib_logger.rb +10 -1
- data/lib/sentry/telemetry_event_buffer.rb +130 -0
- data/lib/sentry/test_helper.rb +30 -0
- data/lib/sentry/transaction.rb +72 -95
- data/lib/sentry/transaction_event.rb +4 -9
- 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 +10 -16
- data/lib/sentry/transport.rb +4 -6
- data/lib/sentry/utils/encoding_helper.rb +6 -0
- data/lib/sentry/utils/logging_helper.rb +25 -9
- data/lib/sentry/utils/sample_rand.rb +97 -0
- data/lib/sentry/utils/telemetry_attributes.rb +30 -0
- data/lib/sentry/vernier/profiler.rb +4 -3
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +57 -30
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +2 -1
- metadata +31 -17
- data/lib/sentry/metrics/aggregator.rb +0 -248
- data/lib/sentry/metrics/configuration.rb +0 -47
- data/lib/sentry/metrics/counter_metric.rb +0 -25
- data/lib/sentry/metrics/distribution_metric.rb +0 -25
- data/lib/sentry/metrics/gauge_metric.rb +0 -35
- data/lib/sentry/metrics/local_aggregator.rb +0 -53
- data/lib/sentry/metrics/metric.rb +0 -19
- data/lib/sentry/metrics/set_metric.rb +0 -28
- data/lib/sentry/metrics/timing.rb +0 -51
data/lib/sentry/metrics.rb
CHANGED
|
@@ -1,55 +1,60 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sentry/
|
|
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"
|
|
3
|
+
require "sentry/metric_event"
|
|
10
4
|
|
|
11
5
|
module Sentry
|
|
12
6
|
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
7
|
class << self
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
8
|
+
# Increments a counter metric
|
|
9
|
+
# @param name [String] the metric name
|
|
10
|
+
# @param value [Numeric] the value to increment by (default: 1)
|
|
11
|
+
# @param attributes [Hash, nil] additional attributes for the metric (optional)
|
|
12
|
+
# @return [void]
|
|
13
|
+
def count(name, value: 1, attributes: nil)
|
|
14
|
+
return unless Sentry.initialized?
|
|
15
|
+
|
|
16
|
+
Sentry.get_current_hub.capture_metric(
|
|
17
|
+
name: name,
|
|
18
|
+
type: :counter,
|
|
19
|
+
value: value,
|
|
20
|
+
attributes: attributes
|
|
21
|
+
)
|
|
31
22
|
end
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
# Records a gauge metric
|
|
25
|
+
# @param name [String] the metric name
|
|
26
|
+
# @param value [Numeric] the gauge value
|
|
27
|
+
# @param unit [String, nil] the metric unit (optional)
|
|
28
|
+
# @param attributes [Hash, nil] additional attributes for the metric (optional)
|
|
29
|
+
# @return [void]
|
|
30
|
+
def gauge(name, value, unit: nil, attributes: nil)
|
|
31
|
+
return unless Sentry.initialized?
|
|
32
|
+
|
|
33
|
+
Sentry.get_current_hub.capture_metric(
|
|
34
|
+
name: name,
|
|
35
|
+
type: :gauge,
|
|
36
|
+
value: value,
|
|
37
|
+
unit: unit,
|
|
38
|
+
attributes: attributes
|
|
39
|
+
)
|
|
35
40
|
end
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
# Records a distribution metric
|
|
43
|
+
# @param name [String] the metric name
|
|
44
|
+
# @param value [Numeric] the distribution value
|
|
45
|
+
# @param unit [String, nil] the metric unit (optional)
|
|
46
|
+
# @param attributes [Hash, nil] additional attributes for the metric (optional)
|
|
47
|
+
# @return [void]
|
|
48
|
+
def distribution(name, value, unit: nil, attributes: nil)
|
|
49
|
+
return unless Sentry.initialized?
|
|
50
|
+
|
|
51
|
+
Sentry.get_current_hub.capture_metric(
|
|
52
|
+
name: name,
|
|
53
|
+
type: :distribution,
|
|
54
|
+
value: value,
|
|
55
|
+
unit: unit,
|
|
56
|
+
attributes: attributes
|
|
57
|
+
)
|
|
53
58
|
end
|
|
54
59
|
end
|
|
55
60
|
end
|
data/lib/sentry/profiler.rb
CHANGED
|
@@ -10,8 +10,6 @@ module Sentry
|
|
|
10
10
|
|
|
11
11
|
VERSION = "1"
|
|
12
12
|
PLATFORM = "ruby"
|
|
13
|
-
# 101 Hz in microseconds
|
|
14
|
-
DEFAULT_INTERVAL = 1e6 / 101
|
|
15
13
|
MICRO_TO_NANO_SECONDS = 1e3
|
|
16
14
|
MIN_SAMPLES_REQUIRED = 2
|
|
17
15
|
|
|
@@ -24,6 +22,7 @@ module Sentry
|
|
|
24
22
|
|
|
25
23
|
@profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
|
|
26
24
|
@profiles_sample_rate = configuration.profiles_sample_rate
|
|
25
|
+
@profiles_sample_interval = configuration.profiles_sample_interval
|
|
27
26
|
@project_root = configuration.project_root
|
|
28
27
|
@app_dirs_pattern = configuration.app_dirs_pattern
|
|
29
28
|
@in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
|
|
@@ -32,7 +31,7 @@ module Sentry
|
|
|
32
31
|
def start
|
|
33
32
|
return unless @sampled
|
|
34
33
|
|
|
35
|
-
@started = StackProf.start(interval:
|
|
34
|
+
@started = StackProf.start(interval: @profiles_sample_interval,
|
|
36
35
|
mode: :wall,
|
|
37
36
|
raw: true,
|
|
38
37
|
aggregate: false)
|
|
@@ -81,9 +80,9 @@ module Sentry
|
|
|
81
80
|
log("Discarding profile due to sampling decision") unless @sampled
|
|
82
81
|
end
|
|
83
82
|
|
|
84
|
-
def
|
|
83
|
+
def to_h
|
|
85
84
|
unless @sampled
|
|
86
|
-
record_lost_event(:sample_rate)
|
|
85
|
+
record_lost_event(:sample_rate) if @profiling_enabled
|
|
87
86
|
return {}
|
|
88
87
|
end
|
|
89
88
|
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
require "sentry/baggage"
|
|
5
5
|
require "sentry/utils/uuid"
|
|
6
|
+
require "sentry/utils/sample_rand"
|
|
6
7
|
|
|
7
8
|
module Sentry
|
|
8
9
|
class PropagationContext
|
|
9
10
|
SENTRY_TRACE_REGEXP = Regexp.new(
|
|
10
|
-
"
|
|
11
|
-
"([0-9a-f]{32})?" + # trace_id
|
|
11
|
+
"\\A([0-9a-f]{32})?" + # trace_id
|
|
12
12
|
"-?([0-9a-f]{16})?" + # span_id
|
|
13
|
-
"-?([01])
|
|
14
|
-
"[ \t]*$" # whitespace
|
|
13
|
+
"-?([01])?\\z" # sampled
|
|
15
14
|
)
|
|
16
15
|
|
|
17
16
|
# An uuid that can be used to identify a trace.
|
|
@@ -33,6 +32,53 @@ module Sentry
|
|
|
33
32
|
# Please use the #get_baggage method for interfacing outside this class.
|
|
34
33
|
# @return [Baggage, nil]
|
|
35
34
|
attr_reader :baggage
|
|
35
|
+
# The propagated random value used for sampling decisions.
|
|
36
|
+
# @return [Float, nil]
|
|
37
|
+
attr_reader :sample_rand
|
|
38
|
+
|
|
39
|
+
# Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
|
|
40
|
+
#
|
|
41
|
+
# @param sentry_trace [String] the sentry-trace header value from the previous transaction.
|
|
42
|
+
# @return [Array, nil]
|
|
43
|
+
def self.extract_sentry_trace(sentry_trace)
|
|
44
|
+
value = sentry_trace.to_s.strip
|
|
45
|
+
return if value.empty?
|
|
46
|
+
|
|
47
|
+
match = SENTRY_TRACE_REGEXP.match(value)
|
|
48
|
+
return if match.nil?
|
|
49
|
+
|
|
50
|
+
trace_id, parent_span_id, sampled_flag = match[1..3]
|
|
51
|
+
parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
|
|
52
|
+
|
|
53
|
+
[trace_id, parent_span_id, parent_sampled]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.extract_sample_rand_from_baggage(baggage, trace_id = nil)
|
|
57
|
+
return unless baggage&.items
|
|
58
|
+
|
|
59
|
+
sample_rand_str = baggage.items["sample_rand"]
|
|
60
|
+
return unless sample_rand_str
|
|
61
|
+
|
|
62
|
+
generator = Utils::SampleRand.new(trace_id: trace_id)
|
|
63
|
+
generator.generate_from_value(sample_rand_str)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.generate_sample_rand(baggage, trace_id, parent_sampled)
|
|
67
|
+
generator = Utils::SampleRand.new(trace_id: trace_id)
|
|
68
|
+
|
|
69
|
+
if baggage&.items && !parent_sampled.nil?
|
|
70
|
+
sample_rate_str = baggage.items["sample_rate"]
|
|
71
|
+
sample_rate = sample_rate_str&.to_f
|
|
72
|
+
|
|
73
|
+
if sample_rate && !parent_sampled.nil?
|
|
74
|
+
generator.generate_from_sampling_decision(parent_sampled, sample_rate)
|
|
75
|
+
else
|
|
76
|
+
generator.generate_from_trace_id
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
generator.generate_from_trace_id
|
|
80
|
+
end
|
|
81
|
+
end
|
|
36
82
|
|
|
37
83
|
def initialize(scope, env = nil)
|
|
38
84
|
@scope = scope
|
|
@@ -40,6 +86,7 @@ module Sentry
|
|
|
40
86
|
@parent_sampled = nil
|
|
41
87
|
@baggage = nil
|
|
42
88
|
@incoming_trace = false
|
|
89
|
+
@sample_rand = nil
|
|
43
90
|
|
|
44
91
|
if env
|
|
45
92
|
sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
|
|
@@ -61,6 +108,8 @@ module Sentry
|
|
|
61
108
|
Baggage.new({})
|
|
62
109
|
end
|
|
63
110
|
|
|
111
|
+
@sample_rand = self.class.extract_sample_rand_from_baggage(@baggage, @trace_id)
|
|
112
|
+
|
|
64
113
|
@baggage.freeze!
|
|
65
114
|
@incoming_trace = true
|
|
66
115
|
end
|
|
@@ -69,20 +118,7 @@ module Sentry
|
|
|
69
118
|
|
|
70
119
|
@trace_id ||= Utils.uuid
|
|
71
120
|
@span_id = Utils.uuid.slice(0, 16)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
# Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
|
|
75
|
-
#
|
|
76
|
-
# @param sentry_trace [String] the sentry-trace header value from the previous transaction.
|
|
77
|
-
# @return [Array, nil]
|
|
78
|
-
def self.extract_sentry_trace(sentry_trace)
|
|
79
|
-
match = SENTRY_TRACE_REGEXP.match(sentry_trace)
|
|
80
|
-
return nil if match.nil?
|
|
81
|
-
|
|
82
|
-
trace_id, parent_span_id, sampled_flag = match[1..3]
|
|
83
|
-
parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
|
|
84
|
-
|
|
85
|
-
[trace_id, parent_span_id, parent_sampled]
|
|
121
|
+
@sample_rand ||= self.class.generate_sample_rand(@baggage, @trace_id, @parent_sampled)
|
|
86
122
|
end
|
|
87
123
|
|
|
88
124
|
# Returns the trace context that can be used to embed in an Event.
|
|
@@ -123,6 +159,7 @@ module Sentry
|
|
|
123
159
|
|
|
124
160
|
items = {
|
|
125
161
|
"trace_id" => trace_id,
|
|
162
|
+
"sample_rand" => Utils::SampleRand.format(@sample_rand),
|
|
126
163
|
"environment" => configuration.environment,
|
|
127
164
|
"release" => configuration.release,
|
|
128
165
|
"public_key" => configuration.dsn&.public_key
|
|
@@ -33,7 +33,7 @@ module Sentry
|
|
|
33
33
|
raise # Don't capture Sentry errors
|
|
34
34
|
rescue Exception => e
|
|
35
35
|
capture_exception(e, env)
|
|
36
|
-
finish_transaction(transaction,
|
|
36
|
+
finish_transaction(transaction, status_code_for_exception(e))
|
|
37
37
|
raise
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -72,7 +72,14 @@ module Sentry
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
transaction = Sentry.continue_trace(env, **options)
|
|
75
|
-
Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
|
|
75
|
+
transaction = Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
|
|
76
|
+
|
|
77
|
+
# attach queue time if available
|
|
78
|
+
if transaction && (queue_time = extract_queue_time(env))
|
|
79
|
+
transaction.set_data(Span::DataConventions::HTTP_QUEUE_TIME_MS, queue_time)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
transaction
|
|
76
83
|
end
|
|
77
84
|
|
|
78
85
|
|
|
@@ -86,6 +93,85 @@ module Sentry
|
|
|
86
93
|
def mechanism
|
|
87
94
|
Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
|
|
88
95
|
end
|
|
96
|
+
|
|
97
|
+
# Extracts queue time from the request environment.
|
|
98
|
+
# Calculates the time (in milliseconds) the request spent waiting in the
|
|
99
|
+
# web server queue before processing began.
|
|
100
|
+
#
|
|
101
|
+
# Subtracts puma.request_body_wait to account for time spent waiting for
|
|
102
|
+
# slow clients to send the request body, isolating actual queue time.
|
|
103
|
+
# See: https://github.com/puma/puma/blob/master/docs/architecture.md
|
|
104
|
+
#
|
|
105
|
+
# @param env [Hash] Rack env
|
|
106
|
+
# @return [Float, nil] queue time in milliseconds or nil
|
|
107
|
+
def extract_queue_time(env)
|
|
108
|
+
return unless Sentry.configuration&.capture_queue_time
|
|
109
|
+
|
|
110
|
+
header_value = env["HTTP_X_REQUEST_START"]
|
|
111
|
+
return unless header_value
|
|
112
|
+
|
|
113
|
+
request_start = parse_request_start_header(header_value)
|
|
114
|
+
return unless request_start
|
|
115
|
+
|
|
116
|
+
total_time_ms = ((Time.now.to_f - request_start) * 1000).round(2)
|
|
117
|
+
|
|
118
|
+
# reject negative (clock skew between proxy & app server)
|
|
119
|
+
return unless total_time_ms >= 0
|
|
120
|
+
|
|
121
|
+
puma_wait_ms = env["puma.request_body_wait"]
|
|
122
|
+
puma_wait_ms = puma_wait_ms.to_f if puma_wait_ms.is_a?(String)
|
|
123
|
+
|
|
124
|
+
if puma_wait_ms && puma_wait_ms > 0
|
|
125
|
+
queue_time_ms = total_time_ms - puma_wait_ms
|
|
126
|
+
queue_time_ms >= 0 ? queue_time_ms : 0.0 # more sanity check
|
|
127
|
+
else
|
|
128
|
+
total_time_ms
|
|
129
|
+
end
|
|
130
|
+
rescue StandardError
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Parses X-Request-Start header value to extract a timestamp.
|
|
134
|
+
# Supports multiple formats:
|
|
135
|
+
# - Nginx: "t=1234567890.123" (seconds with decimal)
|
|
136
|
+
# - Heroku, HAProxy 1.9+: "t=1234567890123456" (microseconds)
|
|
137
|
+
# - HAProxy < 1.9: "t=1234567890" (seconds)
|
|
138
|
+
# - Generic: "1234567890.123" (raw timestamp)
|
|
139
|
+
#
|
|
140
|
+
# @param header_value [String] The X-Request-Start header value
|
|
141
|
+
# @return [Float, nil] Timestamp in seconds since epoch or nil
|
|
142
|
+
def parse_request_start_header(header_value)
|
|
143
|
+
return unless header_value
|
|
144
|
+
|
|
145
|
+
# Take the first value if comma-separated (multiple headers collapsed by a proxy)
|
|
146
|
+
# and strip surrounding whitespace from each token
|
|
147
|
+
raw = header_value.split(",").first.to_s.strip
|
|
148
|
+
|
|
149
|
+
timestamp = if raw.start_with?("t=")
|
|
150
|
+
value = raw[2..-1].strip
|
|
151
|
+
return nil unless value.match?(/\A\d+(?:\.\d+)?\z/)
|
|
152
|
+
value.to_f
|
|
153
|
+
elsif raw.match?(/\A\d+(?:\.\d+)?\z/)
|
|
154
|
+
raw.to_f
|
|
155
|
+
else
|
|
156
|
+
return
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# normalize: timestamps can be in seconds, milliseconds or microseconds
|
|
160
|
+
# any timestamp > 10 trillion = microseconds
|
|
161
|
+
if timestamp > 10_000_000_000_000
|
|
162
|
+
timestamp / 1_000_000.0
|
|
163
|
+
# timestamp > 10 billion & < 10 trillion = milliseconds
|
|
164
|
+
elsif timestamp > 10_000_000_000
|
|
165
|
+
timestamp / 1_000.0
|
|
166
|
+
else
|
|
167
|
+
timestamp # assume seconds
|
|
168
|
+
end
|
|
169
|
+
rescue StandardError
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def status_code_for_exception(exception)
|
|
173
|
+
500
|
|
174
|
+
end
|
|
89
175
|
end
|
|
90
176
|
end
|
|
91
177
|
end
|
data/lib/sentry/rspec.rb
CHANGED
|
@@ -70,7 +70,7 @@ RSpec::Matchers.define :include_sentry_event do |event_message = "", **opts|
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def dump_events(sentry_events)
|
|
73
|
-
sentry_events.map(
|
|
73
|
+
sentry_events.map(&:to_h).map do |hash|
|
|
74
74
|
hash.select { |k, _| [:message, :contexts, :tags, :exception].include?(k) }
|
|
75
75
|
end.map do |hash|
|
|
76
76
|
JSON.pretty_generate(hash)
|
data/lib/sentry/scope.rb
CHANGED
|
@@ -46,7 +46,7 @@ module Sentry
|
|
|
46
46
|
# @param hint [Hash] the hint data that'll be passed to event processors.
|
|
47
47
|
# @return [Event]
|
|
48
48
|
def apply_to_event(event, hint = nil)
|
|
49
|
-
unless event.is_a?(CheckInEvent)
|
|
49
|
+
unless event.is_a?(CheckInEvent)
|
|
50
50
|
event.tags = tags.merge(event.tags)
|
|
51
51
|
event.user = user.merge(event.user)
|
|
52
52
|
event.extra = extra.merge(event.extra)
|
|
@@ -60,23 +60,10 @@ module Sentry
|
|
|
60
60
|
event.attachments = attachments
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if span
|
|
68
|
-
event.contexts[:trace] ||= span.get_trace_context
|
|
69
|
-
|
|
70
|
-
if event.respond_to?(:dynamic_sampling_context)
|
|
71
|
-
event.dynamic_sampling_context ||= span.get_dynamic_sampling_context
|
|
72
|
-
end
|
|
73
|
-
else
|
|
74
|
-
event.contexts[:trace] ||= propagation_context.get_trace_context
|
|
75
|
-
|
|
76
|
-
if event.respond_to?(:dynamic_sampling_context)
|
|
77
|
-
event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
|
|
78
|
-
end
|
|
79
|
-
end
|
|
63
|
+
trace_context = get_trace_context
|
|
64
|
+
dynamic_sampling_context = trace_context.delete(:dynamic_sampling_context)
|
|
65
|
+
event.contexts[:trace] ||= trace_context
|
|
66
|
+
event.dynamic_sampling_context ||= dynamic_sampling_context
|
|
80
67
|
|
|
81
68
|
all_event_processors = self.class.global_event_processors + @event_processors
|
|
82
69
|
|
|
@@ -89,6 +76,37 @@ module Sentry
|
|
|
89
76
|
event
|
|
90
77
|
end
|
|
91
78
|
|
|
79
|
+
# A leaner version of apply_to_event that applies to
|
|
80
|
+
# lightweight payloads like Logs and Metrics.
|
|
81
|
+
#
|
|
82
|
+
# Adds trace_id, span_id, user from the scope and default attributes from configuration.
|
|
83
|
+
#
|
|
84
|
+
# @param telemetry [MetricEvent, LogEvent] the telemetry event to apply scope context to
|
|
85
|
+
# @return [MetricEvent, LogEvent] the telemetry event with scope context applied
|
|
86
|
+
def apply_to_telemetry(telemetry)
|
|
87
|
+
# TODO-neel when new scope set_attribute api is added: add them here
|
|
88
|
+
trace_context = get_trace_context
|
|
89
|
+
telemetry.trace_id = trace_context[:trace_id]
|
|
90
|
+
telemetry.span_id = trace_context[:span_id]
|
|
91
|
+
|
|
92
|
+
configuration = Sentry.configuration
|
|
93
|
+
return telemetry unless configuration
|
|
94
|
+
|
|
95
|
+
telemetry.attributes["sentry.sdk.name"] ||= Sentry.sdk_meta["name"]
|
|
96
|
+
telemetry.attributes["sentry.sdk.version"] ||= Sentry.sdk_meta["version"]
|
|
97
|
+
telemetry.attributes["sentry.environment"] ||= configuration.environment if configuration.environment
|
|
98
|
+
telemetry.attributes["sentry.release"] ||= configuration.release if configuration.release
|
|
99
|
+
telemetry.attributes["server.address"] ||= configuration.server_name if configuration.server_name
|
|
100
|
+
|
|
101
|
+
unless user.empty?
|
|
102
|
+
telemetry.attributes["user.id"] ||= user[:id] if user[:id]
|
|
103
|
+
telemetry.attributes["user.name"] ||= user[:username] if user[:username]
|
|
104
|
+
telemetry.attributes["user.email"] ||= user[:email] if user[:email]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
telemetry
|
|
108
|
+
end
|
|
109
|
+
|
|
92
110
|
# Adds the breadcrumb to the scope's breadcrumbs buffer.
|
|
93
111
|
# @param breadcrumb [Breadcrumb]
|
|
94
112
|
# @return [void]
|
|
@@ -278,6 +296,20 @@ module Sentry
|
|
|
278
296
|
span
|
|
279
297
|
end
|
|
280
298
|
|
|
299
|
+
# Returns the trace context for this scope.
|
|
300
|
+
# Prioritizes external propagation context (from OTel) over local propagation context.
|
|
301
|
+
# @return [Hash]
|
|
302
|
+
def get_trace_context
|
|
303
|
+
if span
|
|
304
|
+
span.get_trace_context.merge(dynamic_sampling_context: span.get_dynamic_sampling_context)
|
|
305
|
+
elsif (external_context = Sentry.get_external_propagation_context)
|
|
306
|
+
trace_id, span_id = external_context
|
|
307
|
+
{ trace_id: trace_id, span_id: span_id }
|
|
308
|
+
else
|
|
309
|
+
propagation_context.get_trace_context.merge(dynamic_sampling_context: propagation_context.get_dynamic_sampling_context)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
281
313
|
# Sets the scope's fingerprint attribute.
|
|
282
314
|
# @param fingerprint [Array]
|
|
283
315
|
# @return [Array]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Sequel
|
|
5
|
+
OP_NAME = "db.sql.sequel"
|
|
6
|
+
SPAN_ORIGIN = "auto.db.sequel"
|
|
7
|
+
|
|
8
|
+
# Sequel Database extension module that instruments queries
|
|
9
|
+
module DatabaseExtension
|
|
10
|
+
def log_connection_yield(sql, conn, args = nil)
|
|
11
|
+
return super unless Sentry.initialized?
|
|
12
|
+
|
|
13
|
+
Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |span|
|
|
14
|
+
result = super
|
|
15
|
+
|
|
16
|
+
if span
|
|
17
|
+
span.set_description(sql)
|
|
18
|
+
span.set_data(Span::DataConventions::DB_SYSTEM, database_type.to_s)
|
|
19
|
+
span.set_data(Span::DataConventions::DB_NAME, opts[:database]) if opts[:database]
|
|
20
|
+
span.set_data(Span::DataConventions::SERVER_ADDRESS, opts[:host]) if opts[:host]
|
|
21
|
+
span.set_data(Span::DataConventions::SERVER_PORT, opts[:port]) if opts[:port]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
result
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
::Sequel::Database.register_extension(:sentry, Sentry::Sequel::DatabaseExtension)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Sentry.register_patch(:sequel) do
|
|
34
|
+
::Sequel::Database.extension(:sentry) if defined?(::Sequel::Database)
|
|
35
|
+
end
|
data/lib/sentry/span.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "securerandom"
|
|
4
|
-
require "sentry/metrics/local_aggregator"
|
|
5
4
|
require "sentry/utils/uuid"
|
|
6
5
|
|
|
7
6
|
module Sentry
|
|
@@ -50,6 +49,9 @@ module Sentry
|
|
|
50
49
|
MESSAGING_DESTINATION_NAME = "messaging.destination.name"
|
|
51
50
|
MESSAGING_MESSAGE_RECEIVE_LATENCY = "messaging.message.receive.latency"
|
|
52
51
|
MESSAGING_MESSAGE_RETRY_COUNT = "messaging.message.retry.count"
|
|
52
|
+
|
|
53
|
+
# Time in ms the request spent in the server queue before processing began.
|
|
54
|
+
HTTP_QUEUE_TIME_MS = "http.server.request.time_in_queue"
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
STATUS_MAP = {
|
|
@@ -173,8 +175,8 @@ module Sentry
|
|
|
173
175
|
end
|
|
174
176
|
|
|
175
177
|
# @return [Hash]
|
|
176
|
-
def
|
|
177
|
-
|
|
178
|
+
def to_h
|
|
179
|
+
{
|
|
178
180
|
trace_id: @trace_id,
|
|
179
181
|
span_id: @span_id,
|
|
180
182
|
parent_span_id: @parent_span_id,
|
|
@@ -187,11 +189,6 @@ module Sentry
|
|
|
187
189
|
data: @data,
|
|
188
190
|
origin: @origin
|
|
189
191
|
}
|
|
190
|
-
|
|
191
|
-
summary = metrics_summary
|
|
192
|
-
hash[:_metrics_summary] = summary if summary
|
|
193
|
-
|
|
194
|
-
hash
|
|
195
192
|
end
|
|
196
193
|
|
|
197
194
|
# Returns the span's context that can be used to embed in an Event.
|
|
@@ -307,14 +304,5 @@ module Sentry
|
|
|
307
304
|
def set_origin(origin)
|
|
308
305
|
@origin = origin
|
|
309
306
|
end
|
|
310
|
-
|
|
311
|
-
# Collects gauge metrics on the span for metric summaries.
|
|
312
|
-
def metrics_local_aggregator
|
|
313
|
-
@metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
def metrics_summary
|
|
317
|
-
@metrics_local_aggregator&.to_hash
|
|
318
|
-
end
|
|
319
307
|
end
|
|
320
308
|
end
|
|
@@ -12,11 +12,16 @@ module Sentry
|
|
|
12
12
|
4 => :fatal
|
|
13
13
|
}.freeze
|
|
14
14
|
|
|
15
|
+
ORIGIN = "auto.log.ruby.std_logger"
|
|
16
|
+
|
|
15
17
|
def add(severity, message = nil, progname = nil, &block)
|
|
16
18
|
result = super
|
|
17
19
|
|
|
18
20
|
return unless Sentry.initialized? && Sentry.get_current_hub
|
|
19
21
|
|
|
22
|
+
# Only process logs that meet or exceed the logger's level
|
|
23
|
+
return result if severity < level
|
|
24
|
+
|
|
20
25
|
# exclude sentry SDK logs -- to prevent recursive log action,
|
|
21
26
|
# do not process internal logs again
|
|
22
27
|
if message.nil? && progname != Sentry::Logger::PROGNAME
|
|
@@ -32,7 +37,11 @@ module Sentry
|
|
|
32
37
|
message = message.to_s.strip
|
|
33
38
|
|
|
34
39
|
if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
|
|
35
|
-
Sentry.
|
|
40
|
+
if (filter = Sentry.configuration.std_lib_logger_filter) && !filter.call(self, message, method)
|
|
41
|
+
return result
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Sentry.logger.send(method, message, origin: ORIGIN)
|
|
36
45
|
end
|
|
37
46
|
end
|
|
38
47
|
|