sentry-ruby 5.23.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -1
  3. data/Gemfile +1 -3
  4. data/README.md +11 -6
  5. data/Rakefile +7 -11
  6. data/lib/sentry/background_worker.rb +2 -3
  7. data/lib/sentry/backpressure_monitor.rb +1 -1
  8. data/lib/sentry/breadcrumb.rb +5 -4
  9. data/lib/sentry/check_in_event.rb +2 -1
  10. data/lib/sentry/client.rb +84 -4
  11. data/lib/sentry/configuration.rb +63 -2
  12. data/lib/sentry/debug_structured_logger.rb +94 -0
  13. data/lib/sentry/dsn.rb +32 -0
  14. data/lib/sentry/envelope/item.rb +1 -1
  15. data/lib/sentry/event.rb +2 -1
  16. data/lib/sentry/hub.rb +13 -1
  17. data/lib/sentry/interfaces/request.rb +1 -1
  18. data/lib/sentry/log_event.rb +206 -0
  19. data/lib/sentry/log_event_buffer.rb +75 -0
  20. data/lib/sentry/metrics/aggregator.rb +1 -1
  21. data/lib/sentry/profiler.rb +3 -2
  22. data/lib/sentry/propagation_context.rb +59 -22
  23. data/lib/sentry/scope.rb +13 -3
  24. data/lib/sentry/session_flusher.rb +1 -1
  25. data/lib/sentry/span.rb +4 -3
  26. data/lib/sentry/std_lib_logger.rb +50 -0
  27. data/lib/sentry/structured_logger.rb +138 -0
  28. data/lib/sentry/test_helper.rb +29 -0
  29. data/lib/sentry/threaded_periodic_worker.rb +3 -3
  30. data/lib/sentry/transaction.rb +30 -8
  31. data/lib/sentry/transport/debug_transport.rb +70 -0
  32. data/lib/sentry/transport/dummy_transport.rb +1 -0
  33. data/lib/sentry/transport/http_transport.rb +9 -5
  34. data/lib/sentry/transport.rb +17 -8
  35. data/lib/sentry/utils/logging_helper.rb +10 -3
  36. data/lib/sentry/utils/sample_rand.rb +97 -0
  37. data/lib/sentry/utils/uuid.rb +13 -0
  38. data/lib/sentry/vernier/profiler.rb +3 -2
  39. data/lib/sentry/version.rb +1 -1
  40. data/lib/sentry-ruby.rb +67 -4
  41. metadata +17 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09ca0c97390e688d58572ffe3ae022523a1003a48ccea319857b302b3e1ebb95'
4
- data.tar.gz: 28f154fb38f21c50090dc8ae3c2bc7b335f288a298e97d934a24db1b8e7ac372
3
+ metadata.gz: b489d374f24123e3e62626462b08bfa3ea1d0a8c03f7332dd8d353860b065bdf
4
+ data.tar.gz: f6548949011e59234ce7b90c04f6a3cc95fb0876e93ac598907f6fac7ccd4560
5
5
  SHA512:
6
- metadata.gz: 5f170fe27327a1753f4a6f2d62b5a694f30c91f1f86c4e2caa837d70467298ea2af261475a2da1045e3513257f3a9be4bb66a0c2e0dba271e43a1478ecd42c0b
7
- data.tar.gz: 32a7f13e8eee4f7c8958dfd1a083aecf7ee306fec695703c5dbeff810e4ae5ebd84a1435e7edaef8a61d94eaf64910442422d2142007d77c5be31080ad32f244
6
+ metadata.gz: 0f02b67848b924a4fe9827ca5abfb3010035ebe41194d0859bd412b1effce07018c261fd59b1eb28efb9721452985ef6895efb4ba5fa297d82fc58c880d1c5ae
7
+ data.tar.gz: 1a8f620b58693aa0b30a81b9fdbdd94f78761c519fb49bbb9a57f6bb82296de8d248f3e537718ebb5325f730abac55da0ebd446db86d9e196f56532db1414097
data/.rspec CHANGED
@@ -1,2 +1,4 @@
1
- --format documentation
1
+ --require spec_helper
2
+ --format progress
2
3
  --color
4
+ --order rand
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
 
data/README.md CHANGED
@@ -15,12 +15,12 @@ Sentry SDK for Ruby
15
15
 
16
16
  | Current version | Build | Coverage | API doc |
17
17
  | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
18
- | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_ruby_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-ruby) |
19
- | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-rails) |
20
- | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_sidekiq_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-sidekiq) |
21
- | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_delayed_job_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-delayed_job) |
22
- | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_resque_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-resque) |
23
- | [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_opentelemetry_test.yml) | [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-opentelemetry) |
18
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-ruby?label=sentry-ruby)](https://rubygems.org/gems/sentry-ruby) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-ruby)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-ruby) |
19
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-rails?label=sentry-rails)](https://rubygems.org/gems/sentry-rails) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-rails)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-rails) |
20
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-sidekiq?label=sentry-sidekiq)](https://rubygems.org/gems/sentry-sidekiq) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-sidekiq)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-sidekiq) |
21
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-delayed_job)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-delayed_job) |
22
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-resque)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-resque) |
23
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-opentelemetry)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-opentelemetry) |
24
24
 
25
25
 
26
26
 
@@ -59,6 +59,8 @@ gem "sentry-opentelemetry"
59
59
 
60
60
  You need to use Sentry.init to initialize and configure your SDK:
61
61
  ```ruby
62
+ require "sentry-ruby"
63
+
62
64
  Sentry.init do |config|
63
65
  config.dsn = "MY_DSN"
64
66
  end
@@ -116,3 +118,6 @@ Thanks to everyone who has contributed to this project so far.
116
118
  <a href="https://github.com/getsentry/sentry-ruby/graphs/contributors">
117
119
  <img src="https://contributors-img.web.app/image?repo=getsentry/sentry-ruby" />
118
120
  </a>
121
+
122
+ > [!WARNING]
123
+ > Example and sample code in sentry-rails/examples and sentry-rails/spec/dummy is unmaintained. Sample code may contain security vulnerabilities, should never be used in production, and exists only for illustrative purposes.
data/Rakefile CHANGED
@@ -6,17 +6,13 @@ CLOBBER.include "pkg"
6
6
  require "bundler/gem_helper"
7
7
  Bundler::GemHelper.install_tasks(name: "sentry-ruby")
8
8
 
9
- require "rspec/core/rake_task"
9
+ require_relative "../lib/sentry/test/rake_tasks"
10
10
 
11
- RSpec::Core::RakeTask.new(:spec).tap do |task|
12
- task.rspec_opts = "--order rand"
13
- task.exclude_pattern = "spec/isolated/**/*_spec.rb"
14
- end
11
+ ISOLATED_SPECS = "spec/isolated/**/*_spec.rb"
15
12
 
16
- task :isolated_specs do
17
- Dir["spec/isolated/*"].each do |file|
18
- sh "bundle exec rspec #{file}"
19
- end
20
- end
13
+ Sentry::Test::RakeTasks.define_spec_tasks(
14
+ isolated_specs_pattern: ISOLATED_SPECS,
15
+ spec_exclude_pattern: ISOLATED_SPECS
16
+ )
21
17
 
22
- task default: [:spec, :isolated_specs]
18
+ task default: [:spec, :"spec:isolated"]
@@ -9,8 +9,7 @@ module Sentry
9
9
  include LoggingHelper
10
10
 
11
11
  attr_reader :max_queue, :number_of_threads
12
- # @deprecated Use Sentry.logger to retrieve the current logger instead.
13
- attr_reader :logger
12
+
14
13
  attr_accessor :shutdown_timeout
15
14
 
16
15
  DEFAULT_MAX_QUEUE = 30
@@ -19,7 +18,7 @@ module Sentry
19
18
  @shutdown_timeout = 1
20
19
  @number_of_threads = configuration.background_worker_threads
21
20
  @max_queue = configuration.background_worker_max_queue
22
- @logger = configuration.logger
21
+ @sdk_logger = configuration.sdk_logger
23
22
  @debug = configuration.debug
24
23
  @shutdown_callback = nil
25
24
 
@@ -6,7 +6,7 @@ module Sentry
6
6
  MAX_DOWNSAMPLE_FACTOR = 10
7
7
 
8
8
  def initialize(configuration, client, interval: DEFAULT_INTERVAL)
9
- super(configuration.logger, interval)
9
+ super(configuration.sdk_logger, interval)
10
10
  @client = client
11
11
 
12
12
  @healthy = true
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Sentry
4
4
  class Breadcrumb
5
+ MAX_NESTING = 10
5
6
  DATA_SERIALIZATION_ERROR_MESSAGE = "[data were removed due to serialization issues]"
6
7
 
7
8
  # @return [String, nil]
@@ -47,7 +48,7 @@ module Sentry
47
48
  # @param message [String]
48
49
  # @return [void]
49
50
  def message=(message)
50
- @message = message ? message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES) : ""
51
+ @message = message && Utils::EncodingHelper.valid_utf_8?(message) ? message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES) : ""
51
52
  end
52
53
 
53
54
  # @param level [String]
@@ -60,16 +61,16 @@ module Sentry
60
61
 
61
62
  def serialized_data
62
63
  begin
63
- ::JSON.parse(::JSON.generate(@data))
64
+ ::JSON.parse(::JSON.generate(@data, max_nesting: MAX_NESTING))
64
65
  rescue Exception => e
65
- Sentry.logger.debug(LOGGER_PROGNAME) do
66
+ Sentry.sdk_logger.debug(LOGGER_PROGNAME) do
66
67
  <<~MSG
67
68
  can't serialize breadcrumb data because of error: #{e}
68
69
  data: #{@data}
69
70
  MSG
70
71
  end
71
72
 
72
- DATA_SERIALIZATION_ERROR_MESSAGE
73
+ { error: DATA_SERIALIZATION_ERROR_MESSAGE }
73
74
  end
74
75
  end
75
76
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "securerandom"
4
4
  require "sentry/cron/monitor_config"
5
+ require "sentry/utils/uuid"
5
6
 
6
7
  module Sentry
7
8
  class CheckInEvent < Event
@@ -43,7 +44,7 @@ module Sentry
43
44
  self.status = status
44
45
  self.duration = duration
45
46
  self.monitor_config = monitor_config
46
- self.check_in_id = check_in_id || SecureRandom.uuid.delete("-")
47
+ self.check_in_id = check_in_id || Utils.uuid
47
48
  end
48
49
 
49
50
  # @return [Hash]
data/lib/sentry/client.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sentry/transport"
4
+ require "sentry/log_event"
5
+ require "sentry/log_event_buffer"
6
+ require "sentry/utils/uuid"
4
7
 
5
8
  module Sentry
6
9
  class Client
@@ -14,16 +17,16 @@ module Sentry
14
17
  # @return [SpotlightTransport, nil]
15
18
  attr_reader :spotlight_transport
16
19
 
20
+ # @!visibility private
21
+ attr_reader :log_event_buffer
22
+
17
23
  # @!macro configuration
18
24
  attr_reader :configuration
19
25
 
20
- # @deprecated Use Sentry.logger to retrieve the current logger instead.
21
- attr_reader :logger
22
-
23
26
  # @param configuration [Configuration]
24
27
  def initialize(configuration)
25
28
  @configuration = configuration
26
- @logger = configuration.logger
29
+ @sdk_logger = configuration.sdk_logger
27
30
 
28
31
  if transport_class = configuration.transport.transport_class
29
32
  @transport = transport_class.new(configuration)
@@ -38,6 +41,10 @@ module Sentry
38
41
  end
39
42
 
40
43
  @spotlight_transport = SpotlightTransport.new(configuration) if configuration.spotlight
44
+
45
+ if configuration.enable_logs
46
+ @log_event_buffer = LogEventBuffer.new(configuration, self).start
47
+ end
41
48
  end
42
49
 
43
50
  # Applies the given scope's data to the event and sends it to Sentry.
@@ -88,6 +95,15 @@ module Sentry
88
95
  nil
89
96
  end
90
97
 
98
+ # Buffer a log event to be sent later with other logs in a single envelope
99
+ # @param event [LogEvent] the log event to be buffered
100
+ # @return [LogEvent]
101
+ def buffer_log_event(event, scope)
102
+ return unless event.is_a?(LogEvent)
103
+ @log_event_buffer.add_event(scope.apply_to_event(event))
104
+ event
105
+ end
106
+
91
107
  # Capture an envelope directly.
92
108
  # @param envelope [Envelope] the envelope to be captured.
93
109
  # @return [void]
@@ -100,6 +116,7 @@ module Sentry
100
116
  def flush
101
117
  transport.flush if configuration.sending_to_dsn_allowed?
102
118
  spotlight_transport.flush if spotlight_transport
119
+ @log_event_buffer&.flush
103
120
  end
104
121
 
105
122
  # Initializes an Event object with the given exception. Returns `nil` if the exception's class is excluded from reporting.
@@ -167,6 +184,22 @@ module Sentry
167
184
  )
168
185
  end
169
186
 
187
+ # Initializes a LogEvent object with the given message and options
188
+ #
189
+ # @param message [String] the log message
190
+ # @param level [Symbol] the log level (:trace, :debug, :info, :warn, :error, :fatal)
191
+ # @param options [Hash] additional options
192
+ # @option options [Array] :parameters Array of values to replace template tokens in the message
193
+ #
194
+ # @return [LogEvent] the created log event
195
+ def event_from_log(message, level:, **options)
196
+ return unless configuration.sending_allowed?
197
+
198
+ attributes = options.reject { |k, _| k == :level || k == :severity }
199
+
200
+ LogEvent.new(level: level, body: message, attributes: attributes)
201
+ end
202
+
170
203
  # Initializes an Event object with the given Transaction object.
171
204
  # @param transaction [Transaction] the transaction to be recorded.
172
205
  # @return [TransactionEvent]
@@ -237,6 +270,53 @@ module Sentry
237
270
  raise
238
271
  end
239
272
 
273
+ # Send an envelope with batched logs
274
+ # @param log_events [Array<LogEvent>] the log events to be sent
275
+ # @api private
276
+ # @return [void]
277
+ def send_logs(log_events)
278
+ envelope = Envelope.new(
279
+ event_id: Sentry::Utils.uuid,
280
+ sent_at: Sentry.utc_now.iso8601,
281
+ dsn: configuration.dsn,
282
+ sdk: Sentry.sdk_meta
283
+ )
284
+
285
+ discarded_count = 0
286
+ envelope_items = []
287
+
288
+ if configuration.before_send_log
289
+ log_events.each do |log_event|
290
+ processed_log_event = configuration.before_send_log.call(log_event)
291
+
292
+ if processed_log_event
293
+ envelope_items << processed_log_event.to_hash
294
+ else
295
+ discarded_count += 1
296
+ end
297
+ end
298
+
299
+ envelope_items
300
+ else
301
+ envelope_items = log_events.map(&:to_hash)
302
+ end
303
+
304
+ envelope.add_item(
305
+ {
306
+ type: "log",
307
+ item_count: envelope_items.size,
308
+ content_type: "application/vnd.sentry.items.log+json"
309
+ },
310
+ { items: envelope_items }
311
+ )
312
+
313
+ send_envelope(envelope)
314
+
315
+ unless discarded_count.zero?
316
+ transport.record_lost_event(:before_send, "log_item", num: discarded_count)
317
+ end
318
+ end
319
+
240
320
  # Send an envelope directly to Sentry.
241
321
  # @param envelope [Envelope] the envelope to be sent.
242
322
  # @return [void]
@@ -12,6 +12,9 @@ require "sentry/cron/configuration"
12
12
  require "sentry/metrics/configuration"
13
13
  require "sentry/linecache"
14
14
  require "sentry/interfaces/stacktrace_builder"
15
+ require "sentry/logger"
16
+ require "sentry/structured_logger"
17
+ require "sentry/log_event_buffer"
15
18
 
16
19
  module Sentry
17
20
  class Configuration
@@ -99,6 +102,15 @@ module Sentry
99
102
  # @return [Proc]
100
103
  attr_reader :before_send_transaction
101
104
 
105
+ # Optional Proc, called before sending an event to the server
106
+ # @example
107
+ # config.before_send_log = lambda do |log|
108
+ # log.attributes["sentry"] = true
109
+ # log
110
+ # end
111
+ # @return [Proc]
112
+ attr_accessor :before_send_log
113
+
102
114
  # An array of breadcrumbs loggers to be used. Available options are:
103
115
  # - :sentry_logger
104
116
  # - :http_logger
@@ -183,7 +195,24 @@ module Sentry
183
195
  # Logger used by Sentry. In Rails, this is the Rails logger, otherwise
184
196
  # Sentry provides its own Sentry::Logger.
185
197
  # @return [Logger]
186
- attr_accessor :logger
198
+ attr_accessor :sdk_logger
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
+
205
+ # @deprecated Use {#sdk_logger=} instead.
206
+ def logger=(logger)
207
+ warn "[sentry] `config.logger=` is deprecated. Please use `config.sdk_logger=` instead."
208
+ self.sdk_logger = logger
209
+ end
210
+
211
+ # @deprecated Use {#sdk_logger} instead.
212
+ def logger
213
+ warn "[sentry] `config.logger` is deprecated. Please use `config.sdk_logger` instead."
214
+ self.sdk_logger
215
+ end
187
216
 
188
217
  # Project directory root for in_app detection. Could be Rails root, etc.
189
218
  # Set automatically for Rails.
@@ -262,6 +291,14 @@ module Sentry
262
291
  # @return [Proc]
263
292
  attr_accessor :traces_sampler
264
293
 
294
+ # Enable Structured Logging
295
+ # @return [Boolean]
296
+ attr_accessor :enable_logs
297
+
298
+ # Structured logging configuration.
299
+ # @return [StructuredLoggingConfiguration]
300
+ attr_reader :structured_logging
301
+
265
302
  # Easier way to use performance tracing
266
303
  # If set to true, will set traces_sample_rate to 1.0
267
304
  # @deprecated It will be removed in the next major release.
@@ -307,6 +344,10 @@ module Sentry
307
344
  # @return [Array<Symbol>]
308
345
  attr_accessor :enabled_patches
309
346
 
347
+ # Maximum number of log events to buffer before sending
348
+ # @return [Integer]
349
+ attr_accessor :max_log_events
350
+
310
351
  # these are not config options
311
352
  # @!visibility private
312
353
  attr_reader :errors, :gem_specs
@@ -419,7 +460,7 @@ module Sentry
419
460
  self.excluded_exceptions = IGNORE_DEFAULT + PUMA_IGNORE_DEFAULT
420
461
  self.inspect_exception_causes_for_exclusion = true
421
462
  self.linecache = ::Sentry::LineCache.new
422
- self.logger = ::Sentry::Logger.new(STDOUT)
463
+ self.sdk_logger = ::Sentry::Logger.new(STDOUT)
423
464
  self.project_root = Dir.pwd
424
465
  self.propagate_traces = true
425
466
 
@@ -443,18 +484,23 @@ module Sentry
443
484
 
444
485
  self.before_send = nil
445
486
  self.before_send_transaction = nil
487
+ self.before_send_log = nil
446
488
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
447
489
  self.traces_sampler = nil
448
490
  self.enable_tracing = nil
491
+ self.enable_logs = false
449
492
 
450
493
  self.profiler_class = Sentry::Profiler
451
494
 
452
495
  @transport = Transport::Configuration.new
453
496
  @cron = Cron::Configuration.new
454
497
  @metrics = Metrics::Configuration.new
498
+ @structured_logging = StructuredLoggingConfiguration.new
455
499
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
456
500
 
457
501
  run_post_initialization_callbacks
502
+
503
+ self.max_log_events = LogEventBuffer::DEFAULT_MAX_EVENTS
458
504
  end
459
505
 
460
506
  def validate
@@ -749,4 +795,19 @@ module Sentry
749
795
  available_processor_count || Concurrent.processor_count
750
796
  end
751
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
752
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
@@ -15,7 +15,7 @@ module Sentry
15
15
  # rate limits and client reports use the data_category rather than envelope item type
16
16
  def self.data_category(type)
17
17
  case type
18
- when "session", "attachment", "transaction", "profile", "span" then type
18
+ when "session", "attachment", "transaction", "profile", "span", "log" then type
19
19
  when "sessions" then "session"
20
20
  when "check_in" then "monitor"
21
21
  when "statsd", "metric_meta" then "metric_bucket"
data/lib/sentry/event.rb CHANGED
@@ -7,6 +7,7 @@ require "sentry/backtrace"
7
7
  require "sentry/utils/real_ip"
8
8
  require "sentry/utils/request_id"
9
9
  require "sentry/utils/custom_inspection"
10
+ require "sentry/utils/uuid"
10
11
 
11
12
  module Sentry
12
13
  # This is an abstract class that defines the shared attributes of an event.
@@ -50,7 +51,7 @@ module Sentry
50
51
  # @param message [String, nil]
51
52
  def initialize(configuration:, integration_meta: nil, message: nil)
52
53
  # Set some simple default values
53
- @event_id = SecureRandom.uuid.delete("-")
54
+ @event_id = Utils.uuid
54
55
  @timestamp = Sentry.utc_now.iso8601
55
56
  @platform = :ruby
56
57
  @type = self.class::TYPE