sentry-ruby-core 6.6.2 → 7.0.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/bin/test +15 -0
- data/gemfiles/ruby-2.7_rack-2_redis-4.gemfile.lock +159 -0
- data/gemfiles/ruby-2.7_rack-3.1_redis-4.gemfile.lock +159 -0
- data/gemfiles/ruby-2.7_rack-3_redis-4.gemfile.lock +159 -0
- data/gemfiles/ruby-3.0_rack-2_redis-4.gemfile.lock +173 -0
- data/gemfiles/ruby-3.0_rack-3.1_redis-4.gemfile.lock +173 -0
- data/gemfiles/ruby-3.0_rack-3_redis-4.gemfile.lock +173 -0
- data/gemfiles/ruby-3.1_rack-2_redis-4.gemfile.lock +241 -0
- data/gemfiles/ruby-3.1_rack-3.1_redis-4.gemfile.lock +241 -0
- data/gemfiles/ruby-3.1_rack-3_redis-4.gemfile.lock +241 -0
- data/gemfiles/ruby-3.2_rack-0_redis-5.gemfile.lock +263 -0
- data/gemfiles/ruby-3.2_rack-2_redis-4.gemfile.lock +260 -0
- data/gemfiles/ruby-3.2_rack-2_redis-5.gemfile.lock +266 -0
- data/gemfiles/ruby-3.2_rack-3.1_redis-4.gemfile.lock +260 -0
- data/gemfiles/ruby-3.2_rack-3_redis-4.gemfile.lock +260 -0
- data/gemfiles/ruby-3.2_rack-3_redis-5.gemfile.lock +266 -0
- data/gemfiles/ruby-3.3_rack-2_redis-4.gemfile.lock +263 -0
- data/gemfiles/ruby-3.3_rack-3.1_redis-4.gemfile.lock +263 -0
- data/gemfiles/ruby-3.3_rack-3.1_redis-5.3.gemfile.lock +269 -0
- data/gemfiles/ruby-3.3_rack-3_redis-4.gemfile.lock +263 -0
- data/gemfiles/ruby-3.4_rack-2_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-3.4_rack-3.1_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-3.4_rack-3.1_redis-5.3.gemfile.lock +260 -0
- data/gemfiles/ruby-3.4_rack-3_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-4.0_rack-2_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-4.0_rack-3.1_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-4.0_rack-3_redis-4.gemfile.lock +254 -0
- data/gemfiles/ruby-jruby-9.4.14.0_rack-2_redis-4.gemfile.lock +266 -0
- data/gemfiles/ruby-jruby-9.4.14.0_rack-3.1_redis-4.gemfile.lock +266 -0
- data/gemfiles/ruby-jruby-9.4.14.0_rack-3_redis-4.gemfile.lock +266 -0
- data/lib/sentry/client.rb +9 -8
- data/lib/sentry/configuration.rb +74 -12
- data/lib/sentry/data_collection/key_value_collection.rb +136 -0
- data/lib/sentry/data_collection.rb +203 -0
- data/lib/sentry/debug_structured_logger.rb +2 -15
- data/lib/sentry/envelope/item.rb +21 -0
- data/lib/sentry/error_event.rb +6 -1
- data/lib/sentry/event.rb +8 -4
- data/lib/sentry/excon/middleware.rb +3 -8
- data/lib/sentry/faraday.rb +3 -4
- data/lib/sentry/hub.rb +2 -2
- data/lib/sentry/interfaces/exception.rb +8 -2
- data/lib/sentry/interfaces/request.rb +42 -46
- data/lib/sentry/interfaces/single_exception.rb +2 -2
- data/lib/sentry/net/http.rb +3 -4
- data/lib/sentry/redis.rb +1 -1
- data/lib/sentry/scope.rb +42 -3
- data/lib/sentry/span.rb +1 -0
- data/lib/sentry/std_lib_logger.rb +2 -6
- data/lib/sentry/telemetry_event_buffer.rb +9 -2
- data/lib/sentry/test_helper.rb +8 -7
- data/lib/sentry/threaded_periodic_worker.rb +8 -3
- data/lib/sentry/transport.rb +14 -3
- data/lib/sentry/utils/http_tracing.rb +42 -26
- data/lib/sentry/utils/telemetry_attributes.rb +26 -14
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +94 -23
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +1 -1
- metadata +35 -3
data/lib/sentry/configuration.rb
CHANGED
|
@@ -16,6 +16,7 @@ require "sentry/logger"
|
|
|
16
16
|
require "sentry/structured_logger"
|
|
17
17
|
require "sentry/log_event_buffer"
|
|
18
18
|
require "sentry/metric_event_buffer"
|
|
19
|
+
require "sentry/data_collection"
|
|
19
20
|
|
|
20
21
|
module Sentry
|
|
21
22
|
class Configuration
|
|
@@ -132,6 +133,7 @@ module Sentry
|
|
|
132
133
|
attr_accessor :max_breadcrumbs
|
|
133
134
|
|
|
134
135
|
# Number of lines of code context to capture, or nil for none
|
|
136
|
+
# @deprecated Use {#data_collection} and `frame_context_lines` instead.
|
|
135
137
|
# @return [Integer, nil]
|
|
136
138
|
attr_accessor :context_lines
|
|
137
139
|
|
|
@@ -167,8 +169,14 @@ module Sentry
|
|
|
167
169
|
alias inspect_exception_causes_for_exclusion? inspect_exception_causes_for_exclusion
|
|
168
170
|
|
|
169
171
|
# Whether to capture local variables from the raised exception's frame. Default is false.
|
|
172
|
+
# @deprecated Use {#data_collection} and `stack_frame_variables` instead.
|
|
170
173
|
# @return [Boolean]
|
|
171
|
-
|
|
174
|
+
attr_reader :include_local_variables
|
|
175
|
+
|
|
176
|
+
def include_local_variables=(value)
|
|
177
|
+
@include_local_variables = value
|
|
178
|
+
@data_collection.backfill_stack_frame_variables(self)
|
|
179
|
+
end
|
|
172
180
|
|
|
173
181
|
# Whether to capture events and traces into Spotlight. Default is false.
|
|
174
182
|
# If you set this to true, Sentry will send events and traces to the local
|
|
@@ -209,6 +217,7 @@ module Sentry
|
|
|
209
217
|
attr_accessor :propagate_traces
|
|
210
218
|
|
|
211
219
|
# Array of rack env parameters to be included in the event sent to sentry.
|
|
220
|
+
# @deprecated Use `data_collection.http_headers.request` to control request data collection instead.
|
|
212
221
|
# @return [Array<String>]
|
|
213
222
|
attr_accessor :rack_env_whitelist
|
|
214
223
|
|
|
@@ -232,8 +241,14 @@ module Sentry
|
|
|
232
241
|
# - request body
|
|
233
242
|
# - query string
|
|
234
243
|
# will not be sent to Sentry.
|
|
244
|
+
# @deprecated Use {#data_collection} instead.
|
|
235
245
|
# @return [Boolean]
|
|
236
|
-
|
|
246
|
+
attr_reader :send_default_pii
|
|
247
|
+
|
|
248
|
+
# Controls which categories of data may be collected.
|
|
249
|
+
# Replacement for send_default_pii.
|
|
250
|
+
# @return [DataCollection]
|
|
251
|
+
attr_accessor :data_collection
|
|
237
252
|
|
|
238
253
|
# Capture queue time from X-Request-Start header set by reverse proxies.
|
|
239
254
|
# Works with any Rack app behind Nginx, HAProxy, Heroku router, etc.
|
|
@@ -273,10 +288,6 @@ module Sentry
|
|
|
273
288
|
# @return [Proc]
|
|
274
289
|
attr_accessor :traces_sampler
|
|
275
290
|
|
|
276
|
-
# Enable Structured Logging
|
|
277
|
-
# @return [Boolean]
|
|
278
|
-
attr_accessor :enable_logs
|
|
279
|
-
|
|
280
291
|
# Structured logging configuration.
|
|
281
292
|
# @return [StructuredLoggingConfiguration]
|
|
282
293
|
attr_reader :structured_logging
|
|
@@ -345,10 +356,6 @@ module Sentry
|
|
|
345
356
|
# @return [Integer]
|
|
346
357
|
attr_accessor :max_log_events
|
|
347
358
|
|
|
348
|
-
# Enable metrics collection, defaults to true
|
|
349
|
-
# @return [Boolean]
|
|
350
|
-
attr_accessor :enable_metrics
|
|
351
|
-
|
|
352
359
|
# Maximum number of metric events to buffer before sending
|
|
353
360
|
# @return [Integer]
|
|
354
361
|
attr_accessor :max_metric_events
|
|
@@ -389,6 +396,24 @@ module Sentry
|
|
|
389
396
|
# @return [Boolean]
|
|
390
397
|
attr_accessor :strict_trace_continuation
|
|
391
398
|
|
|
399
|
+
# Which execution primitive owns the SDK's current hub.
|
|
400
|
+
#
|
|
401
|
+
# [+:thread+ (default)] Store the hub in thread-local storage. Correct for
|
|
402
|
+
# thread-based servers (Puma, Unicorn) and background processors (Sidekiq,
|
|
403
|
+
# Resque). Every fiber on a thread shares one hub.
|
|
404
|
+
# [+:fiber+] Store the hub in Fiber Storage (Ruby 3.2+). Each fiber gets its
|
|
405
|
+
# own hub, so concurrent requests on a fiber-based server (Falcon/async)
|
|
406
|
+
# are isolated instead of sharing and corrupting one another's scope. A
|
|
407
|
+
# fiber or thread started from another inherits its context and trace, but
|
|
408
|
+
# its own scope changes stay local. Requested on a Ruby without Fiber
|
|
409
|
+
# Storage (< 3.2), the SDK logs a warning and falls back to +:thread+.
|
|
410
|
+
#
|
|
411
|
+
# @return [Symbol]
|
|
412
|
+
attr_reader :hub_isolation_level
|
|
413
|
+
|
|
414
|
+
# Isolation levels the SDK understands for hub storage.
|
|
415
|
+
ISOLATION_LEVELS = %i[thread fiber].freeze
|
|
416
|
+
|
|
392
417
|
# these are not config options
|
|
393
418
|
# @!visibility private
|
|
394
419
|
attr_reader :errors, :gem_specs
|
|
@@ -509,6 +534,7 @@ module Sentry
|
|
|
509
534
|
def initialize
|
|
510
535
|
run_callbacks(:before, :initialize)
|
|
511
536
|
|
|
537
|
+
self.data_collection = DataCollection.new
|
|
512
538
|
self.app_dirs_pattern = APP_DIRS_PATTERN
|
|
513
539
|
self.debug = Sentry::Utils::EnvHelper.env_to_bool(ENV["SENTRY_DEBUG"])
|
|
514
540
|
self.background_worker_threads = (processor_count / 2.0).ceil
|
|
@@ -519,6 +545,7 @@ module Sentry
|
|
|
519
545
|
self.breadcrumbs_logger = []
|
|
520
546
|
self.context_lines = 3
|
|
521
547
|
self.include_local_variables = false
|
|
548
|
+
|
|
522
549
|
self.environment = environment_from_env
|
|
523
550
|
self.enabled_environments = nil
|
|
524
551
|
self.exclude_loggers = []
|
|
@@ -541,6 +568,7 @@ module Sentry
|
|
|
541
568
|
self.capture_queue_time = true
|
|
542
569
|
self.org_id = nil
|
|
543
570
|
self.strict_trace_continuation = false
|
|
571
|
+
self.hub_isolation_level = :thread
|
|
544
572
|
|
|
545
573
|
spotlight_env = ENV["SENTRY_SPOTLIGHT"]
|
|
546
574
|
spotlight_bool = Sentry::Utils::EnvHelper.env_to_bool(spotlight_env, strict: true)
|
|
@@ -559,8 +587,6 @@ module Sentry
|
|
|
559
587
|
self.std_lib_logger_filter = nil
|
|
560
588
|
self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
|
|
561
589
|
self.traces_sampler = nil
|
|
562
|
-
self.enable_logs = false
|
|
563
|
-
self.enable_metrics = true
|
|
564
590
|
|
|
565
591
|
self.profiler_class = Sentry::Profiler
|
|
566
592
|
self.profiles_sample_interval = DEFAULT_PROFILES_SAMPLE_INTERVAL
|
|
@@ -577,6 +603,8 @@ module Sentry
|
|
|
577
603
|
|
|
578
604
|
yield(self) if block_given?
|
|
579
605
|
|
|
606
|
+
log_deprecations
|
|
607
|
+
|
|
580
608
|
run_callbacks(:after, :configured)
|
|
581
609
|
end
|
|
582
610
|
|
|
@@ -602,6 +630,11 @@ module Sentry
|
|
|
602
630
|
@dsn = init_dsn(value)
|
|
603
631
|
end
|
|
604
632
|
|
|
633
|
+
def send_default_pii=(value)
|
|
634
|
+
@send_default_pii = value
|
|
635
|
+
self.data_collection = DataCollection.backfill(self)
|
|
636
|
+
end
|
|
637
|
+
|
|
605
638
|
alias server= dsn=
|
|
606
639
|
|
|
607
640
|
def release=(value)
|
|
@@ -610,6 +643,23 @@ module Sentry
|
|
|
610
643
|
@release = value
|
|
611
644
|
end
|
|
612
645
|
|
|
646
|
+
def hub_isolation_level=(level)
|
|
647
|
+
level = level.to_sym if level.respond_to?(:to_sym)
|
|
648
|
+
|
|
649
|
+
unless ISOLATION_LEVELS.include?(level)
|
|
650
|
+
raise ArgumentError, "hub_isolation_level must be one of #{ISOLATION_LEVELS.inspect}, got #{level.inspect}"
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
# :fiber relies on Fiber Storage (Ruby 3.2+); downgrade so the hub is never
|
|
654
|
+
# asked to call Fiber[] on a Ruby that lacks it.
|
|
655
|
+
if level == :fiber && !fiber_storage_available?
|
|
656
|
+
log_warn("hub_isolation_level :fiber requires Ruby 3.2+ Fiber Storage; falling back to :thread on Ruby #{RUBY_VERSION}.")
|
|
657
|
+
level = :thread
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
@hub_isolation_level = level
|
|
661
|
+
end
|
|
662
|
+
|
|
613
663
|
def breadcrumbs_logger=(logger)
|
|
614
664
|
loggers =
|
|
615
665
|
if logger.is_a?(Array)
|
|
@@ -768,6 +818,14 @@ module Sentry
|
|
|
768
818
|
end
|
|
769
819
|
end
|
|
770
820
|
|
|
821
|
+
# @api private
|
|
822
|
+
def log_deprecations
|
|
823
|
+
log_warn("`send_default_pii` is deprecated; use `data_collection` instead.") if self.send_default_pii
|
|
824
|
+
log_warn("`include_local_variables` is deprecated; use `data_collection.stack_frame_variables` instead.") if include_local_variables
|
|
825
|
+
log_warn("`context_lines` is deprecated; use `data_collection.frame_context_lines` instead.") if context_lines != 3
|
|
826
|
+
log_warn("`rack_env_whitelist` is deprecated; use `data_collection.http_headers.request` to control request data collection instead.") if rack_env_whitelist != RACK_ENV_WHITELIST_DEFAULT
|
|
827
|
+
end
|
|
828
|
+
|
|
771
829
|
# @api private
|
|
772
830
|
def stacktrace_builder
|
|
773
831
|
@stacktrace_builder ||= StacktraceBuilder.new(
|
|
@@ -806,6 +864,10 @@ module Sentry
|
|
|
806
864
|
|
|
807
865
|
private
|
|
808
866
|
|
|
867
|
+
def fiber_storage_available?
|
|
868
|
+
::Fiber.respond_to?(:[]) && ::Fiber.respond_to?(:[]=)
|
|
869
|
+
end
|
|
870
|
+
|
|
809
871
|
def init_dsn(dsn_string)
|
|
810
872
|
return if dsn_string.nil? || dsn_string.empty?
|
|
811
873
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
class DataCollection
|
|
5
|
+
# Configuration for key-value data collection.
|
|
6
|
+
class KeyValueCollection
|
|
7
|
+
FILTERED_VALUE = "[Filtered]"
|
|
8
|
+
|
|
9
|
+
# Keys from this list are ALWAYS filtered, regardless of :mode
|
|
10
|
+
SENSITIVE_DENY_LIST = %w[
|
|
11
|
+
auth
|
|
12
|
+
token
|
|
13
|
+
secret
|
|
14
|
+
session
|
|
15
|
+
password
|
|
16
|
+
passwd
|
|
17
|
+
pwd
|
|
18
|
+
key
|
|
19
|
+
jwt
|
|
20
|
+
bearer
|
|
21
|
+
sso
|
|
22
|
+
saml
|
|
23
|
+
csrf
|
|
24
|
+
xsrf
|
|
25
|
+
credentials
|
|
26
|
+
sid
|
|
27
|
+
identity
|
|
28
|
+
cookie
|
|
29
|
+
set-cookie
|
|
30
|
+
].freeze
|
|
31
|
+
|
|
32
|
+
# Additional terms applied to cookie names only. These cover common
|
|
33
|
+
# opaque session, identity-provider, and load-balancer cookies without
|
|
34
|
+
# making the general header denylist overly broad.
|
|
35
|
+
SENSITIVE_COOKIE_NAME_DENY_LIST = %w[
|
|
36
|
+
.sid
|
|
37
|
+
sessid
|
|
38
|
+
remember
|
|
39
|
+
oidc
|
|
40
|
+
pkce
|
|
41
|
+
nonce
|
|
42
|
+
__secure-
|
|
43
|
+
__host-
|
|
44
|
+
awsalb
|
|
45
|
+
awselb
|
|
46
|
+
akamai
|
|
47
|
+
__stripe
|
|
48
|
+
cognito
|
|
49
|
+
firebase
|
|
50
|
+
supabase
|
|
51
|
+
sb-
|
|
52
|
+
mfa
|
|
53
|
+
2fa
|
|
54
|
+
].freeze
|
|
55
|
+
|
|
56
|
+
# `mode` controls whether values are collected:
|
|
57
|
+
# - `:off` disables collection.
|
|
58
|
+
# - `:deny_list` collects values except those matching `terms`.
|
|
59
|
+
# - `:allow_list` collects only values matching `terms`.
|
|
60
|
+
# Boolean values are accepted as shorthand for `:deny_list` and `:off`.
|
|
61
|
+
# @return [:off, :deny_list, :allow_list]
|
|
62
|
+
attr_reader :mode
|
|
63
|
+
|
|
64
|
+
# `terms` contains the keys or patterns used by the selected mode.
|
|
65
|
+
# @return [Array<String, Regexp>, nil]
|
|
66
|
+
attr_reader :terms
|
|
67
|
+
|
|
68
|
+
def initialize(mode:, terms:)
|
|
69
|
+
self.mode = mode
|
|
70
|
+
self.terms = terms
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def mode=(mode)
|
|
74
|
+
@mode = case mode
|
|
75
|
+
when true then :deny_list
|
|
76
|
+
when false, nil then :off
|
|
77
|
+
else mode
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Converts the boolean shorthand into a collection configuration.
|
|
82
|
+
def self.from(value)
|
|
83
|
+
return new(mode: value, terms: nil) if value.equal?(true) || value.equal?(false) || value.nil?
|
|
84
|
+
|
|
85
|
+
value
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def terms=(terms)
|
|
89
|
+
@terms = terms&.map { |term| term.is_a?(Regexp) ? term : term.to_s.downcase }
|
|
90
|
+
&.reject { |term| !term.is_a?(Regexp) && term.strip.empty? }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Applies this collection configuration without changing the input hash.
|
|
94
|
+
# Keys are retained whenever the category is collected; values that are not
|
|
95
|
+
# safe to send are replaced with FILTERED_VALUE.
|
|
96
|
+
#
|
|
97
|
+
# @param values [Hash] key-value data to filter
|
|
98
|
+
# @return [Hash] a new filtered hash, or an empty hash when collection is off
|
|
99
|
+
def filter(values, cookie: false)
|
|
100
|
+
return {} if mode == :off
|
|
101
|
+
|
|
102
|
+
values.each_with_object({}) do |(key, value), filtered|
|
|
103
|
+
filtered[key] = safe_value?(key, cookie: cookie) ? value : FILTERED_VALUE
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def safe_value?(key, cookie: false)
|
|
110
|
+
key_string = key.to_s
|
|
111
|
+
key_downcase = key_string.downcase
|
|
112
|
+
return false if sensitive?(key_downcase, cookie: cookie)
|
|
113
|
+
|
|
114
|
+
case mode
|
|
115
|
+
when :deny_list
|
|
116
|
+
!matches_any_term?(key_string, key_downcase)
|
|
117
|
+
when :allow_list
|
|
118
|
+
matches_any_term?(key_string, key_downcase)
|
|
119
|
+
else
|
|
120
|
+
false
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def sensitive?(key, cookie: false)
|
|
125
|
+
SENSITIVE_DENY_LIST.any? { |term| key.include?(term) } ||
|
|
126
|
+
(cookie && SENSITIVE_COOKIE_NAME_DENY_LIST.any? { |term| key.include?(term) })
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def matches_any_term?(key, key_downcase)
|
|
130
|
+
@terms&.any? do |term|
|
|
131
|
+
term.is_a?(Regexp) ? term.match?(key) : key_downcase.include?(term)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sentry/data_collection/key_value_collection"
|
|
4
|
+
|
|
5
|
+
module Sentry
|
|
6
|
+
class DataCollection
|
|
7
|
+
# Configuration for the categories of data collected by the SDK.
|
|
8
|
+
# Replacement for send_default_pii.
|
|
9
|
+
# Spec: https://develop.sentry.dev/sdk/foundations/client/data-collection/
|
|
10
|
+
#
|
|
11
|
+
# @example Configure data collection
|
|
12
|
+
# Sentry.init do |config|
|
|
13
|
+
# data_collection = config.data_collection
|
|
14
|
+
# data_collection.user_info = true
|
|
15
|
+
# data_collection.cookies.mode = :deny_list
|
|
16
|
+
# data_collection.cookies.terms = ["session", "token"]
|
|
17
|
+
# data_collection.http_headers.request.mode = :deny_list
|
|
18
|
+
# data_collection.http_headers.request.terms = nil
|
|
19
|
+
# data_collection.http_headers.response.mode = :allow_list
|
|
20
|
+
# data_collection.http_headers.response.terms = ["my_special_header"]
|
|
21
|
+
# data_collection.http_bodies = [:incoming_request]
|
|
22
|
+
# data_collection.url_query_params.mode = :allow_list
|
|
23
|
+
# data_collection.url_query_params.terms = ["page", "limit"]
|
|
24
|
+
# data_collection.graphql.document = true
|
|
25
|
+
# data_collection.graphql.variables = true
|
|
26
|
+
# data_collection.database_query_data = true
|
|
27
|
+
# data_collection.queues = true
|
|
28
|
+
# data_collection.stack_frame_variables = true
|
|
29
|
+
# data_collection.frame_context_lines = 5
|
|
30
|
+
# end
|
|
31
|
+
|
|
32
|
+
MODES = %i[off deny_list allow_list].freeze
|
|
33
|
+
|
|
34
|
+
PII_HEADER_SNIPPETS = %w[forwarded -ip _ip remote via _user -user].freeze
|
|
35
|
+
|
|
36
|
+
BODY_TYPES = %i[
|
|
37
|
+
incoming_request
|
|
38
|
+
outgoing_request
|
|
39
|
+
incoming_response
|
|
40
|
+
outgoing_response
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
class HttpHeaders
|
|
44
|
+
# @return [KeyValueCollection]
|
|
45
|
+
attr_reader :request
|
|
46
|
+
|
|
47
|
+
# @return [KeyValueCollection]
|
|
48
|
+
attr_reader :response
|
|
49
|
+
|
|
50
|
+
def initialize(request:, response:)
|
|
51
|
+
self.request = request
|
|
52
|
+
self.response = response
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def request=(value)
|
|
56
|
+
@request = KeyValueCollection.from(value)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def response=(value)
|
|
60
|
+
@response = KeyValueCollection.from(value)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class GraphQL
|
|
65
|
+
# @return [Boolean]
|
|
66
|
+
attr_accessor :document
|
|
67
|
+
|
|
68
|
+
# @return [Boolean]
|
|
69
|
+
attr_accessor :variables
|
|
70
|
+
|
|
71
|
+
def initialize(document:, variables:)
|
|
72
|
+
@document = document
|
|
73
|
+
@variables = variables
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [Boolean]
|
|
78
|
+
# @default `true`
|
|
79
|
+
attr_accessor :user_info
|
|
80
|
+
|
|
81
|
+
# @return [Boolean, KeyValueCollection]
|
|
82
|
+
# A boolean is shorthand for `mode: :deny_list` (`true`) or `mode: :off` (`false`).
|
|
83
|
+
# @default `mode: :deny_list, terms: nil`
|
|
84
|
+
attr_reader :cookies
|
|
85
|
+
|
|
86
|
+
# @return [HttpHeaders]
|
|
87
|
+
# @default request and response use `mode: :deny_list, terms: nil`
|
|
88
|
+
attr_accessor :http_headers
|
|
89
|
+
|
|
90
|
+
# @return [Array<Symbol>] containing values from BODY_TYPES
|
|
91
|
+
# @default `nil` (all valid body types)
|
|
92
|
+
attr_accessor :http_bodies
|
|
93
|
+
|
|
94
|
+
# @return [Boolean, KeyValueCollection]
|
|
95
|
+
# A boolean is shorthand for `mode: :deny_list` (`true`) or `mode: :off` (`false`).
|
|
96
|
+
# @default `mode: :deny_list, terms: nil`
|
|
97
|
+
attr_reader :url_query_params
|
|
98
|
+
|
|
99
|
+
# @return [Boolean]
|
|
100
|
+
# @default `true`
|
|
101
|
+
attr_accessor :database_query_data
|
|
102
|
+
|
|
103
|
+
# @return [GraphQL]
|
|
104
|
+
# @default `document: true, variables: true`
|
|
105
|
+
attr_accessor :graphql
|
|
106
|
+
|
|
107
|
+
# @return [Boolean]
|
|
108
|
+
# @default `true`
|
|
109
|
+
attr_accessor :queues
|
|
110
|
+
|
|
111
|
+
# @return [Boolean, KeyValueCollection]
|
|
112
|
+
# A boolean is shorthand for `mode: :deny_list` (`true`) or `mode: :off` (`false`).
|
|
113
|
+
# @default `false` (Ruby-specific to avoid TracePoint overhead)
|
|
114
|
+
attr_reader :stack_frame_variables
|
|
115
|
+
|
|
116
|
+
# @return [Integer]
|
|
117
|
+
# @default `3`
|
|
118
|
+
attr_accessor :frame_context_lines
|
|
119
|
+
|
|
120
|
+
# Filters key-value data using the default sensitive denylist.
|
|
121
|
+
def self.filter(values)
|
|
122
|
+
default_filter.filter(values, cookie: false)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.default_filter
|
|
126
|
+
@default_filter ||= KeyValueCollection.new(mode: :deny_list, terms: nil)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Builds data collection settings compatible with the legacy send_default_pii
|
|
130
|
+
# configuration.
|
|
131
|
+
def self.backfill(configuration)
|
|
132
|
+
data_collection = new
|
|
133
|
+
data_collection.backfill_stack_frame_variables(configuration)
|
|
134
|
+
return data_collection if configuration.send_default_pii
|
|
135
|
+
|
|
136
|
+
# TODO-neel-data map to exact ruby behaviour for backwards compat behavior
|
|
137
|
+
data_collection.user_info = false
|
|
138
|
+
data_collection.cookies.mode = :off
|
|
139
|
+
data_collection.http_headers.request.mode = :deny_list
|
|
140
|
+
data_collection.http_headers.request.terms = PII_HEADER_SNIPPETS
|
|
141
|
+
data_collection.http_headers.response.mode = :deny_list
|
|
142
|
+
data_collection.http_headers.response.terms = PII_HEADER_SNIPPETS
|
|
143
|
+
data_collection.http_bodies = []
|
|
144
|
+
data_collection.url_query_params.mode = :off
|
|
145
|
+
data_collection.graphql.document = false
|
|
146
|
+
data_collection.graphql.variables = false
|
|
147
|
+
data_collection.database_query_data = false
|
|
148
|
+
data_collection.queues = false
|
|
149
|
+
data_collection.frame_context_lines = configuration.context_lines
|
|
150
|
+
data_collection
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def initialize
|
|
154
|
+
@user_info = true
|
|
155
|
+
self.cookies = KeyValueCollection.new(mode: :deny_list, terms: nil)
|
|
156
|
+
@http_headers = HttpHeaders.new(
|
|
157
|
+
request: KeyValueCollection.new(mode: :deny_list, terms: nil),
|
|
158
|
+
response: KeyValueCollection.new(mode: :deny_list, terms: nil)
|
|
159
|
+
)
|
|
160
|
+
@http_bodies = BODY_TYPES.dup
|
|
161
|
+
self.url_query_params = KeyValueCollection.new(mode: :deny_list, terms: nil)
|
|
162
|
+
@database_query_data = true
|
|
163
|
+
@graphql = GraphQL.new(document: true, variables: true)
|
|
164
|
+
@queues = true
|
|
165
|
+
self.stack_frame_variables = false
|
|
166
|
+
@frame_context_lines = 3
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Copies the legacy stack frame variable configuration.
|
|
170
|
+
def backfill_stack_frame_variables(configuration)
|
|
171
|
+
self.stack_frame_variables = configuration.include_local_variables
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Returns whether stack frame local variables should be captured.
|
|
175
|
+
def collect_stack_frame_variables?
|
|
176
|
+
stack_frame_variables.mode != :off
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def stack_frame_variables=(value)
|
|
180
|
+
@stack_frame_variables = KeyValueCollection.from(value)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def cookies=(value)
|
|
184
|
+
@cookies = KeyValueCollection.from(value)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def url_query_params=(value)
|
|
188
|
+
@url_query_params = KeyValueCollection.from(value)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Returns whether incoming HTTP request bodies should be collected.
|
|
192
|
+
# nil implies all BODY_TYPES according to spec
|
|
193
|
+
def collect_incoming_http_body?
|
|
194
|
+
http_bodies.nil? || http_bodies.include?(:incoming_request)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Returns whether outgoing HTTP request bodies should be collected.
|
|
198
|
+
# nil implies all BODY_TYPES according to spec
|
|
199
|
+
def collect_outgoing_http_body?
|
|
200
|
+
http_bodies.nil? || http_bodies.include?(:outgoing_request)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -8,8 +8,7 @@ require "delegate"
|
|
|
8
8
|
module Sentry
|
|
9
9
|
# DebugStructuredLogger is a logger that captures structured log events to a file for debugging purposes.
|
|
10
10
|
#
|
|
11
|
-
# It
|
|
12
|
-
# is enabled.
|
|
11
|
+
# It also sends log events to Sentry via the normal structured logger.
|
|
13
12
|
class DebugStructuredLogger < SimpleDelegator
|
|
14
13
|
DEFAULT_LOG_FILE_PATH = File.join("log", "sentry_debug_logs.log")
|
|
15
14
|
|
|
@@ -68,12 +67,7 @@ module Sentry
|
|
|
68
67
|
private
|
|
69
68
|
|
|
70
69
|
def initialize_backend(configuration)
|
|
71
|
-
|
|
72
|
-
StructuredLogger.new(configuration)
|
|
73
|
-
else
|
|
74
|
-
# Create a no-op logger if logging is disabled
|
|
75
|
-
NoOpLogger.new
|
|
76
|
-
end
|
|
70
|
+
StructuredLogger.new(configuration)
|
|
77
71
|
end
|
|
78
72
|
|
|
79
73
|
def initialize_log_file(log_file_path)
|
|
@@ -83,12 +77,5 @@ module Sentry
|
|
|
83
77
|
|
|
84
78
|
log_file
|
|
85
79
|
end
|
|
86
|
-
|
|
87
|
-
# No-op logger for when structured logging is disabled
|
|
88
|
-
class NoOpLogger
|
|
89
|
-
%i[trace debug info warn error fatal log].each do |method|
|
|
90
|
-
define_method(method) { |*args, **kwargs| nil }
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
80
|
end
|
|
94
81
|
end
|
data/lib/sentry/envelope/item.rb
CHANGED
|
@@ -25,6 +25,13 @@ module Sentry
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def self.byte_data_category(data_category)
|
|
29
|
+
case data_category
|
|
30
|
+
when "log_item" then "log_byte"
|
|
31
|
+
when "trace_metric" then "trace_metric_byte"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
28
35
|
def initialize(headers, payload)
|
|
29
36
|
@headers = headers
|
|
30
37
|
@payload = payload
|
|
@@ -33,6 +40,20 @@ module Sentry
|
|
|
33
40
|
@size_limit = SIZE_LIMITS[type]
|
|
34
41
|
end
|
|
35
42
|
|
|
43
|
+
def byte_data_category
|
|
44
|
+
self.class.byte_data_category(data_category)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def item_count
|
|
48
|
+
headers[:item_count] || 1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def lost_event_byte_size
|
|
52
|
+
return unless byte_data_category
|
|
53
|
+
|
|
54
|
+
(payload.is_a?(String) ? payload : JSON.generate(payload)).bytesize
|
|
55
|
+
end
|
|
56
|
+
|
|
36
57
|
def to_s
|
|
37
58
|
[JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
|
|
38
59
|
end
|
data/lib/sentry/error_event.rb
CHANGED
|
@@ -32,7 +32,12 @@ module Sentry
|
|
|
32
32
|
@extra.merge!(exception.sentry_context)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
@exception = Sentry::ExceptionInterface.build(
|
|
35
|
+
@exception = Sentry::ExceptionInterface.build(
|
|
36
|
+
exception: exception,
|
|
37
|
+
stacktrace_builder: @stacktrace_builder,
|
|
38
|
+
mechanism: mechanism,
|
|
39
|
+
data_collection: @data_collection
|
|
40
|
+
)
|
|
36
41
|
end
|
|
37
42
|
end
|
|
38
43
|
end
|
data/lib/sentry/event.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Sentry
|
|
|
28
28
|
|
|
29
29
|
MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
|
|
30
30
|
|
|
31
|
-
SKIP_INSPECTION_ATTRIBUTES = [:@modules, :@stacktrace_builder, :@
|
|
31
|
+
SKIP_INSPECTION_ATTRIBUTES = [:@modules, :@stacktrace_builder, :@data_collection, :@trusted_proxies, :@rack_env_whitelist]
|
|
32
32
|
|
|
33
33
|
include CustomInspection
|
|
34
34
|
|
|
@@ -73,7 +73,7 @@ module Sentry
|
|
|
73
73
|
@modules = configuration.gem_specs if configuration.send_modules
|
|
74
74
|
|
|
75
75
|
# configuration options to help events process data
|
|
76
|
-
@
|
|
76
|
+
@data_collection = configuration.data_collection
|
|
77
77
|
@trusted_proxies = configuration.trusted_proxies
|
|
78
78
|
@stacktrace_builder = configuration.stacktrace_builder
|
|
79
79
|
@rack_env_whitelist = configuration.rack_env_whitelist
|
|
@@ -103,7 +103,7 @@ module Sentry
|
|
|
103
103
|
unless request || env.empty?
|
|
104
104
|
add_request_interface(env)
|
|
105
105
|
|
|
106
|
-
user[:ip_address] ||= calculate_real_ip_from_rack(env) if @
|
|
106
|
+
user[:ip_address] ||= calculate_real_ip_from_rack(env) if @data_collection.user_info
|
|
107
107
|
|
|
108
108
|
if request_id = Utils::RequestId.read_from(env)
|
|
109
109
|
tags[:request_id] = request_id
|
|
@@ -127,7 +127,11 @@ module Sentry
|
|
|
127
127
|
private
|
|
128
128
|
|
|
129
129
|
def add_request_interface(env)
|
|
130
|
-
@request = Sentry::RequestInterface.new(
|
|
130
|
+
@request = Sentry::RequestInterface.new(
|
|
131
|
+
env: env,
|
|
132
|
+
data_collection: @data_collection,
|
|
133
|
+
rack_env_whitelist: @rack_env_whitelist
|
|
134
|
+
)
|
|
131
135
|
end
|
|
132
136
|
|
|
133
137
|
def serialize_attributes
|
|
@@ -61,14 +61,9 @@ module Sentry
|
|
|
61
61
|
url = env[:scheme] + "://" + env[:hostname] + env[:path]
|
|
62
62
|
result = { method: env[:method].to_s.upcase, url: url }
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
64
|
+
query = filter_query_params(env[:query])
|
|
65
|
+
result[:query] = query if query
|
|
66
|
+
result[:body] = env[:body] if Sentry.configuration.data_collection.collect_outgoing_http_body?
|
|
72
67
|
|
|
73
68
|
result
|
|
74
69
|
end
|
data/lib/sentry/faraday.rb
CHANGED
|
@@ -59,10 +59,9 @@ module Sentry
|
|
|
59
59
|
url = env[:url].scheme + "://" + env[:url].host + env[:url].path
|
|
60
60
|
result = { method: env[:method].to_s.upcase, url: url }
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
end
|
|
62
|
+
query = filter_query_params(env[:url].query)
|
|
63
|
+
result[:query] = query if query
|
|
64
|
+
result[:body] = env[:body] if Sentry.configuration.data_collection.collect_outgoing_http_body?
|
|
66
65
|
|
|
67
66
|
result
|
|
68
67
|
end
|