sentry-ruby 5.26.0 → 5.27.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb03eb536d1060c3a097a1ff3726ae71c8ffbd0012580d75c491665e054ac7c1
4
- data.tar.gz: 65df577369b281f3e1d1d9335e78786a800947c2014b35608d88c863458f24ca
3
+ metadata.gz: b489d374f24123e3e62626462b08bfa3ea1d0a8c03f7332dd8d353860b065bdf
4
+ data.tar.gz: f6548949011e59234ce7b90c04f6a3cc95fb0876e93ac598907f6fac7ccd4560
5
5
  SHA512:
6
- metadata.gz: b4e398e02a8fe619afc8982760c290a168785c4557dca30767fbeb039032d7907135e84d0598a5ac90b7b1cfb8413ceda83ebe4647702ba5c10c613ff0543574
7
- data.tar.gz: 285a3e0f650a41bcb6cc4539f48c7efcb9bc3442f31c76f301089c4672e6d9c790f407dd9d53db4e216df4eacd9be019b5bc46e453437fa1614aa48555541c9a
6
+ metadata.gz: 0f02b67848b924a4fe9827ca5abfb3010035ebe41194d0859bd412b1effce07018c261fd59b1eb28efb9721452985ef6895efb4ba5fa297d82fc58c880d1c5ae
7
+ data.tar.gz: 1a8f620b58693aa0b30a81b9fdbdd94f78761c519fb49bbb9a57f6bb82296de8d248f3e537718ebb5325f730abac55da0ebd446db86d9e196f56532db1414097
data/Gemfile CHANGED
@@ -3,7 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
  git_source(:github) { |name| "https://github.com/#{name}.git" }
5
5
 
6
- eval_gemfile "../Gemfile"
6
+ eval_gemfile "../Gemfile.dev"
7
7
 
8
8
  gem "sentry-ruby", path: "./"
9
9
 
@@ -11,8 +11,6 @@ rack_version = ENV["RACK_VERSION"]
11
11
  rack_version = "3.0.0" if rack_version.nil?
12
12
  gem "rack", "~> #{Gem::Version.new(rack_version)}" unless rack_version == "0"
13
13
 
14
- gem "ostruct" if RUBY_VERSION >= "3.4"
15
-
16
14
  redis_rb_version = ENV.fetch("REDIS_RB_VERSION", "5.0")
17
15
  gem "redis", "~> #{redis_rb_version}"
18
16
 
@@ -13,6 +13,7 @@ require "sentry/metrics/configuration"
13
13
  require "sentry/linecache"
14
14
  require "sentry/interfaces/stacktrace_builder"
15
15
  require "sentry/logger"
16
+ require "sentry/structured_logger"
16
17
  require "sentry/log_event_buffer"
17
18
 
18
19
  module Sentry
@@ -196,6 +197,11 @@ module Sentry
196
197
  # @return [Logger]
197
198
  attr_accessor :sdk_logger
198
199
 
200
+ # File path for DebugTransport to log events to. If not set, defaults to a temporary file.
201
+ # This is useful for debugging and testing purposes.
202
+ # @return [String, nil]
203
+ attr_accessor :sdk_debug_transport_log_file
204
+
199
205
  # @deprecated Use {#sdk_logger=} instead.
200
206
  def logger=(logger)
201
207
  warn "[sentry] `config.logger=` is deprecated. Please use `config.sdk_logger=` instead."
@@ -289,6 +295,10 @@ module Sentry
289
295
  # @return [Boolean]
290
296
  attr_accessor :enable_logs
291
297
 
298
+ # Structured logging configuration.
299
+ # @return [StructuredLoggingConfiguration]
300
+ attr_reader :structured_logging
301
+
292
302
  # Easier way to use performance tracing
293
303
  # If set to true, will set traces_sample_rate to 1.0
294
304
  # @deprecated It will be removed in the next major release.
@@ -485,6 +495,7 @@ module Sentry
485
495
  @transport = Transport::Configuration.new
486
496
  @cron = Cron::Configuration.new
487
497
  @metrics = Metrics::Configuration.new
498
+ @structured_logging = StructuredLoggingConfiguration.new
488
499
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
489
500
 
490
501
  run_post_initialization_callbacks
@@ -784,4 +795,19 @@ module Sentry
784
795
  available_processor_count || Concurrent.processor_count
785
796
  end
786
797
  end
798
+
799
+ class StructuredLoggingConfiguration
800
+ # File path for DebugStructuredLogger to log events to
801
+ # @return [String, Pathname, nil]
802
+ attr_accessor :file_path
803
+
804
+ # The class to use as a structured logger.
805
+ # @return [Class]
806
+ attr_accessor :logger_class
807
+
808
+ def initialize
809
+ @file_path = nil
810
+ @logger_class = Sentry::StructuredLogger
811
+ end
812
+ end
787
813
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+ require "pathname"
6
+ require "delegate"
7
+
8
+ module Sentry
9
+ # DebugStructuredLogger is a logger that captures structured log events to a file for debugging purposes.
10
+ #
11
+ # It can optionally also send log events to Sentry via the normal structured logger if logging
12
+ # is enabled.
13
+ class DebugStructuredLogger < SimpleDelegator
14
+ DEFAULT_LOG_FILE_PATH = File.join("log", "sentry_debug_logs.log")
15
+
16
+ attr_reader :log_file, :backend
17
+
18
+ def initialize(configuration)
19
+ @log_file = initialize_log_file(
20
+ configuration.structured_logging.file_path || DEFAULT_LOG_FILE_PATH
21
+ )
22
+ @backend = initialize_backend(configuration)
23
+
24
+ super(@backend)
25
+ end
26
+
27
+ # Override all log level methods to capture events
28
+ %i[trace debug info warn error fatal].each do |level|
29
+ define_method(level) do |message, parameters = [], **attributes|
30
+ log_event = capture_log_event(level, message, parameters, **attributes)
31
+ backend.public_send(level, message, parameters, **attributes)
32
+ log_event
33
+ end
34
+ end
35
+
36
+ def log(level, message, parameters:, **attributes)
37
+ log_event = capture_log_event(level, message, parameters, **attributes)
38
+ backend.log(level, message, parameters: parameters, **attributes)
39
+ log_event
40
+ end
41
+
42
+ def capture_log_event(level, message, parameters, **attributes)
43
+ log_event_json = {
44
+ timestamp: Time.now.utc.iso8601,
45
+ level: level.to_s,
46
+ message: message,
47
+ parameters: parameters,
48
+ attributes: attributes
49
+ }
50
+
51
+ File.open(log_file, "a") { |file| file << JSON.dump(log_event_json) << "\n" }
52
+ log_event_json
53
+ end
54
+
55
+ def logged_events
56
+ File.readlines(log_file).map do |line|
57
+ JSON.parse(line)
58
+ end
59
+ end
60
+
61
+ def clear
62
+ File.write(log_file, "")
63
+ if backend.respond_to?(:config)
64
+ backend.config.sdk_logger.debug("DebugStructuredLogger: Cleared events from #{log_file}")
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ 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
77
+ end
78
+
79
+ def initialize_log_file(log_file_path)
80
+ log_file = Pathname(log_file_path)
81
+
82
+ FileUtils.mkdir_p(log_file.dirname) unless log_file.dirname.exist?
83
+
84
+ log_file
85
+ 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
+ end
94
+ end
data/lib/sentry/dsn.rb CHANGED
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "uri"
4
+ require "ipaddr"
5
+ require "resolv"
4
6
 
5
7
  module Sentry
6
8
  class DSN
7
9
  PORT_MAP = { "http" => 80, "https" => 443 }.freeze
8
10
  REQUIRED_ATTRIBUTES = %w[host path public_key project_id].freeze
11
+ LOCALHOST_NAMES = %w[localhost 127.0.0.1 ::1 [::1]].freeze
12
+ LOCALHOST_PATTERN = /\.local(host|domain)?$/i
9
13
 
10
14
  attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
11
15
 
@@ -49,5 +53,33 @@ module Sentry
49
53
  def envelope_endpoint
50
54
  "#{path}/api/#{project_id}/envelope/"
51
55
  end
56
+
57
+ def local?
58
+ @local ||= (localhost? || private_ip? || resolved_ips_private?)
59
+ end
60
+
61
+ def localhost?
62
+ LOCALHOST_NAMES.include?(host.downcase) || LOCALHOST_PATTERN.match?(host)
63
+ end
64
+
65
+ def private_ip?
66
+ @private_ip ||= begin
67
+ begin
68
+ IPAddr.new(host).private?
69
+ rescue IPAddr::InvalidAddressError
70
+ false
71
+ end
72
+ end
73
+ end
74
+
75
+ def resolved_ips_private?
76
+ @resolved_ips_private ||= begin
77
+ begin
78
+ Resolv.getaddresses(host).any? { |ip| IPAddr.new(ip).private? }
79
+ rescue Resolv::ResolvError, IPAddr::InvalidAddressError
80
+ false
81
+ end
82
+ end
83
+ end
52
84
  end
53
85
  end
data/lib/sentry/hub.rb CHANGED
@@ -120,7 +120,8 @@ module Sentry
120
120
 
121
121
  sampling_context = {
122
122
  transaction_context: transaction.to_hash,
123
- parent_sampled: transaction.parent_sampled
123
+ parent_sampled: transaction.parent_sampled,
124
+ parent_sample_rate: transaction.parent_sample_rate
124
125
  }
125
126
 
126
127
  sampling_context.merge!(custom_sampling_context)
@@ -357,6 +358,7 @@ module Sentry
357
358
  parent_span_id: propagation_context.parent_span_id,
358
359
  parent_sampled: propagation_context.parent_sampled,
359
360
  baggage: propagation_context.baggage,
361
+ sample_rand: propagation_context.sample_rand,
360
362
  **options
361
363
  )
362
364
  end
@@ -42,7 +42,7 @@ module Sentry
42
42
 
43
43
  attr_accessor :level, :body, :template, :attributes, :user
44
44
 
45
- attr_reader :configuration, *SERIALIZEABLE_ATTRIBUTES
45
+ attr_reader :configuration, *(SERIALIZEABLE_ATTRIBUTES - %i[level body attributes])
46
46
 
47
47
  SERIALIZERS = %i[
48
48
  attributes
@@ -3,15 +3,14 @@
3
3
  require "securerandom"
4
4
  require "sentry/baggage"
5
5
  require "sentry/utils/uuid"
6
+ require "sentry/utils/sample_rand"
6
7
 
7
8
  module Sentry
8
9
  class PropagationContext
9
10
  SENTRY_TRACE_REGEXP = Regexp.new(
10
- "^[ \t]*" + # whitespace
11
- "([0-9a-f]{32})?" + # trace_id
11
+ "\\A([0-9a-f]{32})?" + # trace_id
12
12
  "-?([0-9a-f]{16})?" + # span_id
13
- "-?([01])?" + # sampled
14
- "[ \t]*$" # whitespace
13
+ "-?([01])?\\z" # sampled
15
14
  )
16
15
 
17
16
  # An uuid that can be used to identify a trace.
@@ -33,6 +32,53 @@ module Sentry
33
32
  # Please use the #get_baggage method for interfacing outside this class.
34
33
  # @return [Baggage, nil]
35
34
  attr_reader :baggage
35
+ # The propagated random value used for sampling decisions.
36
+ # @return [Float, nil]
37
+ attr_reader :sample_rand
38
+
39
+ # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
40
+ #
41
+ # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
42
+ # @return [Array, nil]
43
+ def self.extract_sentry_trace(sentry_trace)
44
+ value = sentry_trace.to_s.strip
45
+ return if value.empty?
46
+
47
+ match = SENTRY_TRACE_REGEXP.match(value)
48
+ return if match.nil?
49
+
50
+ trace_id, parent_span_id, sampled_flag = match[1..3]
51
+ parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
52
+
53
+ [trace_id, parent_span_id, parent_sampled]
54
+ end
55
+
56
+ def self.extract_sample_rand_from_baggage(baggage, trace_id = nil)
57
+ return unless baggage&.items
58
+
59
+ sample_rand_str = baggage.items["sample_rand"]
60
+ return unless sample_rand_str
61
+
62
+ generator = Utils::SampleRand.new(trace_id: trace_id)
63
+ generator.generate_from_value(sample_rand_str)
64
+ end
65
+
66
+ def self.generate_sample_rand(baggage, trace_id, parent_sampled)
67
+ generator = Utils::SampleRand.new(trace_id: trace_id)
68
+
69
+ if baggage&.items && !parent_sampled.nil?
70
+ sample_rate_str = baggage.items["sample_rate"]
71
+ sample_rate = sample_rate_str&.to_f
72
+
73
+ if sample_rate && !parent_sampled.nil?
74
+ generator.generate_from_sampling_decision(parent_sampled, sample_rate)
75
+ else
76
+ generator.generate_from_trace_id
77
+ end
78
+ else
79
+ generator.generate_from_trace_id
80
+ end
81
+ end
36
82
 
37
83
  def initialize(scope, env = nil)
38
84
  @scope = scope
@@ -40,6 +86,7 @@ module Sentry
40
86
  @parent_sampled = nil
41
87
  @baggage = nil
42
88
  @incoming_trace = false
89
+ @sample_rand = nil
43
90
 
44
91
  if env
45
92
  sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
@@ -61,6 +108,8 @@ module Sentry
61
108
  Baggage.new({})
62
109
  end
63
110
 
111
+ @sample_rand = self.class.extract_sample_rand_from_baggage(@baggage, @trace_id)
112
+
64
113
  @baggage.freeze!
65
114
  @incoming_trace = true
66
115
  end
@@ -69,20 +118,7 @@ module Sentry
69
118
 
70
119
  @trace_id ||= Utils.uuid
71
120
  @span_id = Utils.uuid.slice(0, 16)
72
- end
73
-
74
- # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
75
- #
76
- # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
77
- # @return [Array, nil]
78
- def self.extract_sentry_trace(sentry_trace)
79
- match = SENTRY_TRACE_REGEXP.match(sentry_trace)
80
- return nil if match.nil?
81
-
82
- trace_id, parent_span_id, sampled_flag = match[1..3]
83
- parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
84
-
85
- [trace_id, parent_span_id, parent_sampled]
121
+ @sample_rand ||= self.class.generate_sample_rand(@baggage, @trace_id, @parent_sampled)
86
122
  end
87
123
 
88
124
  # Returns the trace context that can be used to embed in an Event.
@@ -123,6 +159,7 @@ module Sentry
123
159
 
124
160
  items = {
125
161
  "trace_id" => trace_id,
162
+ "sample_rand" => Utils::SampleRand.format(@sample_rand),
126
163
  "environment" => configuration.environment,
127
164
  "release" => configuration.release,
128
165
  "public_key" => configuration.dsn&.public_key
@@ -2,8 +2,13 @@
2
2
 
3
3
  module Sentry
4
4
  module TestHelper
5
+ module_function
6
+
5
7
  DUMMY_DSN = "http://12345:67890@sentry.localdomain/sentry/42"
6
8
 
9
+ # Not really real, but it will be resolved as a non-local for testing needs
10
+ REAL_DSN = "https://user:pass@getsentry.io/project/42"
11
+
7
12
  # Alters the existing SDK configuration with test-suitable options. Mainly:
8
13
  # - Sets a dummy DSN instead of `nil` or an actual DSN.
9
14
  # - Sets the transport to DummyTransport, which allows easy access to the captured events.
@@ -46,6 +51,8 @@ module Sentry
46
51
  def teardown_sentry_test
47
52
  return unless Sentry.initialized?
48
53
 
54
+ clear_sentry_events
55
+
49
56
  # pop testing layer created by `setup_sentry_test`
50
57
  # but keep the base layer to avoid nil-pointer errors
51
58
  # TODO: find a way to notify users if they somehow popped the test layer before calling this method
@@ -55,6 +62,21 @@ module Sentry
55
62
  Sentry::Scope.global_event_processors.clear
56
63
  end
57
64
 
65
+ def clear_sentry_events
66
+ return unless Sentry.initialized?
67
+
68
+ sentry_transport.clear if sentry_transport.respond_to?(:clear)
69
+
70
+ if Sentry.configuration.enable_logs && sentry_logger.respond_to?(:clear)
71
+ sentry_logger.clear
72
+ end
73
+ end
74
+
75
+ # @return [Sentry::StructuredLogger, Sentry::DebugStructuredLogger]
76
+ def sentry_logger
77
+ Sentry.logger
78
+ end
79
+
58
80
  # @return [Transport]
59
81
  def sentry_transport
60
82
  Sentry.get_current_client.transport
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "sentry/baggage"
4
4
  require "sentry/profiler"
5
+ require "sentry/utils/sample_rand"
5
6
  require "sentry/propagation_context"
6
7
 
7
8
  module Sentry
@@ -57,12 +58,17 @@ module Sentry
57
58
  # @return [Profiler]
58
59
  attr_reader :profiler
59
60
 
61
+ # Sample rand value generated from trace_id
62
+ # @return [String]
63
+ attr_reader :sample_rand
64
+
60
65
  def initialize(
61
66
  hub:,
62
67
  name: nil,
63
68
  source: :custom,
64
69
  parent_sampled: nil,
65
70
  baggage: nil,
71
+ sample_rand: nil,
66
72
  **options
67
73
  )
68
74
  super(transaction: self, **options)
@@ -82,12 +88,18 @@ module Sentry
82
88
  @effective_sample_rate = nil
83
89
  @contexts = {}
84
90
  @measurements = {}
91
+ @sample_rand = sample_rand
85
92
 
86
93
  unless @hub.profiler_running?
87
94
  @profiler = @configuration.profiler_class.new(@configuration)
88
95
  end
89
96
 
90
97
  init_span_recorder
98
+
99
+ unless @sample_rand
100
+ generator = Utils::SampleRand.new(trace_id: @trace_id)
101
+ @sample_rand = generator.generate_from_trace_id
102
+ end
91
103
  end
92
104
 
93
105
  # @deprecated use Sentry.continue_trace instead.
@@ -123,12 +135,15 @@ module Sentry
123
135
 
124
136
  baggage.freeze!
125
137
 
138
+ sample_rand = extract_sample_rand_from_baggage(baggage, trace_id, parent_sampled)
139
+
126
140
  new(
127
141
  trace_id: trace_id,
128
142
  parent_span_id: parent_span_id,
129
143
  parent_sampled: parent_sampled,
130
144
  hub: hub,
131
145
  baggage: baggage,
146
+ sample_rand: sample_rand,
132
147
  **options
133
148
  )
134
149
  end
@@ -139,6 +154,11 @@ module Sentry
139
154
  PropagationContext.extract_sentry_trace(sentry_trace)
140
155
  end
141
156
 
157
+ def self.extract_sample_rand_from_baggage(baggage, trace_id, parent_sampled)
158
+ PropagationContext.extract_sample_rand_from_baggage(baggage, trace_id) ||
159
+ PropagationContext.generate_sample_rand(baggage, trace_id, parent_sampled)
160
+ end
161
+
142
162
  # @return [Hash]
143
163
  def to_hash
144
164
  hash = super
@@ -153,6 +173,13 @@ module Sentry
153
173
  hash
154
174
  end
155
175
 
176
+ def parent_sample_rate
177
+ return unless @baggage&.items
178
+
179
+ sample_rate_str = @baggage.items["sample_rate"]
180
+ sample_rate_str&.to_f
181
+ end
182
+
156
183
  # @return [Transaction]
157
184
  def deep_dup
158
185
  copy = super
@@ -225,7 +252,7 @@ module Sentry
225
252
  @effective_sample_rate /= 2**factor
226
253
  end
227
254
 
228
- @sampled = Random.rand < @effective_sample_rate
255
+ @sampled = @sample_rand < @effective_sample_rate
229
256
  end
230
257
 
231
258
  if @sampled
@@ -331,6 +358,7 @@ module Sentry
331
358
  items = {
332
359
  "trace_id" => trace_id,
333
360
  "sample_rate" => effective_sample_rate&.to_s,
361
+ "sample_rand" => Utils::SampleRand.format(@sample_rand),
334
362
  "sampled" => sampled&.to_s,
335
363
  "environment" => @environment,
336
364
  "release" => @release,
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+ require "pathname"
6
+ require "delegate"
7
+
8
+ module Sentry
9
+ # DebugTransport is a transport that logs events to a file for debugging purposes.
10
+ #
11
+ # It can optionally also send events to Sentry via HTTP transport if a real DSN
12
+ # is provided.
13
+ class DebugTransport < SimpleDelegator
14
+ DEFAULT_LOG_FILE_PATH = File.join("log", "sentry_debug_events.log")
15
+
16
+ attr_reader :log_file, :backend
17
+
18
+ def initialize(configuration)
19
+ @log_file = initialize_log_file(configuration)
20
+ @backend = initialize_backend(configuration)
21
+
22
+ super(@backend)
23
+ end
24
+
25
+ def send_event(event)
26
+ log_envelope(envelope_from_event(event))
27
+ backend.send_event(event)
28
+ end
29
+
30
+ def log_envelope(envelope)
31
+ envelope_json = {
32
+ timestamp: Time.now.utc.iso8601,
33
+ envelope_headers: envelope.headers,
34
+ items: envelope.items.map do |item|
35
+ { headers: item.headers, payload: item.payload }
36
+ end
37
+ }
38
+
39
+ File.open(log_file, "a") { |file| file << JSON.dump(envelope_json) << "\n" }
40
+ end
41
+
42
+ def logged_envelopes
43
+ return [] unless File.exist?(log_file)
44
+
45
+ File.readlines(log_file).map do |line|
46
+ JSON.parse(line)
47
+ end
48
+ end
49
+
50
+ def clear
51
+ File.write(log_file, "")
52
+ log_debug("DebugTransport: Cleared events from #{log_file}")
53
+ end
54
+
55
+ private
56
+
57
+ def initialize_backend(configuration)
58
+ backend = configuration.dsn.local? ? DummyTransport : HTTPTransport
59
+ backend.new(configuration)
60
+ end
61
+
62
+ def initialize_log_file(configuration)
63
+ log_file = Pathname(configuration.sdk_debug_transport_log_file || DEFAULT_LOG_FILE_PATH)
64
+
65
+ FileUtils.mkdir_p(log_file.dirname) unless log_file.dirname.exist?
66
+
67
+ log_file
68
+ end
69
+ end
70
+ end
@@ -12,6 +12,7 @@ module Sentry
12
12
 
13
13
  def send_event(event)
14
14
  @events << event
15
+ super
15
16
  end
16
17
 
17
18
  def send_envelope(envelope)
@@ -45,11 +45,7 @@ module Sentry
45
45
  auth_header = generate_auth_header
46
46
  headers["X-Sentry-Auth"] = auth_header if auth_header
47
47
 
48
- response = conn.start do |http|
49
- request = ::Net::HTTP::Post.new(endpoint, headers)
50
- request.body = data
51
- http.request(request)
52
- end
48
+ response = do_request(endpoint, headers, data)
53
49
 
54
50
  if response.code.match?(/\A2\d{2}/)
55
51
  handle_rate_limited_response(response) if has_rate_limited_header?(response)
@@ -111,6 +107,14 @@ module Sentry
111
107
  connection
112
108
  end
113
109
 
110
+ def do_request(endpoint, headers, body)
111
+ conn.start do |http|
112
+ request = ::Net::HTTP::Post.new(endpoint, headers)
113
+ request.body = body
114
+ http.request(request)
115
+ end
116
+ end
117
+
114
118
  private
115
119
 
116
120
  def has_rate_limited_header?(headers)
@@ -223,3 +223,4 @@ end
223
223
  require "sentry/transport/dummy_transport"
224
224
  require "sentry/transport/http_transport"
225
225
  require "sentry/transport/spotlight_transport"
226
+ require "sentry/transport/debug_transport"
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Utils
5
+ class SampleRand
6
+ PRECISION = 1_000_000.0
7
+ FORMAT_PRECISION = 6
8
+
9
+ attr_reader :trace_id
10
+
11
+ def self.valid?(value)
12
+ return false unless value
13
+ value >= 0.0 && value < 1.0
14
+ end
15
+
16
+ def self.format(value)
17
+ return unless value
18
+
19
+ truncated = (value * PRECISION).floor / PRECISION
20
+ "%.#{FORMAT_PRECISION}f" % truncated
21
+ end
22
+
23
+ def initialize(trace_id: nil)
24
+ @trace_id = trace_id
25
+ end
26
+
27
+ def generate_from_trace_id
28
+ (random_from_trace_id * PRECISION).floor / PRECISION
29
+ end
30
+
31
+ def generate_from_sampling_decision(sampled, sample_rate)
32
+ if invalid_sample_rate?(sample_rate)
33
+ fallback_generation
34
+ else
35
+ generate_based_on_sampling(sampled, sample_rate)
36
+ end
37
+ end
38
+
39
+ def generate_from_value(sample_rand_value)
40
+ parsed_value = parse_value(sample_rand_value)
41
+
42
+ if self.class.valid?(parsed_value)
43
+ parsed_value
44
+ else
45
+ fallback_generation
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def random_from_trace_id
52
+ if @trace_id
53
+ Random.new(@trace_id[0, 16].to_i(16))
54
+ else
55
+ Random.new
56
+ end.rand(1.0)
57
+ end
58
+
59
+ def invalid_sample_rate?(sample_rate)
60
+ sample_rate.nil? || sample_rate <= 0.0 || sample_rate > 1.0
61
+ end
62
+
63
+ def fallback_generation
64
+ if @trace_id
65
+ (random_from_trace_id * PRECISION).floor / PRECISION
66
+ else
67
+ format_random(Random.rand(1.0))
68
+ end
69
+ end
70
+
71
+ def generate_based_on_sampling(sampled, sample_rate)
72
+ random = random_from_trace_id
73
+
74
+ result = if sampled
75
+ random * sample_rate
76
+ elsif sample_rate == 1.0
77
+ random
78
+ else
79
+ sample_rate + random * (1.0 - sample_rate)
80
+ end
81
+
82
+ format_random(result)
83
+ end
84
+
85
+ def format_random(value)
86
+ truncated = (value * PRECISION).floor / PRECISION
87
+ ("%.#{FORMAT_PRECISION}f" % truncated).to_f
88
+ end
89
+
90
+ def parse_value(sample_rand_value)
91
+ Float(sample_rand_value)
92
+ rescue ArgumentError
93
+ nil
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.26.0"
4
+ VERSION = "5.27.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -10,8 +10,10 @@ require "sentry/core_ext/object/deep_dup"
10
10
  require "sentry/utils/argument_checking_helper"
11
11
  require "sentry/utils/encoding_helper"
12
12
  require "sentry/utils/logging_helper"
13
+ require "sentry/utils/sample_rand"
13
14
  require "sentry/configuration"
14
15
  require "sentry/structured_logger"
16
+ require "sentry/debug_structured_logger"
15
17
  require "sentry/event"
16
18
  require "sentry/error_event"
17
19
  require "sentry/transaction_event"
@@ -638,13 +640,15 @@ module Sentry
638
640
  @logger ||=
639
641
  if configuration.enable_logs
640
642
  # Initialize the public-facing Structured Logger if logs are enabled
641
- # This creates a StructuredLogger instance that implements Sentry's SDK telemetry logs protocol
643
+ # Use configured structured logger class or default to StructuredLogger
642
644
  # @see https://develop.sentry.dev/sdk/telemetry/logs/
643
- StructuredLogger.new(configuration)
645
+ configuration.structured_logging.logger_class.new(configuration)
644
646
  else
645
647
  warn <<~STR
646
648
  [sentry] `Sentry.logger` will no longer be used as internal SDK logger when `enable_logs` feature is turned on.
647
649
  Use Sentry.configuration.sdk_logger for SDK-specific logging needs."
650
+
651
+ Caller: #{caller.first}
648
652
  STR
649
653
 
650
654
  configuration.sdk_logger
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.26.0
4
+ version: 5.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
@@ -80,6 +80,7 @@ files:
80
80
  - lib/sentry/cron/monitor_check_ins.rb
81
81
  - lib/sentry/cron/monitor_config.rb
82
82
  - lib/sentry/cron/monitor_schedule.rb
83
+ - lib/sentry/debug_structured_logger.rb
83
84
  - lib/sentry/dsn.rb
84
85
  - lib/sentry/envelope.rb
85
86
  - lib/sentry/envelope/item.rb
@@ -137,6 +138,7 @@ files:
137
138
  - lib/sentry/transaction_event.rb
138
139
  - lib/sentry/transport.rb
139
140
  - lib/sentry/transport/configuration.rb
141
+ - lib/sentry/transport/debug_transport.rb
140
142
  - lib/sentry/transport/dummy_transport.rb
141
143
  - lib/sentry/transport/http_transport.rb
142
144
  - lib/sentry/transport/spotlight_transport.rb
@@ -149,21 +151,22 @@ files:
149
151
  - lib/sentry/utils/logging_helper.rb
150
152
  - lib/sentry/utils/real_ip.rb
151
153
  - lib/sentry/utils/request_id.rb
154
+ - lib/sentry/utils/sample_rand.rb
152
155
  - lib/sentry/utils/uuid.rb
153
156
  - lib/sentry/vernier/output.rb
154
157
  - lib/sentry/vernier/profiler.rb
155
158
  - lib/sentry/version.rb
156
159
  - sentry-ruby-core.gemspec
157
160
  - sentry-ruby.gemspec
158
- homepage: https://github.com/getsentry/sentry-ruby/tree/5.26.0/sentry-ruby
161
+ homepage: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-ruby
159
162
  licenses:
160
163
  - MIT
161
164
  metadata:
162
- homepage_uri: https://github.com/getsentry/sentry-ruby/tree/5.26.0/sentry-ruby
163
- source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.26.0/sentry-ruby
164
- changelog_uri: https://github.com/getsentry/sentry-ruby/blob/5.26.0/CHANGELOG.md
165
+ homepage_uri: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-ruby
166
+ source_code_uri: https://github.com/getsentry/sentry-ruby/tree/5.27.0/sentry-ruby
167
+ changelog_uri: https://github.com/getsentry/sentry-ruby/blob/5.27.0/CHANGELOG.md
165
168
  bug_tracker_uri: https://github.com/getsentry/sentry-ruby/issues
166
- documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/5.26.0
169
+ documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/5.27.0
167
170
  rdoc_options: []
168
171
  require_paths:
169
172
  - lib
@@ -178,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
181
  - !ruby/object:Gem::Version
179
182
  version: '0'
180
183
  requirements: []
181
- rubygems_version: 3.6.7
184
+ rubygems_version: 3.6.9
182
185
  specification_version: 4
183
186
  summary: A gem that provides a client interface for the Sentry error logger
184
187
  test_files: []