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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/bin/test +15 -0
  3. data/gemfiles/ruby-2.7_rack-2_redis-4.gemfile.lock +159 -0
  4. data/gemfiles/ruby-2.7_rack-3.1_redis-4.gemfile.lock +159 -0
  5. data/gemfiles/ruby-2.7_rack-3_redis-4.gemfile.lock +159 -0
  6. data/gemfiles/ruby-3.0_rack-2_redis-4.gemfile.lock +173 -0
  7. data/gemfiles/ruby-3.0_rack-3.1_redis-4.gemfile.lock +173 -0
  8. data/gemfiles/ruby-3.0_rack-3_redis-4.gemfile.lock +173 -0
  9. data/gemfiles/ruby-3.1_rack-2_redis-4.gemfile.lock +241 -0
  10. data/gemfiles/ruby-3.1_rack-3.1_redis-4.gemfile.lock +241 -0
  11. data/gemfiles/ruby-3.1_rack-3_redis-4.gemfile.lock +241 -0
  12. data/gemfiles/ruby-3.2_rack-0_redis-5.gemfile.lock +263 -0
  13. data/gemfiles/ruby-3.2_rack-2_redis-4.gemfile.lock +260 -0
  14. data/gemfiles/ruby-3.2_rack-2_redis-5.gemfile.lock +266 -0
  15. data/gemfiles/ruby-3.2_rack-3.1_redis-4.gemfile.lock +260 -0
  16. data/gemfiles/ruby-3.2_rack-3_redis-4.gemfile.lock +260 -0
  17. data/gemfiles/ruby-3.2_rack-3_redis-5.gemfile.lock +266 -0
  18. data/gemfiles/ruby-3.3_rack-2_redis-4.gemfile.lock +263 -0
  19. data/gemfiles/ruby-3.3_rack-3.1_redis-4.gemfile.lock +263 -0
  20. data/gemfiles/ruby-3.3_rack-3.1_redis-5.3.gemfile.lock +269 -0
  21. data/gemfiles/ruby-3.3_rack-3_redis-4.gemfile.lock +263 -0
  22. data/gemfiles/ruby-3.4_rack-2_redis-4.gemfile.lock +254 -0
  23. data/gemfiles/ruby-3.4_rack-3.1_redis-4.gemfile.lock +254 -0
  24. data/gemfiles/ruby-3.4_rack-3.1_redis-5.3.gemfile.lock +260 -0
  25. data/gemfiles/ruby-3.4_rack-3_redis-4.gemfile.lock +254 -0
  26. data/gemfiles/ruby-4.0_rack-2_redis-4.gemfile.lock +254 -0
  27. data/gemfiles/ruby-4.0_rack-3.1_redis-4.gemfile.lock +254 -0
  28. data/gemfiles/ruby-4.0_rack-3_redis-4.gemfile.lock +254 -0
  29. data/gemfiles/ruby-jruby-9.4.14.0_rack-2_redis-4.gemfile.lock +266 -0
  30. data/gemfiles/ruby-jruby-9.4.14.0_rack-3.1_redis-4.gemfile.lock +266 -0
  31. data/gemfiles/ruby-jruby-9.4.14.0_rack-3_redis-4.gemfile.lock +266 -0
  32. data/lib/sentry/client.rb +9 -8
  33. data/lib/sentry/configuration.rb +74 -12
  34. data/lib/sentry/data_collection/key_value_collection.rb +136 -0
  35. data/lib/sentry/data_collection.rb +203 -0
  36. data/lib/sentry/debug_structured_logger.rb +2 -15
  37. data/lib/sentry/envelope/item.rb +21 -0
  38. data/lib/sentry/error_event.rb +6 -1
  39. data/lib/sentry/event.rb +8 -4
  40. data/lib/sentry/excon/middleware.rb +3 -8
  41. data/lib/sentry/faraday.rb +3 -4
  42. data/lib/sentry/hub.rb +2 -2
  43. data/lib/sentry/interfaces/exception.rb +8 -2
  44. data/lib/sentry/interfaces/request.rb +42 -46
  45. data/lib/sentry/interfaces/single_exception.rb +2 -2
  46. data/lib/sentry/net/http.rb +3 -4
  47. data/lib/sentry/redis.rb +1 -1
  48. data/lib/sentry/scope.rb +42 -3
  49. data/lib/sentry/span.rb +1 -0
  50. data/lib/sentry/std_lib_logger.rb +2 -6
  51. data/lib/sentry/telemetry_event_buffer.rb +9 -2
  52. data/lib/sentry/test_helper.rb +8 -7
  53. data/lib/sentry/threaded_periodic_worker.rb +8 -3
  54. data/lib/sentry/transport.rb +14 -3
  55. data/lib/sentry/utils/http_tracing.rb +42 -26
  56. data/lib/sentry/utils/telemetry_attributes.rb +26 -14
  57. data/lib/sentry/version.rb +1 -1
  58. data/lib/sentry-ruby.rb +94 -23
  59. data/sentry-ruby-core.gemspec +1 -1
  60. data/sentry-ruby.gemspec +1 -1
  61. metadata +35 -3
@@ -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
- attr_accessor :include_local_variables
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
- attr_accessor :send_default_pii
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 can optionally also send log events to Sentry via the normal structured logger if logging
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
- if configuration.enable_logs
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
@@ -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
@@ -32,7 +32,12 @@ module Sentry
32
32
  @extra.merge!(exception.sentry_context)
33
33
  end
34
34
 
35
- @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder, mechanism: mechanism)
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, :@send_default_pii, :@trusted_proxies, :@rack_env_whitelist]
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
- @send_default_pii = configuration.send_default_pii
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 @send_default_pii
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(env: env, send_default_pii: @send_default_pii, rack_env_whitelist: @rack_env_whitelist)
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
- if Sentry.configuration.send_default_pii
65
- result[:query] = env[:query]
66
-
67
- # Handle excon 1.0.0+
68
- result[:query] = build_nested_query(result[:query]) unless result[:query].is_a?(String)
69
-
70
- result[:body] = env[:body]
71
- end
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
@@ -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
- if Sentry.configuration.send_default_pii
63
- result[:query] = env[:url].query
64
- result[:body] = env[:body]
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