sentry-ruby 5.28.1 → 6.3.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/Gemfile +25 -1
- data/README.md +2 -2
- 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/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 +57 -135
- data/lib/sentry/configuration.rb +117 -75
- 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/envelope/item.rb +3 -3
- data/lib/sentry/error_event.rb +3 -3
- data/lib/sentry/event.rb +4 -10
- data/lib/sentry/hub.rb +26 -4
- 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 +24 -142
- 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 -54
- data/lib/sentry/profiler.rb +4 -5
- data/lib/sentry/rack/capture_exceptions.rb +5 -1
- 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 +2 -17
- data/lib/sentry/std_lib_logger.rb +4 -0
- data/lib/sentry/telemetry_event_buffer.rb +130 -0
- data/lib/sentry/test_helper.rb +8 -0
- data/lib/sentry/transaction.rb +52 -103
- data/lib/sentry/transaction_event.rb +4 -9
- data/lib/sentry/transport.rb +2 -5
- data/lib/sentry/utils/encoding_helper.rb +6 -0
- data/lib/sentry/utils/logging_helper.rb +25 -9
- 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 +53 -30
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +2 -1
- metadata +27 -16
- data/lib/sentry/metrics/aggregator.rb +0 -248
- data/lib/sentry/metrics/configuration.rb +0 -57
- 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/log_event.rb
CHANGED
|
@@ -1,134 +1,52 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "sentry/utils/telemetry_attributes"
|
|
4
|
+
|
|
3
5
|
module Sentry
|
|
4
6
|
# Event type that represents a log entry with its attributes
|
|
5
7
|
#
|
|
6
8
|
# @see https://develop.sentry.dev/sdk/telemetry/logs/#log-envelope-item-payload
|
|
7
9
|
class LogEvent
|
|
10
|
+
include Sentry::Utils::TelemetryAttributes
|
|
11
|
+
|
|
8
12
|
TYPE = "log"
|
|
9
13
|
|
|
10
14
|
DEFAULT_PARAMETERS = [].freeze
|
|
11
|
-
DEFAULT_ATTRIBUTES = {}.freeze
|
|
12
|
-
|
|
13
|
-
SERIALIZEABLE_ATTRIBUTES = %i[
|
|
14
|
-
level
|
|
15
|
-
body
|
|
16
|
-
timestamp
|
|
17
|
-
environment
|
|
18
|
-
release
|
|
19
|
-
server_name
|
|
20
|
-
trace_id
|
|
21
|
-
attributes
|
|
22
|
-
contexts
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
SENTRY_ATTRIBUTES = {
|
|
26
|
-
"sentry.trace.parent_span_id" => :parent_span_id,
|
|
27
|
-
"sentry.environment" => :environment,
|
|
28
|
-
"sentry.release" => :release,
|
|
29
|
-
"sentry.address" => :server_name,
|
|
30
|
-
"sentry.sdk.name" => :sdk_name,
|
|
31
|
-
"sentry.sdk.version" => :sdk_version,
|
|
32
|
-
"sentry.message.template" => :template,
|
|
33
|
-
"sentry.origin" => :origin
|
|
34
|
-
}
|
|
35
15
|
|
|
36
16
|
PARAMETER_PREFIX = "sentry.message.parameter"
|
|
37
17
|
|
|
38
|
-
USER_ATTRIBUTES = {
|
|
39
|
-
"user.id" => :user_id,
|
|
40
|
-
"user.name" => :user_username,
|
|
41
|
-
"user.email" => :user_email
|
|
42
|
-
}
|
|
43
|
-
|
|
44
18
|
LEVELS = %i[trace debug info warn error fatal].freeze
|
|
45
19
|
|
|
46
|
-
attr_accessor :level, :body, :template, :attributes, :
|
|
47
|
-
|
|
48
|
-
attr_reader :configuration, *(SERIALIZEABLE_ATTRIBUTES - %i[level body attributes])
|
|
49
|
-
|
|
50
|
-
SERIALIZERS = %i[
|
|
51
|
-
attributes
|
|
52
|
-
body
|
|
53
|
-
level
|
|
54
|
-
parent_span_id
|
|
55
|
-
sdk_name
|
|
56
|
-
sdk_version
|
|
57
|
-
template
|
|
58
|
-
timestamp
|
|
59
|
-
trace_id
|
|
60
|
-
user_id
|
|
61
|
-
user_username
|
|
62
|
-
user_email
|
|
63
|
-
].map { |name| [name, :"serialize_#{name}"] }.to_h
|
|
64
|
-
|
|
65
|
-
VALUE_TYPES = Hash.new("string").merge!({
|
|
66
|
-
TrueClass => "boolean",
|
|
67
|
-
FalseClass => "boolean",
|
|
68
|
-
Integer => "integer",
|
|
69
|
-
Float => "double"
|
|
70
|
-
}).freeze
|
|
20
|
+
attr_accessor :level, :body, :template, :attributes, :origin, :trace_id, :span_id
|
|
21
|
+
attr_reader :timestamp
|
|
71
22
|
|
|
72
23
|
TOKEN_REGEXP = /%\{(\w+)\}/
|
|
73
24
|
|
|
74
|
-
def initialize(
|
|
75
|
-
@configuration = configuration
|
|
25
|
+
def initialize(**options)
|
|
76
26
|
@type = TYPE
|
|
77
|
-
@server_name = configuration.server_name
|
|
78
|
-
@environment = configuration.environment
|
|
79
|
-
@release = configuration.release
|
|
80
27
|
@timestamp = Sentry.utc_now
|
|
81
28
|
@level = options.fetch(:level)
|
|
82
29
|
@body = options[:body]
|
|
83
30
|
@template = @body if is_template?
|
|
84
|
-
@attributes = options[:attributes] ||
|
|
85
|
-
@user = options[:user] || {}
|
|
31
|
+
@attributes = options[:attributes] || {}
|
|
86
32
|
@origin = options[:origin]
|
|
87
|
-
@
|
|
33
|
+
@trace_id = nil
|
|
34
|
+
@span_id = nil
|
|
88
35
|
end
|
|
89
36
|
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
37
|
+
def to_h
|
|
38
|
+
{
|
|
39
|
+
level: level.to_s,
|
|
40
|
+
timestamp: timestamp.to_f,
|
|
41
|
+
trace_id: @trace_id,
|
|
42
|
+
span_id: @span_id,
|
|
43
|
+
body: serialize_body,
|
|
44
|
+
attributes: serialize_attributes
|
|
45
|
+
}.compact
|
|
94
46
|
end
|
|
95
47
|
|
|
96
48
|
private
|
|
97
49
|
|
|
98
|
-
def serialize(name)
|
|
99
|
-
serializer = SERIALIZERS[name]
|
|
100
|
-
|
|
101
|
-
if serializer
|
|
102
|
-
__send__(serializer)
|
|
103
|
-
else
|
|
104
|
-
public_send(name)
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def serialize_level
|
|
109
|
-
level.to_s
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def serialize_sdk_name
|
|
113
|
-
Sentry.sdk_meta["name"]
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def serialize_sdk_version
|
|
117
|
-
Sentry.sdk_meta["version"]
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def serialize_timestamp
|
|
121
|
-
timestamp.to_f
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def serialize_trace_id
|
|
125
|
-
contexts.dig(:trace, :trace_id)
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def serialize_parent_span_id
|
|
129
|
-
contexts.dig(:trace, :parent_span_id)
|
|
130
|
-
end
|
|
131
|
-
|
|
132
50
|
def serialize_body
|
|
133
51
|
if parameters.empty?
|
|
134
52
|
body
|
|
@@ -139,50 +57,14 @@ module Sentry
|
|
|
139
57
|
end
|
|
140
58
|
end
|
|
141
59
|
|
|
142
|
-
def serialize_user_id
|
|
143
|
-
user[:id]
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def serialize_user_username
|
|
147
|
-
user[:username]
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def serialize_user_email
|
|
151
|
-
user[:email]
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def serialize_template
|
|
155
|
-
template if has_parameters?
|
|
156
|
-
end
|
|
157
|
-
|
|
158
60
|
def serialize_attributes
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
attributes.each do |key, value|
|
|
162
|
-
hash[key] = attribute_hash(value)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
SENTRY_ATTRIBUTES.each do |key, name|
|
|
166
|
-
if (value = serialize(name))
|
|
167
|
-
hash[key] = attribute_hash(value)
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
USER_ATTRIBUTES.each do |key, name|
|
|
172
|
-
if (value = serialize(name))
|
|
173
|
-
hash[key] = value
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
hash
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def attribute_hash(value)
|
|
181
|
-
{ value: value, type: value_type(value) }
|
|
61
|
+
populate_sentry_attributes!
|
|
62
|
+
@attributes.transform_values! { |v| attribute_hash(v) }
|
|
182
63
|
end
|
|
183
64
|
|
|
184
|
-
def
|
|
185
|
-
|
|
65
|
+
def populate_sentry_attributes!
|
|
66
|
+
@attributes["sentry.origin"] ||= @origin if @origin
|
|
67
|
+
@attributes["sentry.message.template"] ||= template if has_parameters?
|
|
186
68
|
end
|
|
187
69
|
|
|
188
70
|
def parameters
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "sentry/
|
|
3
|
+
require "sentry/telemetry_event_buffer"
|
|
4
4
|
|
|
5
5
|
module Sentry
|
|
6
6
|
# LogEventBuffer buffers log events and sends them to Sentry in a single envelope.
|
|
@@ -8,68 +8,21 @@ module Sentry
|
|
|
8
8
|
# This is used internally by the `Sentry::Client`.
|
|
9
9
|
#
|
|
10
10
|
# @!visibility private
|
|
11
|
-
class LogEventBuffer <
|
|
12
|
-
FLUSH_INTERVAL = 5 # seconds
|
|
11
|
+
class LogEventBuffer < TelemetryEventBuffer
|
|
13
12
|
DEFAULT_MAX_EVENTS = 100
|
|
14
|
-
|
|
15
|
-
# @!visibility private
|
|
16
|
-
attr_reader :pending_events
|
|
13
|
+
MAX_EVENTS_BEFORE_DROP = 1000
|
|
17
14
|
|
|
18
15
|
def initialize(configuration, client)
|
|
19
|
-
super(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def start
|
|
30
|
-
ensure_thread
|
|
31
|
-
self
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def flush
|
|
35
|
-
@mutex.synchronize do
|
|
36
|
-
return if empty?
|
|
37
|
-
|
|
38
|
-
log_debug("[LogEventBuffer] flushing #{size} log events")
|
|
39
|
-
|
|
40
|
-
send_events
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
log_debug("[LogEventBuffer] flushed #{size} log events")
|
|
44
|
-
|
|
45
|
-
self
|
|
46
|
-
end
|
|
47
|
-
alias_method :run, :flush
|
|
48
|
-
|
|
49
|
-
def add_event(event)
|
|
50
|
-
raise ArgumentError, "expected a LogEvent, got #{event.class}" unless event.is_a?(LogEvent)
|
|
51
|
-
|
|
52
|
-
@mutex.synchronize do
|
|
53
|
-
@pending_events << event
|
|
54
|
-
send_events if size >= @max_events
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
self
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def empty?
|
|
61
|
-
@pending_events.empty?
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def size
|
|
65
|
-
@pending_events.size
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
private
|
|
69
|
-
|
|
70
|
-
def send_events
|
|
71
|
-
@client.send_logs(@pending_events)
|
|
72
|
-
@pending_events.clear
|
|
16
|
+
super(
|
|
17
|
+
configuration,
|
|
18
|
+
client,
|
|
19
|
+
event_class: LogEvent,
|
|
20
|
+
max_items: configuration.max_log_events || DEFAULT_MAX_EVENTS,
|
|
21
|
+
max_items_before_drop: MAX_EVENTS_BEFORE_DROP,
|
|
22
|
+
envelope_type: "log",
|
|
23
|
+
envelope_content_type: "application/vnd.sentry.items.log+json",
|
|
24
|
+
before_send: configuration.before_send_log
|
|
25
|
+
)
|
|
73
26
|
end
|
|
74
27
|
end
|
|
75
28
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sentry/utils/telemetry_attributes"
|
|
4
|
+
|
|
5
|
+
module Sentry
|
|
6
|
+
class MetricEvent
|
|
7
|
+
include Sentry::Utils::TelemetryAttributes
|
|
8
|
+
|
|
9
|
+
attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes
|
|
10
|
+
attr_writer :trace_id, :span_id, :attributes
|
|
11
|
+
|
|
12
|
+
def initialize(
|
|
13
|
+
name:,
|
|
14
|
+
type:,
|
|
15
|
+
value:,
|
|
16
|
+
unit: nil,
|
|
17
|
+
attributes: nil
|
|
18
|
+
)
|
|
19
|
+
@name = name
|
|
20
|
+
@type = type
|
|
21
|
+
@value = value
|
|
22
|
+
@unit = unit
|
|
23
|
+
@attributes = attributes || {}
|
|
24
|
+
|
|
25
|
+
@timestamp = Sentry.utc_now
|
|
26
|
+
@trace_id = nil
|
|
27
|
+
@span_id = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_h
|
|
31
|
+
{
|
|
32
|
+
name: @name,
|
|
33
|
+
type: @type,
|
|
34
|
+
value: @value,
|
|
35
|
+
unit: @unit,
|
|
36
|
+
timestamp: @timestamp,
|
|
37
|
+
trace_id: @trace_id,
|
|
38
|
+
span_id: @span_id,
|
|
39
|
+
attributes: serialize_attributes
|
|
40
|
+
}.compact
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def serialize_attributes
|
|
46
|
+
@attributes.transform_values! { |v| attribute_hash(v) }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sentry/telemetry_event_buffer"
|
|
4
|
+
|
|
5
|
+
module Sentry
|
|
6
|
+
# MetricEventBuffer buffers metric events and sends them to Sentry in a single envelope.
|
|
7
|
+
#
|
|
8
|
+
# This is used internally by the `Sentry::Client`.
|
|
9
|
+
#
|
|
10
|
+
# @!visibility private
|
|
11
|
+
class MetricEventBuffer < TelemetryEventBuffer
|
|
12
|
+
DEFAULT_MAX_METRICS = 1000
|
|
13
|
+
MAX_METRICS_BEFORE_DROP = 10_000
|
|
14
|
+
|
|
15
|
+
def initialize(configuration, client)
|
|
16
|
+
super(
|
|
17
|
+
configuration,
|
|
18
|
+
client,
|
|
19
|
+
event_class: MetricEvent,
|
|
20
|
+
max_items: configuration.max_metric_events || DEFAULT_MAX_METRICS,
|
|
21
|
+
max_items_before_drop: MAX_METRICS_BEFORE_DROP,
|
|
22
|
+
envelope_type: "trace_metric",
|
|
23
|
+
envelope_content_type: "application/vnd.sentry.items.trace-metric+json",
|
|
24
|
+
before_send: configuration.before_send_metric
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/sentry/metrics.rb
CHANGED
|
@@ -1,67 +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
|
-
def
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
)
|
|
29
22
|
end
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Sentry.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
|
|
48
|
-
tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
|
|
49
|
-
|
|
50
|
-
start = Timing.send(unit.to_sym)
|
|
51
|
-
result = yield
|
|
52
|
-
value = Timing.send(unit.to_sym) - start
|
|
53
|
-
|
|
54
|
-
[result, value]
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
|
58
|
-
result
|
|
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
|
+
)
|
|
59
40
|
end
|
|
60
41
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
)
|
|
65
58
|
end
|
|
66
59
|
end
|
|
67
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
|
|
|
@@ -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
|
|
|
@@ -86,6 +86,10 @@ module Sentry
|
|
|
86
86
|
def mechanism
|
|
87
87
|
Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
|
|
88
88
|
end
|
|
89
|
+
|
|
90
|
+
def status_code_for_exception(exception)
|
|
91
|
+
500
|
|
92
|
+
end
|
|
89
93
|
end
|
|
90
94
|
end
|
|
91
95
|
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
|
+
if configuration.send_default_pii && !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
|