Skip to content

Commit 67ccdff

Browse files
committed
feat(data-collection): Add base DataCollection configuration with defaults and backfill
1 parent b02a915 commit 67ccdff

4 files changed

Lines changed: 316 additions & 1 deletion

File tree

sentry-ruby/lib/sentry/configuration.rb

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
require "sentry/structured_logger"
1717
require "sentry/log_event_buffer"
1818
require "sentry/metric_event_buffer"
19+
require "sentry/data_collection"
1920

2021
module Sentry
2122
class Configuration
@@ -132,6 +133,7 @@ class Configuration
132133
attr_accessor :max_breadcrumbs
133134

134135
# Number of lines of code context to capture, or nil for none
136+
# @deprecated Use {#data_collection} and `frame_context_lines` instead.
135137
# @return [Integer, nil]
136138
attr_accessor :context_lines
137139

@@ -167,6 +169,7 @@ class Configuration
167169
alias inspect_exception_causes_for_exclusion? inspect_exception_causes_for_exclusion
168170

169171
# Whether to capture local variables from the raised exception's frame. Default is false.
172+
# @deprecated Use {#data_collection} and `stack_frame_variables` instead.
170173
# @return [Boolean]
171174
attr_accessor :include_local_variables
172175

@@ -232,8 +235,14 @@ class Configuration
232235
# - request body
233236
# - query string
234237
# will not be sent to Sentry.
238+
# @deprecated Use {#data_collection} instead.
235239
# @return [Boolean]
236-
attr_accessor :send_default_pii
240+
attr_reader :send_default_pii
241+
242+
# Controls which categories of data may be collected.
243+
# Replacement for send_default_pii.
244+
# @return [DataCollection]
245+
attr_accessor :data_collection
237246

238247
# Capture queue time from X-Request-Start header set by reverse proxies.
239248
# Works with any Rack app behind Nginx, HAProxy, Heroku router, etc.
@@ -536,6 +545,7 @@ def initialize
536545
self.breadcrumbs_logger = []
537546
self.context_lines = 3
538547
self.include_local_variables = false
548+
539549
self.environment = environment_from_env
540550
self.enabled_environments = nil
541551
self.exclude_loggers = []
@@ -548,6 +558,7 @@ def initialize
548558

549559
self.sample_rate = 1.0
550560
self.send_modules = true
561+
self.data_collection = DataCollection.new
551562
self.send_default_pii = false
552563
self.skip_rake_integration = false
553564
self.send_client_reports = true
@@ -595,6 +606,8 @@ def initialize
595606

596607
yield(self) if block_given?
597608

609+
log_deprecations
610+
598611
run_callbacks(:after, :configured)
599612
end
600613

@@ -620,6 +633,11 @@ def dsn=(value)
620633
@dsn = init_dsn(value)
621634
end
622635

636+
def send_default_pii=(value)
637+
@send_default_pii = value
638+
backfill_data_collection
639+
end
640+
623641
alias server= dsn=
624642

625643
def release=(value)
@@ -803,6 +821,37 @@ def csp_report_uri
803821
end
804822
end
805823

824+
# @api private
825+
def log_deprecations
826+
log_warn("`send_default_pii` is deprecated; use `data_collection` instead.") if self.send_default_pii
827+
log_warn("`include_local_variables` is deprecated; use `data_collection.stack_frame_variables` instead.") if include_local_variables
828+
log_warn("`context_lines` is deprecated; use `data_collection.frame_context_lines` instead.") if context_lines != 3
829+
end
830+
831+
# Updates data collection defaults for applications still using send_default_pii.
832+
# @api private
833+
def backfill_data_collection
834+
# the new DataCollection defaults are already correct if pii is enabled
835+
if self.send_default_pii
836+
self.data_collection = DataCollection.new
837+
return
838+
end
839+
840+
# TODO-neel-data map to exact ruby behaviour for backwards compat behavior
841+
self.data_collection.user_info = false
842+
self.data_collection.cookies.mode = :off
843+
self.data_collection.http_headers.request.mode = :off
844+
self.data_collection.http_headers.response.mode = :off
845+
self.data_collection.http_bodies = []
846+
self.data_collection.url_query_params.mode = :off
847+
self.data_collection.graphql.document = false
848+
self.data_collection.graphql.variables = false
849+
self.data_collection.database_query_data = false
850+
self.data_collection.queues = false
851+
self.data_collection.stack_frame_variables = self.include_local_variables
852+
self.data_collection.frame_context_lines = self.context_lines
853+
end
854+
806855
# @api private
807856
def stacktrace_builder
808857
@stacktrace_builder ||= StacktraceBuilder.new(
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# frozen_string_literal: true
2+
3+
module Sentry
4+
class DataCollection
5+
# Configuration for the categories of data collected by the SDK.
6+
# Replacement for send_default_pii.
7+
# Spec: https://develop.sentry.dev/sdk/foundations/client/data-collection/
8+
#
9+
# @example Configure data collection
10+
# Sentry.init do |config|
11+
# data_collection = config.data_collection
12+
# data_collection.user_info = true
13+
# data_collection.cookies.mode = :deny_list
14+
# data_collection.cookies.terms = ["session", "token"]
15+
# data_collection.http_headers.request.mode = :deny_list
16+
# data_collection.http_headers.request.terms = nil
17+
# data_collection.http_headers.response.mode = :allow_list
18+
# data_collection.http_headers.response.terms = ["my_special_header"]
19+
# data_collection.http_bodies = [:incoming_request]
20+
# data_collection.url_query_params.mode = :allow_list
21+
# data_collection.url_query_params.terms = ["page", "limit"]
22+
# data_collection.graphql.document = true
23+
# data_collection.graphql.variables = true
24+
# data_collection.database_query_data = true
25+
# data_collection.queues = true
26+
# data_collection.stack_frame_variables = true
27+
# data_collection.frame_context_lines = 5
28+
# end
29+
MODES = %i[off deny_list allow_list].freeze
30+
BODY_TYPES = %i[
31+
incoming_request
32+
outgoing_request
33+
incoming_response
34+
outgoing_response
35+
].freeze
36+
37+
# Configuration for key-value data collection.
38+
class KeyValueCollection
39+
# `mode` controls whether values are collected:
40+
# - `:off` disables collection.
41+
# - `:deny_list` collects values except those matching `terms`.
42+
# - `:allow_list` collects only values matching `terms`.
43+
# @return [:off, :deny_list, :allow_list]
44+
attr_accessor :mode
45+
46+
# `terms` contains the keys or patterns used by the selected mode.
47+
# @return [Array<String>, nil]
48+
attr_accessor :terms
49+
50+
def initialize(mode:, terms:)
51+
@mode = mode
52+
@terms = terms
53+
end
54+
end
55+
56+
class HttpHeaders
57+
# @return [KeyValueCollection]
58+
attr_accessor :request
59+
60+
# @return [KeyValueCollection]
61+
attr_accessor :response
62+
63+
def initialize(request:, response:)
64+
@request = request
65+
@response = response
66+
end
67+
end
68+
69+
class GraphQL
70+
# @return [Boolean]
71+
attr_accessor :document
72+
73+
# @return [Boolean]
74+
attr_accessor :variables
75+
76+
def initialize(document:, variables:)
77+
@document = document
78+
@variables = variables
79+
end
80+
end
81+
82+
# @return [Boolean]
83+
# @default `true`
84+
attr_accessor :user_info
85+
86+
# @return [KeyValueCollection]
87+
# @default `mode: :deny_list, terms: nil`
88+
attr_accessor :cookies
89+
90+
# @return [HttpHeaders]
91+
# @default request and response use `mode: :deny_list, terms: nil`
92+
attr_accessor :http_headers
93+
94+
# @return [Array<Symbol>] containing values from BODY_TYPES
95+
# @default `nil` (all valid body types)
96+
attr_accessor :http_bodies
97+
98+
# @return [KeyValueCollection]
99+
# @default `mode: :deny_list, terms: nil`
100+
attr_accessor :url_query_params
101+
102+
# @return [Boolean]
103+
# @default `true`
104+
attr_accessor :database_query_data
105+
106+
# @return [GraphQL]
107+
# @default `document: true, variables: true`
108+
attr_accessor :graphql
109+
110+
# @return [Boolean]
111+
# @default `true`
112+
attr_accessor :queues
113+
114+
# @return [Boolean]
115+
# @default `false`
116+
attr_accessor :stack_frame_variables
117+
118+
# @return [Integer]
119+
# @default `3`
120+
attr_accessor :frame_context_lines
121+
122+
def initialize
123+
@user_info = true
124+
@cookies = KeyValueCollection.new(mode: :deny_list, terms: nil)
125+
@http_headers = HttpHeaders.new(
126+
request: KeyValueCollection.new(mode: :deny_list, terms: nil),
127+
response: KeyValueCollection.new(mode: :deny_list, terms: nil)
128+
)
129+
@http_bodies = nil
130+
@url_query_params = KeyValueCollection.new(mode: :deny_list, terms: nil)
131+
@database_query_data = true
132+
@graphql = GraphQL.new(document: true, variables: true)
133+
@queues = true
134+
@stack_frame_variables = false
135+
@frame_context_lines = 3
136+
end
137+
end
138+
end

sentry-ruby/spec/sentry/configuration_spec.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,70 @@
11
# frozen_string_literal: true
22

33
RSpec.describe Sentry::Configuration do
4+
describe "#data_collection" do
5+
it "uses the send_default_pii=false defaults" do
6+
expect(subject.data_collection.user_info).to eq(false)
7+
expect(subject.data_collection.cookies.mode).to eq(:off)
8+
expect(subject.data_collection.http_headers.request.mode).to eq(:off)
9+
expect(subject.data_collection.http_headers.response.mode).to eq(:off)
10+
expect(subject.data_collection.http_bodies).to eq([])
11+
expect(subject.data_collection.url_query_params.mode).to eq(:off)
12+
expect(subject.data_collection.database_query_data).to eq(false)
13+
expect(subject.data_collection.graphql.document).to eq(false)
14+
expect(subject.data_collection.graphql.variables).to eq(false)
15+
expect(subject.data_collection.queues).to eq(false)
16+
expect(subject.data_collection.stack_frame_variables).to eq(false)
17+
expect(subject.data_collection.frame_context_lines).to eq(3)
18+
end
19+
20+
it "uses the send_default_pii=true defaults when enabled later" do
21+
configuration = Sentry::Configuration.new do |config|
22+
config.send_default_pii = true
23+
end
24+
25+
expect(configuration.data_collection.user_info).to eq(true)
26+
expect(configuration.data_collection.cookies.mode).to eq(:deny_list)
27+
end
28+
29+
it "allows explicit data collection configuration after enabling send_default_pii" do
30+
configuration = Sentry::Configuration.new do |config|
31+
config.send_default_pii = true
32+
config.data_collection.user_info = false
33+
end
34+
35+
expect(configuration.data_collection.user_info).to eq(false)
36+
end
37+
end
38+
39+
describe "deprecated configuration warnings" do
40+
it "warns when send_default_pii is enabled" do
41+
allow(subject).to receive(:log_warn)
42+
subject.send_default_pii = true
43+
44+
subject.send(:log_deprecations)
45+
46+
expect(subject).to have_received(:log_warn).with("`send_default_pii` is deprecated; use `data_collection` instead.")
47+
end
48+
49+
it "warns when include_local_variables is enabled" do
50+
allow(subject).to receive(:log_warn)
51+
subject.include_local_variables = true
52+
53+
subject.send(:log_deprecations)
54+
55+
expect(subject).to have_received(:log_warn).with("`include_local_variables` is deprecated; use `data_collection.stack_frame_variables` instead.")
56+
end
57+
58+
it "warns when context_lines is changed" do
59+
allow(subject).to receive(:log_warn)
60+
subject.context_lines = 4
61+
62+
subject.send(:log_deprecations)
63+
64+
expect(subject).to have_received(:log_warn).with("`context_lines` is deprecated; use `data_collection.frame_context_lines` instead.")
65+
end
66+
end
67+
468
describe "#background_worker_threads" do
569
it "sets to have of the processors count" do
670
allow_any_instance_of(Sentry::Configuration).to receive(:processor_count).and_return(8)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe Sentry::DataCollection do
4+
subject(:data_collection) { described_class.new }
5+
6+
describe "defaults" do
7+
it "uses the defaults from the Data Collection specification" do
8+
expect(data_collection.user_info).to eq(true)
9+
expect(data_collection.cookies.mode).to eq(:deny_list)
10+
expect(data_collection.cookies.terms).to be_nil
11+
expect(data_collection.http_headers.request.mode).to eq(:deny_list)
12+
expect(data_collection.http_headers.request.terms).to be_nil
13+
expect(data_collection.http_headers.response.mode).to eq(:deny_list)
14+
expect(data_collection.http_headers.response.terms).to be_nil
15+
expect(data_collection.http_bodies).to be_nil
16+
expect(data_collection.url_query_params.mode).to eq(:deny_list)
17+
expect(data_collection.url_query_params.terms).to be_nil
18+
expect(data_collection.database_query_data).to eq(true)
19+
expect(data_collection.graphql.document).to eq(true)
20+
expect(data_collection.graphql.variables).to eq(true)
21+
expect(data_collection.queues).to eq(true)
22+
expect(data_collection.stack_frame_variables).to eq(false)
23+
expect(data_collection.frame_context_lines).to eq(3)
24+
end
25+
end
26+
27+
describe "constants" do
28+
it "defines the supported modes" do
29+
expect(described_class::MODES).to eq(%i[off deny_list allow_list])
30+
end
31+
32+
it "defines the supported body types" do
33+
expect(described_class::BODY_TYPES).to eq(
34+
%i[incoming_request outgoing_request incoming_response outgoing_response]
35+
)
36+
end
37+
end
38+
39+
describe "nested configuration objects" do
40+
it "supports configuring key-value collection modes and terms" do
41+
data_collection.cookies.mode = :allow_list
42+
data_collection.cookies.terms = ["page"]
43+
44+
expect(data_collection.cookies.mode).to eq(:allow_list)
45+
expect(data_collection.cookies.terms).to eq(["page"])
46+
end
47+
48+
it "supports configuring request and response headers independently" do
49+
data_collection.http_headers.request.mode = :off
50+
data_collection.http_headers.response.terms = ["x-request-id"]
51+
52+
expect(data_collection.http_headers.request.mode).to eq(:off)
53+
expect(data_collection.http_headers.response.terms).to eq(["x-request-id"])
54+
end
55+
56+
it "supports configuring GraphQL fields independently" do
57+
data_collection.graphql.document = false
58+
data_collection.graphql.variables = false
59+
60+
expect(data_collection.graphql.document).to eq(false)
61+
expect(data_collection.graphql.variables).to eq(false)
62+
end
63+
end
64+
end

0 commit comments

Comments
 (0)