sentry-ruby 5.18.2 → 5.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -1
  3. data/Gemfile +9 -3
  4. data/README.md +12 -7
  5. data/Rakefile +9 -11
  6. data/bin/console +1 -0
  7. data/lib/sentry/attachment.rb +40 -0
  8. data/lib/sentry/background_worker.rb +2 -3
  9. data/lib/sentry/backpressure_monitor.rb +1 -1
  10. data/lib/sentry/backtrace.rb +7 -8
  11. data/lib/sentry/baggage.rb +7 -7
  12. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  13. data/lib/sentry/breadcrumb.rb +5 -4
  14. data/lib/sentry/check_in_event.rb +5 -4
  15. data/lib/sentry/client.rb +121 -19
  16. data/lib/sentry/configuration.rb +207 -38
  17. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  18. data/lib/sentry/cron/monitor_check_ins.rb +6 -4
  19. data/lib/sentry/cron/monitor_config.rb +1 -1
  20. data/lib/sentry/debug_structured_logger.rb +94 -0
  21. data/lib/sentry/dsn.rb +35 -3
  22. data/lib/sentry/envelope/item.rb +88 -0
  23. data/lib/sentry/envelope.rb +2 -85
  24. data/lib/sentry/event.rb +13 -8
  25. data/lib/sentry/excon/middleware.rb +77 -0
  26. data/lib/sentry/excon.rb +10 -0
  27. data/lib/sentry/faraday.rb +77 -0
  28. data/lib/sentry/graphql.rb +1 -1
  29. data/lib/sentry/hub.rb +57 -4
  30. data/lib/sentry/interfaces/mechanism.rb +1 -1
  31. data/lib/sentry/interfaces/request.rb +6 -6
  32. data/lib/sentry/interfaces/single_exception.rb +3 -3
  33. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  34. data/lib/sentry/interfaces/stacktrace_builder.rb +15 -2
  35. data/lib/sentry/linecache.rb +3 -3
  36. data/lib/sentry/log_event.rb +219 -0
  37. data/lib/sentry/log_event_buffer.rb +75 -0
  38. data/lib/sentry/logger.rb +1 -1
  39. data/lib/sentry/metrics/aggregator.rb +13 -13
  40. data/lib/sentry/metrics/configuration.rb +12 -2
  41. data/lib/sentry/metrics/set_metric.rb +2 -2
  42. data/lib/sentry/metrics/timing.rb +8 -0
  43. data/lib/sentry/metrics.rb +27 -15
  44. data/lib/sentry/net/http.rb +17 -38
  45. data/lib/sentry/profiler/helpers.rb +46 -0
  46. data/lib/sentry/profiler.rb +27 -57
  47. data/lib/sentry/propagation_context.rb +60 -23
  48. data/lib/sentry/rack/capture_exceptions.rb +2 -2
  49. data/lib/sentry/rack.rb +2 -2
  50. data/lib/sentry/rake.rb +2 -2
  51. data/lib/sentry/release_detector.rb +4 -4
  52. data/lib/sentry/rspec.rb +91 -0
  53. data/lib/sentry/scope.rb +27 -2
  54. data/lib/sentry/session_flusher.rb +10 -6
  55. data/lib/sentry/span.rb +17 -4
  56. data/lib/sentry/std_lib_logger.rb +55 -0
  57. data/lib/sentry/structured_logger.rb +138 -0
  58. data/lib/sentry/test_helper.rb +45 -1
  59. data/lib/sentry/threaded_periodic_worker.rb +3 -3
  60. data/lib/sentry/transaction.rb +41 -13
  61. data/lib/sentry/transaction_event.rb +5 -3
  62. data/lib/sentry/transport/debug_transport.rb +70 -0
  63. data/lib/sentry/transport/dummy_transport.rb +1 -0
  64. data/lib/sentry/transport/http_transport.rb +21 -18
  65. data/lib/sentry/transport.rb +26 -11
  66. data/lib/sentry/utils/env_helper.rb +21 -0
  67. data/lib/sentry/utils/http_tracing.rb +74 -0
  68. data/lib/sentry/utils/logging_helper.rb +10 -3
  69. data/lib/sentry/utils/real_ip.rb +1 -1
  70. data/lib/sentry/utils/sample_rand.rb +97 -0
  71. data/lib/sentry/utils/uuid.rb +13 -0
  72. data/lib/sentry/vernier/output.rb +89 -0
  73. data/lib/sentry/vernier/profiler.rb +132 -0
  74. data/lib/sentry/version.rb +1 -1
  75. data/lib/sentry-ruby.rb +106 -12
  76. data/sentry-ruby-core.gemspec +3 -1
  77. data/sentry-ruby.gemspec +4 -2
  78. metadata +28 -12
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # Event type that represents a log entry with its attributes
5
+ #
6
+ # @see https://develop.sentry.dev/sdk/telemetry/logs/#log-envelope-item-payload
7
+ class LogEvent
8
+ TYPE = "log"
9
+
10
+ DEFAULT_PARAMETERS = [].freeze
11
+ DEFAULT_ATTRIBUTES = {}.freeze
12
+
13
+ SERIALIZEABLE_ATTRIBUTES = %i[
14
+ level
15
+ body
16
+ timestamp
17
+ environment
18
+ release
19
+ server_name
20
+ trace_id
21
+ attributes
22
+ contexts
23
+ ]
24
+
25
+ SENTRY_ATTRIBUTES = {
26
+ "sentry.trace.parent_span_id" => :parent_span_id,
27
+ "sentry.environment" => :environment,
28
+ "sentry.release" => :release,
29
+ "sentry.address" => :server_name,
30
+ "sentry.sdk.name" => :sdk_name,
31
+ "sentry.sdk.version" => :sdk_version,
32
+ "sentry.message.template" => :template,
33
+ "sentry.origin" => :origin
34
+ }
35
+
36
+ PARAMETER_PREFIX = "sentry.message.parameter"
37
+
38
+ USER_ATTRIBUTES = {
39
+ "user.id" => :user_id,
40
+ "user.name" => :user_username,
41
+ "user.email" => :user_email
42
+ }
43
+
44
+ LEVELS = %i[trace debug info warn error fatal].freeze
45
+
46
+ attr_accessor :level, :body, :template, :attributes, :user, :origin
47
+
48
+ attr_reader :configuration, *(SERIALIZEABLE_ATTRIBUTES - %i[level body attributes])
49
+
50
+ SERIALIZERS = %i[
51
+ attributes
52
+ body
53
+ level
54
+ parent_span_id
55
+ sdk_name
56
+ sdk_version
57
+ template
58
+ timestamp
59
+ trace_id
60
+ user_id
61
+ user_username
62
+ user_email
63
+ ].map { |name| [name, :"serialize_#{name}"] }.to_h
64
+
65
+ VALUE_TYPES = Hash.new("string").merge!({
66
+ TrueClass => "boolean",
67
+ FalseClass => "boolean",
68
+ Integer => "integer",
69
+ Float => "double"
70
+ }).freeze
71
+
72
+ TOKEN_REGEXP = /%\{(\w+)\}/
73
+
74
+ def initialize(configuration: Sentry.configuration, **options)
75
+ @configuration = configuration
76
+ @type = TYPE
77
+ @server_name = configuration.server_name
78
+ @environment = configuration.environment
79
+ @release = configuration.release
80
+ @timestamp = Sentry.utc_now
81
+ @level = options.fetch(:level)
82
+ @body = options[:body]
83
+ @template = @body if is_template?
84
+ @attributes = options[:attributes] || DEFAULT_ATTRIBUTES
85
+ @user = options[:user] || {}
86
+ @origin = options[:origin]
87
+ @contexts = {}
88
+ end
89
+
90
+ def to_hash
91
+ SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |name, memo|
92
+ memo[name] = serialize(name)
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def serialize(name)
99
+ serializer = SERIALIZERS[name]
100
+
101
+ if serializer
102
+ __send__(serializer)
103
+ else
104
+ public_send(name)
105
+ end
106
+ end
107
+
108
+ def serialize_level
109
+ level.to_s
110
+ end
111
+
112
+ def serialize_sdk_name
113
+ Sentry.sdk_meta["name"]
114
+ end
115
+
116
+ def serialize_sdk_version
117
+ Sentry.sdk_meta["version"]
118
+ end
119
+
120
+ def serialize_timestamp
121
+ timestamp.to_f
122
+ end
123
+
124
+ def serialize_trace_id
125
+ contexts.dig(:trace, :trace_id)
126
+ end
127
+
128
+ def serialize_parent_span_id
129
+ contexts.dig(:trace, :parent_span_id)
130
+ end
131
+
132
+ def serialize_body
133
+ if parameters.empty?
134
+ body
135
+ elsif parameters.is_a?(Hash)
136
+ body % parameters
137
+ else
138
+ sprintf(body, *parameters)
139
+ end
140
+ end
141
+
142
+ def serialize_user_id
143
+ user[:id]
144
+ end
145
+
146
+ def serialize_user_username
147
+ user[:username]
148
+ end
149
+
150
+ def serialize_user_email
151
+ user[:email]
152
+ end
153
+
154
+ def serialize_template
155
+ template if has_parameters?
156
+ end
157
+
158
+ def serialize_attributes
159
+ hash = {}
160
+
161
+ attributes.each do |key, value|
162
+ hash[key] = attribute_hash(value)
163
+ end
164
+
165
+ SENTRY_ATTRIBUTES.each do |key, name|
166
+ if (value = serialize(name))
167
+ hash[key] = attribute_hash(value)
168
+ end
169
+ end
170
+
171
+ USER_ATTRIBUTES.each do |key, name|
172
+ if (value = serialize(name))
173
+ hash[key] = value
174
+ end
175
+ end
176
+
177
+ hash
178
+ end
179
+
180
+ def attribute_hash(value)
181
+ { value: value, type: value_type(value) }
182
+ end
183
+
184
+ def value_type(value)
185
+ VALUE_TYPES[value.class]
186
+ end
187
+
188
+ def parameters
189
+ @parameters ||= begin
190
+ return DEFAULT_PARAMETERS unless template
191
+
192
+ parameters = template_tokens.empty? ?
193
+ attributes.fetch(:parameters, DEFAULT_PARAMETERS) : attributes.slice(*template_tokens)
194
+
195
+ if parameters.is_a?(Hash)
196
+ parameters.each do |key, value|
197
+ attributes["#{PARAMETER_PREFIX}.#{key}"] = value
198
+ end
199
+ else
200
+ parameters.each_with_index do |param, index|
201
+ attributes["#{PARAMETER_PREFIX}.#{index}"] = param
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ def template_tokens
208
+ @template_tokens ||= body.scan(TOKEN_REGEXP).flatten.map(&:to_sym)
209
+ end
210
+
211
+ def is_template?
212
+ body.include?("%s") || TOKEN_REGEXP.match?(body)
213
+ end
214
+
215
+ def has_parameters?
216
+ attributes.keys.any? { |key| key.start_with?(PARAMETER_PREFIX) }
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/threaded_periodic_worker"
4
+
5
+ module Sentry
6
+ # LogEventBuffer buffers log events and sends them to Sentry in a single envelope.
7
+ #
8
+ # This is used internally by the `Sentry::Client`.
9
+ #
10
+ # @!visibility private
11
+ class LogEventBuffer < ThreadedPeriodicWorker
12
+ FLUSH_INTERVAL = 5 # seconds
13
+ DEFAULT_MAX_EVENTS = 100
14
+
15
+ # @!visibility private
16
+ attr_reader :pending_events
17
+
18
+ def initialize(configuration, client)
19
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
20
+
21
+ @client = client
22
+ @pending_events = []
23
+ @max_events = configuration.max_log_events || DEFAULT_MAX_EVENTS
24
+ @mutex = Mutex.new
25
+
26
+ log_debug("[Logging] Initialized buffer with max_events=#{@max_events}, flush_interval=#{FLUSH_INTERVAL}s")
27
+ end
28
+
29
+ def start
30
+ ensure_thread
31
+ self
32
+ end
33
+
34
+ def flush
35
+ @mutex.synchronize do
36
+ return if empty?
37
+
38
+ log_debug("[LogEventBuffer] flushing #{size} log events")
39
+
40
+ send_events
41
+ end
42
+
43
+ log_debug("[LogEventBuffer] flushed #{size} log events")
44
+
45
+ self
46
+ end
47
+ alias_method :run, :flush
48
+
49
+ def add_event(event)
50
+ raise ArgumentError, "expected a LogEvent, got #{event.class}" unless event.is_a?(LogEvent)
51
+
52
+ @mutex.synchronize do
53
+ @pending_events << event
54
+ send_events if size >= @max_events
55
+ end
56
+
57
+ self
58
+ end
59
+
60
+ def empty?
61
+ @pending_events.empty?
62
+ end
63
+
64
+ def size
65
+ @pending_events.size
66
+ end
67
+
68
+ private
69
+
70
+ def send_events
71
+ @client.send_logs(@pending_events)
72
+ @pending_events.clear
73
+ end
74
+ end
75
+ end
data/lib/sentry/logger.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
3
+ require "logger"
4
4
 
5
5
  module Sentry
6
6
  class Logger < ::Logger
@@ -34,15 +34,15 @@ module Sentry
34
34
  attr_reader :client, :thread, :buckets, :flush_shift, :code_locations
35
35
 
36
36
  def initialize(configuration, client)
37
- super(configuration.logger, FLUSH_INTERVAL)
37
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
38
38
  @client = client
39
39
  @before_emit = configuration.metrics.before_emit
40
40
  @enable_code_locations = configuration.metrics.enable_code_locations
41
41
  @stacktrace_builder = configuration.stacktrace_builder
42
42
 
43
43
  @default_tags = {}
44
- @default_tags['release'] = configuration.release if configuration.release
45
- @default_tags['environment'] = configuration.environment if configuration.environment
44
+ @default_tags["release"] = configuration.release if configuration.release
45
+ @default_tags["environment"] = configuration.environment if configuration.environment
46
46
 
47
47
  @mutex = Mutex.new
48
48
 
@@ -59,7 +59,7 @@ module Sentry
59
59
  def add(type,
60
60
  key,
61
61
  value,
62
- unit: 'none',
62
+ unit: "none",
63
63
  tags: {},
64
64
  timestamp: nil,
65
65
  stacklevel: nil)
@@ -98,7 +98,7 @@ module Sentry
98
98
  unless flushable_buckets.empty?
99
99
  payload = serialize_buckets(flushable_buckets)
100
100
  envelope.add_item(
101
- { type: 'statsd', length: payload.bytesize },
101
+ { type: "statsd", length: payload.bytesize },
102
102
  payload
103
103
  )
104
104
  end
@@ -107,7 +107,7 @@ module Sentry
107
107
  code_locations.each do |timestamp, locations|
108
108
  payload = serialize_locations(timestamp, locations)
109
109
  envelope.add_item(
110
- { type: 'metric_meta', content_type: 'application/json' },
110
+ { type: "metric_meta", content_type: "application/json" },
111
111
  payload
112
112
  )
113
113
  end
@@ -161,8 +161,8 @@ module Sentry
161
161
  buckets.map do |timestamp, timestamp_buckets|
162
162
  timestamp_buckets.map do |metric_key, metric|
163
163
  type, key, unit, tags = metric_key
164
- values = metric.serialize.join(':')
165
- sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(',')
164
+ values = metric.serialize.join(":")
165
+ sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(",")
166
166
 
167
167
  "#{sanitize_key(key)}@#{sanitize_unit(unit)}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
168
168
  end
@@ -175,22 +175,22 @@ module Sentry
175
175
  mri = "#{type}:#{sanitize_key(key)}@#{sanitize_unit(unit)}"
176
176
 
177
177
  # note this needs to be an array but it really doesn't serve a purpose right now
178
- [mri, [location.merge(type: 'location')]]
178
+ [mri, [location.merge(type: "location")]]
179
179
  end.to_h
180
180
 
181
181
  { timestamp: timestamp, mapping: mapping }
182
182
  end
183
183
 
184
184
  def sanitize_key(key)
185
- key.gsub(KEY_SANITIZATION_REGEX, '_')
185
+ key.gsub(KEY_SANITIZATION_REGEX, "_")
186
186
  end
187
187
 
188
188
  def sanitize_unit(unit)
189
- unit.gsub(UNIT_SANITIZATION_REGEX, '')
189
+ unit.gsub(UNIT_SANITIZATION_REGEX, "")
190
190
  end
191
191
 
192
192
  def sanitize_tag_key(key)
193
- key.gsub(TAG_KEY_SANITIZATION_REGEX, '')
193
+ key.gsub(TAG_KEY_SANITIZATION_REGEX, "")
194
194
  end
195
195
 
196
196
  def sanitize_tag_value(value)
@@ -209,7 +209,7 @@ module Sentry
209
209
  updated_tags = @default_tags.merge(tags)
210
210
 
211
211
  transaction_name = get_transaction_name
212
- updated_tags['transaction'] = transaction_name if transaction_name
212
+ updated_tags["transaction"] = transaction_name if transaction_name
213
213
 
214
214
  updated_tags
215
215
  end
@@ -4,12 +4,13 @@ module Sentry
4
4
  module Metrics
5
5
  class Configuration
6
6
  include ArgumentCheckingHelper
7
+ include LoggingHelper
7
8
 
8
9
  # Enable metrics usage.
9
10
  # Starts a new {Sentry::Metrics::Aggregator} instance to aggregate metrics
10
11
  # and a thread to aggregate flush every 5 seconds.
11
12
  # @return [Boolean]
12
- attr_accessor :enabled
13
+ attr_reader :enabled
13
14
 
14
15
  # Enable code location reporting.
15
16
  # Will be sent once per day.
@@ -32,11 +33,20 @@ module Sentry
32
33
  # @return [Proc, nil]
33
34
  attr_reader :before_emit
34
35
 
35
- def initialize
36
+ def initialize(sdk_logger)
37
+ @sdk_logger = sdk_logger
36
38
  @enabled = false
37
39
  @enable_code_locations = true
38
40
  end
39
41
 
42
+ def enabled=(value)
43
+ log_warn <<~MSG
44
+ `config.metrics` is now deprecated and will be removed in the next major.
45
+ MSG
46
+
47
+ @enabled = value
48
+ end
49
+
40
50
  def before_emit=(value)
41
51
  check_callable!("metrics.before_emit", value)
42
52
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
- require 'zlib'
3
+ require "set"
4
+ require "zlib"
5
5
 
6
6
  module Sentry
7
7
  module Metrics
@@ -37,6 +37,14 @@ module Sentry
37
37
  def week
38
38
  Sentry.utc_now.to_i / (3600.0 * 24.0 * 7.0)
39
39
  end
40
+
41
+ def duration_start
42
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
43
+ end
44
+
45
+ def duration_end(start)
46
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
47
+ end
40
48
  end
41
49
  end
42
50
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sentry/metrics/metric'
4
- require 'sentry/metrics/counter_metric'
5
- require 'sentry/metrics/distribution_metric'
6
- require 'sentry/metrics/gauge_metric'
7
- require 'sentry/metrics/set_metric'
8
- require 'sentry/metrics/timing'
9
- require 'sentry/metrics/aggregator'
3
+ require "sentry/metrics/metric"
4
+ require "sentry/metrics/counter_metric"
5
+ require "sentry/metrics/distribution_metric"
6
+ require "sentry/metrics/gauge_metric"
7
+ require "sentry/metrics/set_metric"
8
+ require "sentry/metrics/timing"
9
+ require "sentry/metrics/aggregator"
10
10
 
11
11
  module Sentry
12
12
  module Metrics
@@ -14,32 +14,38 @@ module Sentry
14
14
  INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
15
15
  FRACTIONAL_UNITS = %w[ratio percent]
16
16
 
17
- OP_NAME = 'metric.timing'
18
- SPAN_ORIGIN = 'auto.metric.timing'
17
+ OP_NAME = "metric.timing"
18
+ SPAN_ORIGIN = "auto.metric.timing"
19
19
 
20
20
  class << self
21
- def increment(key, value = 1.0, unit: 'none', tags: {}, timestamp: nil)
21
+ def increment(key, value = 1.0, unit: "none", tags: {}, timestamp: nil)
22
+ log_deprecation
22
23
  Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
23
24
  end
24
25
 
25
- def distribution(key, value, unit: 'none', tags: {}, timestamp: nil)
26
+ def distribution(key, value, unit: "none", tags: {}, timestamp: nil)
27
+ log_deprecation
26
28
  Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
27
29
  end
28
30
 
29
- def set(key, value, unit: 'none', tags: {}, timestamp: nil)
31
+ def set(key, value, unit: "none", tags: {}, timestamp: nil)
32
+ log_deprecation
30
33
  Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
31
34
  end
32
35
 
33
- def gauge(key, value, unit: 'none', tags: {}, timestamp: nil)
36
+ def gauge(key, value, unit: "none", tags: {}, timestamp: nil)
37
+ log_deprecation
34
38
  Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
35
39
  end
36
40
 
37
- def timing(key, unit: 'second', tags: {}, timestamp: nil, &block)
41
+ def timing(key, unit: "second", tags: {}, timestamp: nil, &block)
42
+ log_deprecation
43
+
38
44
  return unless block_given?
39
45
  return yield unless DURATION_UNITS.include?(unit)
40
46
 
41
47
  result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
42
- tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(', ') : v.to_s) } if span
48
+ tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
43
49
 
44
50
  start = Timing.send(unit.to_sym)
45
51
  result = yield
@@ -51,6 +57,12 @@ module Sentry
51
57
  Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
52
58
  result
53
59
  end
60
+
61
+ def log_deprecation
62
+ Sentry.sdk_logger.warn(LOGGER_PROGNAME) do
63
+ "`Sentry::Metrics` is now deprecated and will be removed in the next major."
64
+ end
65
+ end
54
66
  end
55
67
  end
56
68
  end
@@ -2,14 +2,18 @@
2
2
 
3
3
  require "net/http"
4
4
  require "resolv"
5
+ require "sentry/utils/http_tracing"
5
6
 
6
7
  module Sentry
7
8
  # @api private
8
9
  module Net
9
10
  module HTTP
11
+ include Utils::HttpTracing
12
+
10
13
  OP_NAME = "http.client"
11
14
  SPAN_ORIGIN = "auto.http.net_http"
12
15
  BREADCRUMB_CATEGORY = "net.http"
16
+ URI_PARSER = URI.const_defined?("RFC2396_PARSER") ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
13
17
 
14
18
  # To explain how the entire thing works, we need to know how the original Net::HTTP#request works
15
19
  # Here's part of its definition. As you can see, it usually calls itself inside a #start block
@@ -21,8 +25,7 @@ module Sentry
21
25
  # req['connection'] ||= 'close'
22
26
  # return request(req, body, &block) # <- request will be called for the second time from the first call
23
27
  # }
24
- # end
25
- # # .....
28
+ # end # .....
26
29
  # end
27
30
  # ```
28
31
  #
@@ -34,44 +37,26 @@ module Sentry
34
37
  Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
35
38
  request_info = extract_request_info(req)
36
39
 
37
- if propagate_trace?(request_info[:url], Sentry.configuration)
40
+ if propagate_trace?(request_info[:url])
38
41
  set_propagation_headers(req)
39
42
  end
40
43
 
41
- super.tap do |res|
42
- record_sentry_breadcrumb(request_info, res)
44
+ res = super
45
+ response_status = res.code.to_i
43
46
 
44
- if sentry_span
45
- sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
46
- sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
47
- sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
48
- sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
49
- sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, res.code.to_i)
50
- end
47
+ if record_sentry_breadcrumb?
48
+ record_sentry_breadcrumb(request_info, response_status)
51
49
  end
52
- end
53
- end
54
50
 
55
- private
51
+ if sentry_span
52
+ set_span_info(sentry_span, request_info, response_status)
53
+ end
56
54
 
57
- def set_propagation_headers(req)
58
- Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
55
+ res
56
+ end
59
57
  end
60
58
 
61
- def record_sentry_breadcrumb(request_info, res)
62
- return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
63
-
64
- crumb = Sentry::Breadcrumb.new(
65
- level: :info,
66
- category: BREADCRUMB_CATEGORY,
67
- type: :info,
68
- data: {
69
- status: res.code.to_i,
70
- **request_info
71
- }
72
- )
73
- Sentry.add_breadcrumb(crumb)
74
- end
59
+ private
75
60
 
76
61
  def from_sentry_sdk?
77
62
  dsn = Sentry.configuration.dsn
@@ -82,7 +67,7 @@ module Sentry
82
67
  # IPv6 url could look like '::1/path', and that won't parse without
83
68
  # wrapping it in square brackets.
84
69
  hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
85
- uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}")
70
+ uri = req.uri || URI.parse(URI_PARSER.escape("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}"))
86
71
  url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
87
72
 
88
73
  result = { method: req.method, url: url }
@@ -94,12 +79,6 @@ module Sentry
94
79
 
95
80
  result
96
81
  end
97
-
98
- def propagate_trace?(url, configuration)
99
- url &&
100
- configuration.propagate_traces &&
101
- configuration.trace_propagation_targets.any? { |target| url.match?(target) }
102
- end
103
82
  end
104
83
  end
105
84
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Sentry
6
+ class Profiler
7
+ module Helpers
8
+ def in_app?(abs_path)
9
+ abs_path.match?(@in_app_pattern)
10
+ end
11
+
12
+ # copied from stacktrace.rb since I don't want to touch existing code
13
+ # TODO-neel-profiler try to fetch this from stackprof once we patch
14
+ # the native extension
15
+ def compute_filename(abs_path, in_app)
16
+ return nil if abs_path.nil?
17
+
18
+ under_project_root = @project_root && abs_path.start_with?(@project_root)
19
+
20
+ prefix =
21
+ if under_project_root && in_app
22
+ @project_root
23
+ else
24
+ longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
25
+
26
+ if under_project_root
27
+ longest_load_path || @project_root
28
+ else
29
+ longest_load_path
30
+ end
31
+ end
32
+
33
+ prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
34
+ end
35
+
36
+ def split_module(name)
37
+ # last module plus class/instance method
38
+ i = name.rindex("::")
39
+ function = i ? name[(i + 2)..-1] : name
40
+ mod = i ? name[0...i] : nil
41
+
42
+ [function, mod]
43
+ end
44
+ end
45
+ end
46
+ end