sentry-ruby-core 5.1.1 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bfc28e6f57d57de669447d555889196617f72ce09624b343cd284375021f60f5
4
- data.tar.gz: 5a6af91521df30a18ac1d021b2e59cd9e871653f1325cf6d65346e6c1dc91d7e
3
+ metadata.gz: 03d233c6e1bd55ddc4044c7a23b6bc74047eec17a8c16d5f33f768065641259c
4
+ data.tar.gz: bec07f880bff192b6ef71aaefdf9dc9ff2ff072a539f94ed5c4fd0398ad1146a
5
5
  SHA512:
6
- metadata.gz: fbe80e4c07f972d3babbf8c26c31cd97da84e91d77e86628a6931f35d7e66c83fb48461838d1a3e402361ee78dd9a3b6dfe849833f08a4573328d0eb4b5e66e1
7
- data.tar.gz: 0f081f57f89207ece5279bd77fa89d311c3d516231421ccf60f4b9c232065a606d80eacc53ab460f12e087aa7b2fadfc4cdd4636863cb58261cc4e58a605ad2c
6
+ metadata.gz: c3d90a9b888db8a396e260dccf5df3a38d4f153c9bc2b31b92fc206009553fec2d3d0fe47bb1a871b001372fcba8013fee4e16b60405588db4ce3984f56d675e
7
+ data.tar.gz: 2ae1db7dee78487fec06b1cb1c6fb2dfa77852ae20b0c28b6c2f0e0b964f728fdea40a8261495439ef6e193182a0f7dc3196dc1bd75368abcf2d78852310361a
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  source "https://rubygems.org"
2
+ git_source(:github) { |name| "https://github.com/#{name}.git" }
2
3
 
3
4
  gem "sentry-ruby-core", path: "./"
4
5
  gem "sentry-ruby", path: "./"
data/README.md CHANGED
@@ -29,7 +29,7 @@ If you're using `sentry-raven`, we recommend you to migrate to this new SDK. You
29
29
 
30
30
  ## Requirements
31
31
 
32
- We test on Ruby 2.4, 2.5, 2.6, 2.7, and 3.0 at the latest patchlevel/teeny version. We also support JRuby 9.0.
32
+ We test on Ruby 2.4, 2.5, 2.6, 2.7, 3.0, and 3.1 at the latest patchlevel/teeny version. We also support JRuby 9.0.
33
33
 
34
34
  If you use self-hosted Sentry, please also make sure its version is above `20.6.0`.
35
35
 
@@ -29,7 +29,7 @@ module Sentry
29
29
  log_debug("config.background_worker_threads is set to 0, all events will be sent synchronously")
30
30
  Concurrent::ImmediateExecutor.new
31
31
  else
32
- log_debug("initialized a background worker with #{@number_of_threads} threads")
32
+ log_debug("Initializing the background worker with #{@number_of_threads} threads")
33
33
 
34
34
  executor = Concurrent::ThreadPoolExecutor.new(
35
35
  min_threads: 0,
@@ -59,6 +59,7 @@ module Sentry
59
59
  end
60
60
 
61
61
  def shutdown
62
+ log_debug("Shutting down background worker")
62
63
  @shutdown_callback&.call
63
64
  end
64
65
 
data/lib/sentry/client.rb CHANGED
@@ -80,9 +80,10 @@ module Sentry
80
80
 
81
81
  integration_meta = Sentry.integrations[hint[:integration]]
82
82
 
83
- Event.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
83
+ ErrorEvent.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
84
84
  event.add_exception_interface(exception)
85
85
  event.add_threads_interface(crashed: true)
86
+ event.level = :error
86
87
  end
87
88
  end
88
89
 
@@ -94,8 +95,9 @@ module Sentry
94
95
  return unless @configuration.sending_allowed?
95
96
 
96
97
  integration_meta = Sentry.integrations[hint[:integration]]
97
- event = Event.new(configuration: configuration, integration_meta: integration_meta, message: message)
98
+ event = ErrorEvent.new(configuration: configuration, integration_meta: integration_meta, message: message)
98
99
  event.add_threads_interface(backtrace: backtrace || caller)
100
+ event.level = :error
99
101
  event
100
102
  end
101
103
 
@@ -133,7 +135,7 @@ module Sentry
133
135
 
134
136
  event
135
137
  rescue => e
136
- loggable_event_type = (event_type || "event").capitalize
138
+ loggable_event_type = event_type.capitalize
137
139
  log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
138
140
 
139
141
  event_info = Event.get_log_message(event.to_hash)
@@ -174,8 +176,7 @@ module Sentry
174
176
  async_block.call(event_hash)
175
177
  end
176
178
  rescue => e
177
- loggable_event_type = event_hash["type"] || "event"
178
- log_error("Async #{loggable_event_type} sending failed", e, debug: configuration.debug)
179
+ log_error("Async #{event_hash["type"]} sending failed", e, debug: configuration.debug)
179
180
  send_event(event, hint)
180
181
  end
181
182
  end
@@ -207,6 +207,10 @@ module Sentry
207
207
  # @return [Boolean]
208
208
  attr_accessor :send_client_reports
209
209
 
210
+ # Track sessions in request/response cycles automatically
211
+ # @return [Boolean]
212
+ attr_accessor :auto_session_tracking
213
+
210
214
  # these are not config options
211
215
  # @!visibility private
212
216
  attr_reader :errors, :gem_specs
@@ -261,6 +265,7 @@ module Sentry
261
265
  self.send_default_pii = false
262
266
  self.skip_rake_integration = false
263
267
  self.send_client_reports = true
268
+ self.auto_session_tracking = true
264
269
  self.trusted_proxies = []
265
270
  self.dsn = ENV['SENTRY_DSN']
266
271
  self.server_name = server_name_from_env
@@ -34,10 +34,6 @@ module Sentry
34
34
  @items << Item.new(headers, payload)
35
35
  end
36
36
 
37
- def to_s
38
- [JSON.generate(@headers), *@items.map(&:to_s)].join("\n")
39
- end
40
-
41
37
  def item_types
42
38
  @items.map(&:type)
43
39
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # ErrorEvent represents error or normal message events.
5
+ class ErrorEvent < Event
6
+ # @return [ExceptionInterface]
7
+ attr_reader :exception
8
+
9
+ # @return [ThreadsInterface]
10
+ attr_reader :threads
11
+
12
+ # @return [Hash]
13
+ def to_hash
14
+ data = super
15
+ data[:threads] = threads.to_hash if threads
16
+ data[:exception] = exception.to_hash if exception
17
+ data
18
+ end
19
+
20
+ # @!visibility private
21
+ def add_threads_interface(backtrace: nil, **options)
22
+ @threads = ThreadsInterface.build(
23
+ backtrace: backtrace,
24
+ stacktrace_builder: @stacktrace_builder,
25
+ **options
26
+ )
27
+ end
28
+
29
+ # @!visibility private
30
+ def add_exception_interface(exception)
31
+ if exception.respond_to?(:sentry_context)
32
+ @extra.merge!(exception.sentry_context)
33
+ end
34
+
35
+ @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
36
+ end
37
+ end
38
+ end
data/lib/sentry/event.rb CHANGED
@@ -9,7 +9,10 @@ require 'sentry/utils/request_id'
9
9
  require 'sentry/utils/custom_inspection'
10
10
 
11
11
  module Sentry
12
+ # This is an abstract class that defines the shared attributes of an event.
13
+ # Please don't use it directly. The user-facing classes are its child classes.
12
14
  class Event
15
+ TYPE = "event"
13
16
  # These are readable attributes.
14
17
  SERIALIZEABLE_ATTRIBUTES = %i(
15
18
  event_id level timestamp
@@ -23,6 +26,7 @@ module Sentry
23
26
  WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)
24
27
 
25
28
  MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
29
+ MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 200
26
30
 
27
31
  SKIP_INSPECTION_ATTRIBUTES = [:@modules, :@stacktrace_builder, :@send_default_pii, :@trusted_proxies, :@rack_env_whitelist]
28
32
 
@@ -34,12 +38,6 @@ module Sentry
34
38
  # @return [RequestInterface]
35
39
  attr_reader :request
36
40
 
37
- # @return [ExceptionInterface]
38
- attr_reader :exception
39
-
40
- # @return [ThreadsInterface]
41
- attr_reader :threads
42
-
43
41
  # @param configuration [Configuration]
44
42
  # @param integration_meta [Hash, nil]
45
43
  # @param message [String, nil]
@@ -48,6 +46,7 @@ module Sentry
48
46
  @event_id = SecureRandom.uuid.delete("-")
49
47
  @timestamp = Sentry.utc_now.iso8601
50
48
  @platform = :ruby
49
+ @type = self.class::TYPE
51
50
  @sdk = integration_meta || Sentry.sdk_meta
52
51
 
53
52
  @user = {}
@@ -70,8 +69,6 @@ module Sentry
70
69
  @rack_env_whitelist = configuration.rack_env_whitelist
71
70
 
72
71
  @message = (message || "").byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
73
-
74
- self.level = :error
75
72
  end
76
73
 
77
74
  class << self
@@ -145,9 +142,6 @@ module Sentry
145
142
  data = serialize_attributes
146
143
  data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
147
144
  data[:request] = request.to_hash if request
148
- data[:exception] = exception.to_hash if exception
149
- data[:threads] = threads.to_hash if threads
150
-
151
145
  data
152
146
  end
153
147
 
@@ -156,24 +150,6 @@ module Sentry
156
150
  JSON.parse(JSON.generate(to_hash))
157
151
  end
158
152
 
159
- # @!visibility private
160
- def add_threads_interface(backtrace: nil, **options)
161
- @threads = ThreadsInterface.build(
162
- backtrace: backtrace,
163
- stacktrace_builder: @stacktrace_builder,
164
- **options
165
- )
166
- end
167
-
168
- # @!visibility private
169
- def add_exception_interface(exception)
170
- if exception.respond_to?(:sentry_context)
171
- @extra.merge!(exception.sentry_context)
172
- end
173
-
174
- @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
175
- end
176
-
177
153
  private
178
154
 
179
155
  def add_request_interface(env)
data/lib/sentry/hub.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "sentry/scope"
4
4
  require "sentry/client"
5
+ require "sentry/session"
5
6
 
6
7
  module Sentry
7
8
  class Hub
@@ -104,6 +105,8 @@ module Sentry
104
105
 
105
106
  return unless event
106
107
 
108
+ current_scope.session&.update_from_exception(event.exception)
109
+
107
110
  capture_event(event, **options, &block).tap do
108
111
  # mark the exception as captured so we can use this information to avoid duplicated capturing
109
112
  exception.instance_variable_set(Sentry::CAPTURED_SIGNATURE, true)
@@ -143,12 +146,11 @@ module Sentry
143
146
 
144
147
  event = current_client.capture_event(event, scope, hint)
145
148
 
146
-
147
149
  if event && configuration.debug
148
150
  configuration.log_debug(event.to_json_compatible)
149
151
  end
150
152
 
151
- @last_event_id = event&.event_id
153
+ @last_event_id = event&.event_id unless event.is_a?(Sentry::TransactionEvent)
152
154
  event
153
155
  end
154
156
 
@@ -175,6 +177,30 @@ module Sentry
175
177
  configuration.background_worker_threads = original_background_worker_threads
176
178
  end
177
179
 
180
+ def start_session
181
+ return unless current_scope
182
+ current_scope.set_session(Session.new)
183
+ end
184
+
185
+ def end_session
186
+ return unless current_scope
187
+ session = current_scope.session
188
+ current_scope.set_session(nil)
189
+
190
+ return unless session
191
+ session.close
192
+ Sentry.session_flusher.add_session(session)
193
+ end
194
+
195
+ def with_session_tracking(&block)
196
+ return yield unless configuration.auto_session_tracking
197
+
198
+ start_session
199
+ yield
200
+ ensure
201
+ end_session
202
+ end
203
+
178
204
  private
179
205
 
180
206
  def current_layer
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "set"
2
3
 
3
4
  module Sentry
4
5
  class ExceptionInterface < Interface
@@ -74,11 +74,11 @@ module Sentry
74
74
  end
75
75
 
76
76
  def start_sentry_span
77
- return unless Sentry.initialized? && transaction = Sentry.get_current_scope.get_transaction
77
+ return unless Sentry.initialized? && span = Sentry.get_current_scope.get_span
78
78
  return if from_sentry_sdk?
79
- return if transaction.sampled == false
79
+ return if span.sampled == false
80
80
 
81
- transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
81
+ span.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
82
82
  end
83
83
 
84
84
  def finish_sentry_span(sentry_span)
@@ -14,30 +14,32 @@ module Sentry
14
14
  Sentry.clone_hub_to_current_thread
15
15
 
16
16
  Sentry.with_scope do |scope|
17
- scope.clear_breadcrumbs
18
- scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
19
- scope.set_rack_env(env)
20
-
21
- transaction = start_transaction(env, scope)
22
- scope.set_span(transaction) if transaction
23
-
24
- begin
25
- response = @app.call(env)
26
- rescue Sentry::Error
27
- finish_transaction(transaction, 500)
28
- raise # Don't capture Sentry errors
29
- rescue Exception => e
30
- capture_exception(e)
31
- finish_transaction(transaction, 500)
32
- raise
17
+ Sentry.with_session_tracking do
18
+ scope.clear_breadcrumbs
19
+ scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
20
+ scope.set_rack_env(env)
21
+
22
+ transaction = start_transaction(env, scope)
23
+ scope.set_span(transaction) if transaction
24
+
25
+ begin
26
+ response = @app.call(env)
27
+ rescue Sentry::Error
28
+ finish_transaction(transaction, 500)
29
+ raise # Don't capture Sentry errors
30
+ rescue Exception => e
31
+ capture_exception(e)
32
+ finish_transaction(transaction, 500)
33
+ raise
34
+ end
35
+
36
+ exception = collect_exception(env)
37
+ capture_exception(exception) if exception
38
+
39
+ finish_transaction(transaction, response[0])
40
+
41
+ response
33
42
  end
34
-
35
- exception = collect_exception(env)
36
- capture_exception(exception) if exception
37
-
38
- finish_transaction(transaction, response[0])
39
-
40
- response
41
43
  end
42
44
  end
43
45
 
@@ -59,7 +61,7 @@ module Sentry
59
61
  sentry_trace = env["HTTP_SENTRY_TRACE"]
60
62
  options = { name: scope.transaction_name, op: transaction_op }
61
63
  transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
62
- Sentry.start_transaction(transaction: transaction, **options)
64
+ Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
63
65
  end
64
66
 
65
67
 
data/lib/sentry/redis.rb CHANGED
@@ -3,8 +3,8 @@
3
3
  module Sentry
4
4
  # @api private
5
5
  class Redis
6
- OP_NAME ||= "db.redis.command"
7
- LOGGER_NAME ||= :redis_logger
6
+ OP_NAME = "db.redis.command"
7
+ LOGGER_NAME = :redis_logger
8
8
 
9
9
  def initialize(commands, host, port, db)
10
10
  @commands, @host, @port, @db = commands, host, port, db
@@ -60,9 +60,11 @@ module Sentry
60
60
 
61
61
  def parsed_commands
62
62
  commands.map do |statement|
63
- command, key, *_values = statement
63
+ command, key, *arguments = statement
64
64
 
65
- { command: command.to_s.upcase, key: key }
65
+ { command: command.to_s.upcase, key: key }.tap do |command_set|
66
+ command_set[:arguments] = arguments.join(" ") if Sentry.configuration.send_default_pii
67
+ end
66
68
  end
67
69
  end
68
70
 
data/lib/sentry/scope.rb CHANGED
@@ -7,7 +7,7 @@ module Sentry
7
7
  class Scope
8
8
  include ArgumentCheckingHelper
9
9
 
10
- ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span]
10
+ ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span, :session]
11
11
 
12
12
  attr_reader(*ATTRIBUTES)
13
13
 
@@ -76,6 +76,7 @@ module Sentry
76
76
  copy.transaction_names = transaction_names.deep_dup
77
77
  copy.fingerprint = fingerprint.deep_dup
78
78
  copy.span = span.deep_dup
79
+ copy.session = session.deep_dup
79
80
  copy
80
81
  end
81
82
 
@@ -198,6 +199,13 @@ module Sentry
198
199
  @transaction_names << transaction_name
199
200
  end
200
201
 
202
+ # Sets the currently active session on the scope.
203
+ # @param session [Session, nil]
204
+ # @return [void]
205
+ def set_session(session)
206
+ @session = session
207
+ end
208
+
201
209
  # Returns current transaction name.
202
210
  # The "transaction" here does not refer to `Transaction` objects.
203
211
  # @return [String, nil]
@@ -251,6 +259,7 @@ module Sentry
251
259
  @event_processors = []
252
260
  @rack_env = {}
253
261
  @span = nil
262
+ @session = nil
254
263
  set_new_breadcrumb_buffer
255
264
  end
256
265
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class Session
5
+ attr_reader :started, :status
6
+
7
+ # TODO-neel add :crashed after adding handled mechanism
8
+ STATUSES = %i(ok errored exited)
9
+ AGGREGATE_STATUSES = %i(errored exited)
10
+
11
+ def initialize
12
+ @started = Sentry.utc_now
13
+ @status = :ok
14
+ end
15
+
16
+ # TODO-neel add :crashed after adding handled mechanism
17
+ def update_from_exception(_exception = nil)
18
+ @status = :errored
19
+ end
20
+
21
+ def close
22
+ @status = :exited if @status == :ok
23
+ end
24
+
25
+ # truncate seconds from the timestamp since we only care about
26
+ # minute level granularity for aggregation
27
+ def aggregation_key
28
+ Time.utc(started.year, started.month, started.day, started.hour, started.min)
29
+ end
30
+
31
+ def deep_dup
32
+ dup
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class SessionFlusher
5
+ include LoggingHelper
6
+
7
+ FLUSH_INTERVAL = 60
8
+
9
+ def initialize(configuration, client)
10
+ @thread = nil
11
+ @client = client
12
+ @pending_aggregates = {}
13
+ @release = configuration.release
14
+ @environment = configuration.environment
15
+ @logger = configuration.logger
16
+
17
+ log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
18
+ end
19
+
20
+ def flush
21
+ return if @pending_aggregates.empty?
22
+ envelope = pending_envelope
23
+
24
+ Sentry.background_worker.perform do
25
+ @client.transport.send_envelope(envelope)
26
+ end
27
+
28
+ @pending_aggregates = {}
29
+ end
30
+
31
+ def add_session(session)
32
+ return unless @release
33
+
34
+ ensure_thread
35
+
36
+ return unless Session::AGGREGATE_STATUSES.include?(session.status)
37
+ @pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
38
+ @pending_aggregates[session.aggregation_key][session.status] += 1
39
+ end
40
+
41
+ def kill
42
+ log_debug("Killing session flusher")
43
+ @thread&.kill
44
+ end
45
+
46
+ private
47
+
48
+ def init_aggregates(aggregation_key)
49
+ aggregates = { started: aggregation_key.iso8601 }
50
+ Session::AGGREGATE_STATUSES.each { |k| aggregates[k] = 0 }
51
+ aggregates
52
+ end
53
+
54
+ def pending_envelope
55
+ envelope = Envelope.new
56
+
57
+ header = { type: 'sessions' }
58
+ payload = { attrs: attrs, aggregates: @pending_aggregates.values }
59
+
60
+ envelope.add_item(header, payload)
61
+ envelope
62
+ end
63
+
64
+ def attrs
65
+ { release: @release, environment: @environment }
66
+ end
67
+
68
+ def ensure_thread
69
+ return if @thread&.alive?
70
+
71
+ @thread = Thread.new do
72
+ loop do
73
+ sleep(FLUSH_INTERVAL)
74
+ flush
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end
@@ -1,31 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
+ # TransactionEvent represents events that carry transaction data (type: "transaction").
4
5
  class TransactionEvent < Event
5
6
  TYPE = "transaction"
6
7
 
7
- SERIALIZEABLE_ATTRIBUTES = %i(
8
- event_id level timestamp start_timestamp
9
- release environment server_name modules
10
- user tags contexts extra
11
- transaction platform sdk type
12
- )
13
-
14
- WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp start_timestamp level)
15
-
16
- attr_writer(*WRITER_ATTRIBUTES)
17
- attr_reader(*SERIALIZEABLE_ATTRIBUTES)
18
-
19
8
  # @return [<Array[Span]>]
20
9
  attr_accessor :spans
21
10
 
22
- # @param configuration [Configuration]
23
- # @param integration_meta [Hash, nil]
24
- # @param message [String, nil]
25
- def initialize(configuration:, integration_meta: nil, message: nil)
26
- super
27
- @type = TYPE
28
- end
11
+ # @return [Float, nil]
12
+ attr_reader :start_timestamp
29
13
 
30
14
  # Sets the event's start_timestamp.
31
15
  # @param time [Time, Float]
@@ -38,6 +22,7 @@ module Sentry
38
22
  def to_hash
39
23
  data = super
40
24
  data[:spans] = @spans.map(&:to_hash) if @spans
25
+ data[:start_timestamp] = @start_timestamp
41
26
  data
42
27
  end
43
28
  end
@@ -2,15 +2,20 @@
2
2
 
3
3
  module Sentry
4
4
  class DummyTransport < Transport
5
- attr_accessor :events
5
+ attr_accessor :events, :envelopes
6
6
 
7
7
  def initialize(*)
8
8
  super
9
9
  @events = []
10
+ @envelopes = []
10
11
  end
11
12
 
12
13
  def send_event(event)
13
14
  @events << event
14
15
  end
16
+
17
+ def send_envelope(envelope)
18
+ @envelopes << envelope
19
+ end
15
20
  end
16
21
  end
@@ -130,7 +130,7 @@ module Sentry
130
130
  server = URI(@dsn.server)
131
131
 
132
132
  connection =
133
- if proxy = @transport_configuration.proxy
133
+ if proxy = normalize_proxy(@transport_configuration.proxy)
134
134
  ::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
135
135
  else
136
136
  ::Net::HTTP.new(server.hostname, server.port, nil)
@@ -148,6 +148,20 @@ module Sentry
148
148
  connection
149
149
  end
150
150
 
151
+ def normalize_proxy(proxy)
152
+ return proxy unless proxy
153
+
154
+ case proxy
155
+ when String
156
+ uri = URI(proxy)
157
+ { uri: uri, user: uri.user, password: uri.password }
158
+ when URI
159
+ { uri: proxy, user: proxy.user, password: proxy.password }
160
+ when Hash
161
+ proxy
162
+ end
163
+ end
164
+
151
165
  def ssl_configuration
152
166
  configuration = {
153
167
  verify: @transport_configuration.ssl_verification,
@@ -57,8 +57,48 @@ module Sentry
57
57
 
58
58
  return if envelope.items.empty?
59
59
 
60
- log_info("[Transport] Sending envelope with items [#{envelope.item_types.join(', ')}] #{envelope.event_id} to Sentry")
61
- send_data(envelope.to_s)
60
+ data, serialized_items = serialize_envelope(envelope)
61
+
62
+ if data
63
+ log_info("[Transport] Sending envelope with items [#{serialized_items.map(&:type).join(', ')}] #{envelope.event_id} to Sentry")
64
+ send_data(data)
65
+ end
66
+ end
67
+
68
+ def serialize_envelope(envelope)
69
+ serialized_items = []
70
+ serialized_results = []
71
+
72
+ envelope.items.each do |item|
73
+ result = item.to_s
74
+
75
+ if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE
76
+ if item.payload.key?(:breadcrumbs)
77
+ item.payload.delete(:breadcrumbs)
78
+ elsif item.payload.key?("breadcrumbs")
79
+ item.payload.delete("breadcrumbs")
80
+ end
81
+
82
+ result = item.to_s
83
+ end
84
+
85
+ if result.bytesize > Event::MAX_SERIALIZED_PAYLOAD_SIZE
86
+ size_breakdown = item.payload.map do |key, value|
87
+ "#{key}: #{JSON.generate(value).bytesize}"
88
+ end.join(", ")
89
+
90
+ log_debug("Envelope item [#{item.type}] is still oversized without breadcrumbs: {#{size_breakdown}}")
91
+
92
+ next
93
+ end
94
+
95
+ serialized_results << result
96
+ serialized_items << item
97
+ end
98
+
99
+ data = [JSON.generate(envelope.headers), *serialized_results].join("\n") unless serialized_results.empty?
100
+
101
+ [data, serialized_items]
62
102
  end
63
103
 
64
104
  def is_rate_limited?(item_type)
@@ -67,6 +107,8 @@ module Sentry
67
107
  case item_type
68
108
  when "transaction"
69
109
  @rate_limits["transaction"]
110
+ when "sessions"
111
+ @rate_limits["session"]
70
112
  else
71
113
  @rate_limits["error"]
72
114
  end
@@ -106,7 +148,7 @@ module Sentry
106
148
  # Convert to hash
107
149
  event_payload = event.to_hash
108
150
  event_id = event_payload[:event_id] || event_payload["event_id"]
109
- item_type = get_item_type(event_payload)
151
+ item_type = event_payload[:type] || event_payload["type"]
110
152
 
111
153
  envelope = Envelope.new(
112
154
  {
@@ -125,7 +167,6 @@ module Sentry
125
167
  client_report_headers, client_report_payload = fetch_pending_client_report
126
168
  envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
127
169
 
128
-
129
170
  envelope
130
171
  end
131
172
 
@@ -133,16 +174,11 @@ module Sentry
133
174
  return unless @send_client_reports
134
175
  return unless CLIENT_REPORT_REASONS.include?(reason)
135
176
 
136
- item_type ||= 'event'
137
177
  @discarded_events[[reason, item_type]] += 1
138
178
  end
139
179
 
140
180
  private
141
181
 
142
- def get_item_type(event_hash)
143
- event_hash[:type] || event_hash["type"] || "event"
144
- end
145
-
146
182
  def fetch_pending_client_report
147
183
  return nil unless @send_client_reports
148
184
  return nil if @last_client_report_sent > Time.now - CLIENT_REPORT_INTERVAL
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.1.1"
4
+ VERSION = "5.3.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -12,11 +12,13 @@ require "sentry/utils/logging_helper"
12
12
  require "sentry/configuration"
13
13
  require "sentry/logger"
14
14
  require "sentry/event"
15
+ require "sentry/error_event"
15
16
  require "sentry/transaction_event"
16
17
  require "sentry/span"
17
18
  require "sentry/transaction"
18
19
  require "sentry/hub"
19
20
  require "sentry/background_worker"
21
+ require "sentry/session_flusher"
20
22
 
21
23
  [
22
24
  "sentry/rake",
@@ -61,6 +63,10 @@ module Sentry
61
63
  # @return [BackgroundWorker]
62
64
  attr_accessor :background_worker
63
65
 
66
+ # @!attribute [r] session_flusher
67
+ # @return [SessionFlusher]
68
+ attr_reader :session_flusher
69
+
64
70
  ##### Patch Registration #####
65
71
 
66
72
  # @!visibility private
@@ -94,6 +100,14 @@ module Sentry
94
100
  # @param name [String] name of the integration
95
101
  # @param version [String] version of the integration
96
102
  def register_integration(name, version)
103
+ if initialized?
104
+ logger.warn(LOGGER_PROGNAME) do
105
+ <<~MSG
106
+ Integration '#{name}' is loaded after the SDK is initialized, which can cause unexpected behavior. Please make sure all integrations are loaded before SDK initialization.
107
+ MSG
108
+ end
109
+ end
110
+
97
111
  meta = { name: "sentry.ruby.#{name}", version: version }.freeze
98
112
  integrations[name.to_s] = meta
99
113
  end
@@ -189,11 +203,18 @@ module Sentry
189
203
  @main_hub = hub
190
204
  @background_worker = Sentry::BackgroundWorker.new(config)
191
205
 
206
+ @session_flusher = if config.auto_session_tracking
207
+ Sentry::SessionFlusher.new(config, client)
208
+ else
209
+ nil
210
+ end
211
+
192
212
  if config.capture_exception_frame_locals
193
213
  exception_locals_tp.enable
194
214
  end
195
215
 
196
216
  at_exit do
217
+ @session_flusher&.kill
197
218
  @background_worker.shutdown
198
219
  end
199
220
  end
@@ -310,6 +331,26 @@ module Sentry
310
331
  get_current_hub.with_scope(&block)
311
332
  end
312
333
 
334
+ # Wrap a given block with session tracking.
335
+ # Aggregate sessions in minutely buckets will be recorded
336
+ # around this block and flushed every minute.
337
+ #
338
+ # @example
339
+ # Sentry.with_session_tracking do
340
+ # a = 1 + 1 # new session recorded with :exited status
341
+ # end
342
+ #
343
+ # Sentry.with_session_tracking do
344
+ # 1 / 0
345
+ # rescue => e
346
+ # Sentry.capture_exception(e) # new session recorded with :errored status
347
+ # end
348
+ # @return [void]
349
+ def with_session_tracking(&block)
350
+ return yield unless initialized?
351
+ get_current_hub.with_session_tracking(&block)
352
+ end
353
+
313
354
  # Takes an exception and reports it to Sentry via the currently active hub.
314
355
  #
315
356
  # @yieldparam scope [Scope]
@@ -344,6 +385,40 @@ module Sentry
344
385
  get_current_hub.start_transaction(**options)
345
386
  end
346
387
 
388
+ # Records the block's execution as a child of the current span.
389
+ # If the current scope doesn't have a span, the block would still be executed but the yield param will be nil.
390
+ # @param attributes [Hash] attributes for the child span.
391
+ # @yieldparam child_span [Span, nil]
392
+ # @return yield result
393
+ #
394
+ # @example
395
+ # Sentry.with_child_span(op: "my operation") do |child_span|
396
+ # child_span.set_data(operation_data)
397
+ # child_span.set_description(operation_detail)
398
+ # # result will be returned
399
+ # end
400
+ #
401
+ def with_child_span(**attributes, &block)
402
+ current_span = get_current_scope.get_span
403
+
404
+ if current_span
405
+ result = nil
406
+
407
+ begin
408
+ current_span.with_child_span(**attributes) do |child_span|
409
+ get_current_scope.set_span(child_span)
410
+ result = yield(child_span)
411
+ end
412
+ ensure
413
+ get_current_scope.set_span(current_span)
414
+ end
415
+
416
+ result
417
+ else
418
+ yield(nil)
419
+ end
420
+ end
421
+
347
422
  # Returns the id of the lastly reported Sentry::Event.
348
423
  #
349
424
  # @return [String, nil]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.1
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-24 00:00:00.000000000 Z
11
+ date: 2022-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -56,6 +56,7 @@ files:
56
56
  - lib/sentry/core_ext/object/duplicable.rb
57
57
  - lib/sentry/dsn.rb
58
58
  - lib/sentry/envelope.rb
59
+ - lib/sentry/error_event.rb
59
60
  - lib/sentry/event.rb
60
61
  - lib/sentry/exceptions.rb
61
62
  - lib/sentry/hub.rb
@@ -76,6 +77,8 @@ files:
76
77
  - lib/sentry/redis.rb
77
78
  - lib/sentry/release_detector.rb
78
79
  - lib/sentry/scope.rb
80
+ - lib/sentry/session.rb
81
+ - lib/sentry/session_flusher.rb
79
82
  - lib/sentry/span.rb
80
83
  - lib/sentry/transaction.rb
81
84
  - lib/sentry/transaction_event.rb