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,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
# Event type that represents a log entry with its attributes
|
|
5
|
+
#
|
|
6
|
+
# @see https://develop.sentry.dev/sdk/telemetry/logs/#log-envelope-item-payload
|
|
7
|
+
class LogEvent
|
|
8
|
+
TYPE = "log"
|
|
9
|
+
|
|
10
|
+
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
|
+
|
|
36
|
+
PARAMETER_PREFIX = "sentry.message.parameter"
|
|
37
|
+
|
|
38
|
+
USER_ATTRIBUTES = {
|
|
39
|
+
"user.id" => :user_id,
|
|
40
|
+
"user.name" => :user_username,
|
|
41
|
+
"user.email" => :user_email
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
LEVELS = %i[trace debug info warn error fatal].freeze
|
|
45
|
+
|
|
46
|
+
attr_accessor :level, :body, :template, :attributes, :user, :origin
|
|
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
|
|
71
|
+
|
|
72
|
+
TOKEN_REGEXP = /%\{(\w+)\}/
|
|
73
|
+
|
|
74
|
+
def initialize(configuration: Sentry.configuration, **options)
|
|
75
|
+
@configuration = configuration
|
|
76
|
+
@type = TYPE
|
|
77
|
+
@server_name = configuration.server_name
|
|
78
|
+
@environment = configuration.environment
|
|
79
|
+
@release = configuration.release
|
|
80
|
+
@timestamp = Sentry.utc_now
|
|
81
|
+
@level = options.fetch(:level)
|
|
82
|
+
@body = options[:body]
|
|
83
|
+
@template = @body if is_template?
|
|
84
|
+
@attributes = options[:attributes] || DEFAULT_ATTRIBUTES
|
|
85
|
+
@user = options[:user] || {}
|
|
86
|
+
@origin = options[:origin]
|
|
87
|
+
@contexts = {}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def to_hash
|
|
91
|
+
SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |name, memo|
|
|
92
|
+
memo[name] = serialize(name)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
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
|
+
def serialize_body
|
|
133
|
+
if parameters.empty?
|
|
134
|
+
body
|
|
135
|
+
elsif parameters.is_a?(Hash)
|
|
136
|
+
body % parameters
|
|
137
|
+
else
|
|
138
|
+
sprintf(body, *parameters)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
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
|
+
def serialize_attributes
|
|
159
|
+
hash = {}
|
|
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) }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def value_type(value)
|
|
185
|
+
VALUE_TYPES[value.class]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def parameters
|
|
189
|
+
@parameters ||= begin
|
|
190
|
+
return DEFAULT_PARAMETERS unless template
|
|
191
|
+
|
|
192
|
+
parameters = template_tokens.empty? ?
|
|
193
|
+
attributes.fetch(:parameters, DEFAULT_PARAMETERS) : attributes.slice(*template_tokens)
|
|
194
|
+
|
|
195
|
+
if parameters.is_a?(Hash)
|
|
196
|
+
parameters.each do |key, value|
|
|
197
|
+
attributes["#{PARAMETER_PREFIX}.#{key}"] = value
|
|
198
|
+
end
|
|
199
|
+
else
|
|
200
|
+
parameters.each_with_index do |param, index|
|
|
201
|
+
attributes["#{PARAMETER_PREFIX}.#{index}"] = param
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def template_tokens
|
|
208
|
+
@template_tokens ||= body.scan(TOKEN_REGEXP).flatten.map(&:to_sym)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def is_template?
|
|
212
|
+
body.include?("%s") || TOKEN_REGEXP.match?(body)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def has_parameters?
|
|
216
|
+
attributes.keys.any? { |key| key.start_with?(PARAMETER_PREFIX) }
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sentry/threaded_periodic_worker"
|
|
4
|
+
|
|
5
|
+
module Sentry
|
|
6
|
+
# LogEventBuffer buffers log 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 LogEventBuffer < ThreadedPeriodicWorker
|
|
12
|
+
FLUSH_INTERVAL = 5 # seconds
|
|
13
|
+
DEFAULT_MAX_EVENTS = 100
|
|
14
|
+
|
|
15
|
+
# @!visibility private
|
|
16
|
+
attr_reader :pending_events
|
|
17
|
+
|
|
18
|
+
def initialize(configuration, client)
|
|
19
|
+
super(configuration.sdk_logger, FLUSH_INTERVAL)
|
|
20
|
+
|
|
21
|
+
@client = client
|
|
22
|
+
@pending_events = []
|
|
23
|
+
@max_events = configuration.max_log_events || DEFAULT_MAX_EVENTS
|
|
24
|
+
@mutex = Mutex.new
|
|
25
|
+
|
|
26
|
+
log_debug("[Logging] Initialized buffer with max_events=#{@max_events}, flush_interval=#{FLUSH_INTERVAL}s")
|
|
27
|
+
end
|
|
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
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/sentry/logger.rb
CHANGED
|
@@ -34,15 +34,15 @@ module Sentry
|
|
|
34
34
|
attr_reader :client, :thread, :buckets, :flush_shift, :code_locations
|
|
35
35
|
|
|
36
36
|
def initialize(configuration, client)
|
|
37
|
-
super(configuration.
|
|
37
|
+
super(configuration.sdk_logger, FLUSH_INTERVAL)
|
|
38
38
|
@client = client
|
|
39
39
|
@before_emit = configuration.metrics.before_emit
|
|
40
40
|
@enable_code_locations = configuration.metrics.enable_code_locations
|
|
41
41
|
@stacktrace_builder = configuration.stacktrace_builder
|
|
42
42
|
|
|
43
43
|
@default_tags = {}
|
|
44
|
-
@default_tags[
|
|
45
|
-
@default_tags[
|
|
44
|
+
@default_tags["release"] = configuration.release if configuration.release
|
|
45
|
+
@default_tags["environment"] = configuration.environment if configuration.environment
|
|
46
46
|
|
|
47
47
|
@mutex = Mutex.new
|
|
48
48
|
|
|
@@ -59,7 +59,7 @@ module Sentry
|
|
|
59
59
|
def add(type,
|
|
60
60
|
key,
|
|
61
61
|
value,
|
|
62
|
-
unit:
|
|
62
|
+
unit: "none",
|
|
63
63
|
tags: {},
|
|
64
64
|
timestamp: nil,
|
|
65
65
|
stacklevel: nil)
|
|
@@ -98,7 +98,7 @@ module Sentry
|
|
|
98
98
|
unless flushable_buckets.empty?
|
|
99
99
|
payload = serialize_buckets(flushable_buckets)
|
|
100
100
|
envelope.add_item(
|
|
101
|
-
{ type:
|
|
101
|
+
{ type: "statsd", length: payload.bytesize },
|
|
102
102
|
payload
|
|
103
103
|
)
|
|
104
104
|
end
|
|
@@ -107,7 +107,7 @@ module Sentry
|
|
|
107
107
|
code_locations.each do |timestamp, locations|
|
|
108
108
|
payload = serialize_locations(timestamp, locations)
|
|
109
109
|
envelope.add_item(
|
|
110
|
-
{ type:
|
|
110
|
+
{ type: "metric_meta", content_type: "application/json" },
|
|
111
111
|
payload
|
|
112
112
|
)
|
|
113
113
|
end
|
|
@@ -161,8 +161,8 @@ module Sentry
|
|
|
161
161
|
buckets.map do |timestamp, timestamp_buckets|
|
|
162
162
|
timestamp_buckets.map do |metric_key, metric|
|
|
163
163
|
type, key, unit, tags = metric_key
|
|
164
|
-
values = metric.serialize.join(
|
|
165
|
-
sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(
|
|
164
|
+
values = metric.serialize.join(":")
|
|
165
|
+
sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(",")
|
|
166
166
|
|
|
167
167
|
"#{sanitize_key(key)}@#{sanitize_unit(unit)}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
|
|
168
168
|
end
|
|
@@ -175,22 +175,22 @@ module Sentry
|
|
|
175
175
|
mri = "#{type}:#{sanitize_key(key)}@#{sanitize_unit(unit)}"
|
|
176
176
|
|
|
177
177
|
# note this needs to be an array but it really doesn't serve a purpose right now
|
|
178
|
-
[mri, [location.merge(type:
|
|
178
|
+
[mri, [location.merge(type: "location")]]
|
|
179
179
|
end.to_h
|
|
180
180
|
|
|
181
181
|
{ timestamp: timestamp, mapping: mapping }
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
def sanitize_key(key)
|
|
185
|
-
key.gsub(KEY_SANITIZATION_REGEX,
|
|
185
|
+
key.gsub(KEY_SANITIZATION_REGEX, "_")
|
|
186
186
|
end
|
|
187
187
|
|
|
188
188
|
def sanitize_unit(unit)
|
|
189
|
-
unit.gsub(UNIT_SANITIZATION_REGEX,
|
|
189
|
+
unit.gsub(UNIT_SANITIZATION_REGEX, "")
|
|
190
190
|
end
|
|
191
191
|
|
|
192
192
|
def sanitize_tag_key(key)
|
|
193
|
-
key.gsub(TAG_KEY_SANITIZATION_REGEX,
|
|
193
|
+
key.gsub(TAG_KEY_SANITIZATION_REGEX, "")
|
|
194
194
|
end
|
|
195
195
|
|
|
196
196
|
def sanitize_tag_value(value)
|
|
@@ -209,7 +209,7 @@ module Sentry
|
|
|
209
209
|
updated_tags = @default_tags.merge(tags)
|
|
210
210
|
|
|
211
211
|
transaction_name = get_transaction_name
|
|
212
|
-
updated_tags[
|
|
212
|
+
updated_tags["transaction"] = transaction_name if transaction_name
|
|
213
213
|
|
|
214
214
|
updated_tags
|
|
215
215
|
end
|
|
@@ -4,12 +4,13 @@ module Sentry
|
|
|
4
4
|
module Metrics
|
|
5
5
|
class Configuration
|
|
6
6
|
include ArgumentCheckingHelper
|
|
7
|
+
include LoggingHelper
|
|
7
8
|
|
|
8
9
|
# Enable metrics usage.
|
|
9
10
|
# Starts a new {Sentry::Metrics::Aggregator} instance to aggregate metrics
|
|
10
11
|
# and a thread to aggregate flush every 5 seconds.
|
|
11
12
|
# @return [Boolean]
|
|
12
|
-
|
|
13
|
+
attr_reader :enabled
|
|
13
14
|
|
|
14
15
|
# Enable code location reporting.
|
|
15
16
|
# Will be sent once per day.
|
|
@@ -32,11 +33,20 @@ module Sentry
|
|
|
32
33
|
# @return [Proc, nil]
|
|
33
34
|
attr_reader :before_emit
|
|
34
35
|
|
|
35
|
-
def initialize
|
|
36
|
+
def initialize(sdk_logger)
|
|
37
|
+
@sdk_logger = sdk_logger
|
|
36
38
|
@enabled = false
|
|
37
39
|
@enable_code_locations = true
|
|
38
40
|
end
|
|
39
41
|
|
|
42
|
+
def enabled=(value)
|
|
43
|
+
log_warn <<~MSG
|
|
44
|
+
`config.metrics` is now deprecated and will be removed in the next major.
|
|
45
|
+
MSG
|
|
46
|
+
|
|
47
|
+
@enabled = value
|
|
48
|
+
end
|
|
49
|
+
|
|
40
50
|
def before_emit=(value)
|
|
41
51
|
check_callable!("metrics.before_emit", value)
|
|
42
52
|
|
|
@@ -37,6 +37,14 @@ module Sentry
|
|
|
37
37
|
def week
|
|
38
38
|
Sentry.utc_now.to_i / (3600.0 * 24.0 * 7.0)
|
|
39
39
|
end
|
|
40
|
+
|
|
41
|
+
def duration_start
|
|
42
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def duration_end(start)
|
|
46
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
47
|
+
end
|
|
40
48
|
end
|
|
41
49
|
end
|
|
42
50
|
end
|
data/lib/sentry/metrics.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
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
10
|
|
|
11
11
|
module Sentry
|
|
12
12
|
module Metrics
|
|
@@ -14,32 +14,38 @@ module Sentry
|
|
|
14
14
|
INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
|
|
15
15
|
FRACTIONAL_UNITS = %w[ratio percent]
|
|
16
16
|
|
|
17
|
-
OP_NAME =
|
|
18
|
-
SPAN_ORIGIN =
|
|
17
|
+
OP_NAME = "metric.timing"
|
|
18
|
+
SPAN_ORIGIN = "auto.metric.timing"
|
|
19
19
|
|
|
20
20
|
class << self
|
|
21
|
-
def increment(key, value = 1.0, unit:
|
|
21
|
+
def increment(key, value = 1.0, unit: "none", tags: {}, timestamp: nil)
|
|
22
|
+
log_deprecation
|
|
22
23
|
Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
|
23
24
|
end
|
|
24
25
|
|
|
25
|
-
def distribution(key, value, unit:
|
|
26
|
+
def distribution(key, value, unit: "none", tags: {}, timestamp: nil)
|
|
27
|
+
log_deprecation
|
|
26
28
|
Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
def set(key, value, unit:
|
|
31
|
+
def set(key, value, unit: "none", tags: {}, timestamp: nil)
|
|
32
|
+
log_deprecation
|
|
30
33
|
Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
|
31
34
|
end
|
|
32
35
|
|
|
33
|
-
def gauge(key, value, unit:
|
|
36
|
+
def gauge(key, value, unit: "none", tags: {}, timestamp: nil)
|
|
37
|
+
log_deprecation
|
|
34
38
|
Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
def timing(key, unit:
|
|
41
|
+
def timing(key, unit: "second", tags: {}, timestamp: nil, &block)
|
|
42
|
+
log_deprecation
|
|
43
|
+
|
|
38
44
|
return unless block_given?
|
|
39
45
|
return yield unless DURATION_UNITS.include?(unit)
|
|
40
46
|
|
|
41
47
|
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(
|
|
48
|
+
tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
|
|
43
49
|
|
|
44
50
|
start = Timing.send(unit.to_sym)
|
|
45
51
|
result = yield
|
|
@@ -51,6 +57,12 @@ module Sentry
|
|
|
51
57
|
Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
|
52
58
|
result
|
|
53
59
|
end
|
|
60
|
+
|
|
61
|
+
def log_deprecation
|
|
62
|
+
Sentry.sdk_logger.warn(LOGGER_PROGNAME) do
|
|
63
|
+
"`Sentry::Metrics` is now deprecated and will be removed in the next major."
|
|
64
|
+
end
|
|
65
|
+
end
|
|
54
66
|
end
|
|
55
67
|
end
|
|
56
68
|
end
|
data/lib/sentry/net/http.rb
CHANGED
|
@@ -2,14 +2,18 @@
|
|
|
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"
|
|
11
14
|
SPAN_ORIGIN = "auto.http.net_http"
|
|
12
15
|
BREADCRUMB_CATEGORY = "net.http"
|
|
16
|
+
URI_PARSER = URI.const_defined?("RFC2396_PARSER") ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
|
|
13
17
|
|
|
14
18
|
# To explain how the entire thing works, we need to know how the original Net::HTTP#request works
|
|
15
19
|
# Here's part of its definition. As you can see, it usually calls itself inside a #start block
|
|
@@ -21,8 +25,7 @@ module Sentry
|
|
|
21
25
|
# req['connection'] ||= 'close'
|
|
22
26
|
# return request(req, body, &block) # <- request will be called for the second time from the first call
|
|
23
27
|
# }
|
|
24
|
-
# end
|
|
25
|
-
# # .....
|
|
28
|
+
# end # .....
|
|
26
29
|
# end
|
|
27
30
|
# ```
|
|
28
31
|
#
|
|
@@ -34,44 +37,26 @@ module Sentry
|
|
|
34
37
|
Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
|
|
35
38
|
request_info = extract_request_info(req)
|
|
36
39
|
|
|
37
|
-
if propagate_trace?(request_info[:url]
|
|
40
|
+
if propagate_trace?(request_info[:url])
|
|
38
41
|
set_propagation_headers(req)
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
res = super
|
|
45
|
+
response_status = res.code.to_i
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
|
|
47
|
-
sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
|
|
48
|
-
sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
|
|
49
|
-
sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, res.code.to_i)
|
|
50
|
-
end
|
|
47
|
+
if record_sentry_breadcrumb?
|
|
48
|
+
record_sentry_breadcrumb(request_info, response_status)
|
|
51
49
|
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
50
|
|
|
55
|
-
|
|
51
|
+
if sentry_span
|
|
52
|
+
set_span_info(sentry_span, request_info, response_status)
|
|
53
|
+
end
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
res
|
|
56
|
+
end
|
|
59
57
|
end
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
|
|
63
|
-
|
|
64
|
-
crumb = Sentry::Breadcrumb.new(
|
|
65
|
-
level: :info,
|
|
66
|
-
category: BREADCRUMB_CATEGORY,
|
|
67
|
-
type: :info,
|
|
68
|
-
data: {
|
|
69
|
-
status: res.code.to_i,
|
|
70
|
-
**request_info
|
|
71
|
-
}
|
|
72
|
-
)
|
|
73
|
-
Sentry.add_breadcrumb(crumb)
|
|
74
|
-
end
|
|
59
|
+
private
|
|
75
60
|
|
|
76
61
|
def from_sentry_sdk?
|
|
77
62
|
dsn = Sentry.configuration.dsn
|
|
@@ -82,7 +67,7 @@ module Sentry
|
|
|
82
67
|
# IPv6 url could look like '::1/path', and that won't parse without
|
|
83
68
|
# wrapping it in square brackets.
|
|
84
69
|
hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
|
|
85
|
-
uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}")
|
|
70
|
+
uri = req.uri || URI.parse(URI_PARSER.escape("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}"))
|
|
86
71
|
url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
|
|
87
72
|
|
|
88
73
|
result = { method: req.method, url: url }
|
|
@@ -94,12 +79,6 @@ module Sentry
|
|
|
94
79
|
|
|
95
80
|
result
|
|
96
81
|
end
|
|
97
|
-
|
|
98
|
-
def propagate_trace?(url, configuration)
|
|
99
|
-
url &&
|
|
100
|
-
configuration.propagate_traces &&
|
|
101
|
-
configuration.trace_propagation_targets.any? { |target| url.match?(target) }
|
|
102
|
-
end
|
|
103
82
|
end
|
|
104
83
|
end
|
|
105
84
|
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
|