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
@@ -3,7 +3,8 @@
3
3
  require "concurrent/utility/processor_counter"
4
4
 
5
5
  require "sentry/utils/exception_cause_chain"
6
- require 'sentry/utils/custom_inspection'
6
+ require "sentry/utils/custom_inspection"
7
+ require "sentry/utils/env_helper"
7
8
  require "sentry/dsn"
8
9
  require "sentry/release_detector"
9
10
  require "sentry/transport/configuration"
@@ -11,6 +12,9 @@ require "sentry/cron/configuration"
11
12
  require "sentry/metrics/configuration"
12
13
  require "sentry/linecache"
13
14
  require "sentry/interfaces/stacktrace_builder"
15
+ require "sentry/logger"
16
+ require "sentry/structured_logger"
17
+ require "sentry/log_event_buffer"
14
18
 
15
19
  module Sentry
16
20
  class Configuration
@@ -22,6 +26,8 @@ module Sentry
22
26
  # have an `engines` dir at the root of your project, you may want
23
27
  # to set this to something like /(app|config|engines|lib)/
24
28
  #
29
+ # The default is value is /(bin|exe|app|config|lib|test|spec)/
30
+ #
25
31
  # @return [Regexp, nil]
26
32
  attr_accessor :app_dirs_pattern
27
33
 
@@ -96,6 +102,15 @@ module Sentry
96
102
  # @return [Proc]
97
103
  attr_reader :before_send_transaction
98
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
+
99
114
  # An array of breadcrumbs loggers to be used. Available options are:
100
115
  # - :sentry_logger
101
116
  # - :http_logger
@@ -180,13 +195,35 @@ module Sentry
180
195
  # Logger used by Sentry. In Rails, this is the Rails logger, otherwise
181
196
  # Sentry provides its own Sentry::Logger.
182
197
  # @return [Logger]
183
- 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
184
216
 
185
217
  # Project directory root for in_app detection. Could be Rails root, etc.
186
218
  # Set automatically for Rails.
187
219
  # @return [String]
188
220
  attr_accessor :project_root
189
221
 
222
+ # Whether to strip the load path while constructing the backtrace frame filename.
223
+ # Defaults to true.
224
+ # @return [Boolean]
225
+ attr_accessor :strip_backtrace_load_path
226
+
190
227
  # Insert sentry-trace to outgoing requests' headers
191
228
  # @return [Boolean]
192
229
  attr_accessor :propagate_traces
@@ -254,8 +291,17 @@ module Sentry
254
291
  # @return [Proc]
255
292
  attr_accessor :traces_sampler
256
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
+
257
302
  # Easier way to use performance tracing
258
303
  # If set to true, will set traces_sample_rate to 1.0
304
+ # @deprecated It will be removed in the next major release.
259
305
  # @return [Boolean, nil]
260
306
  attr_reader :enable_tracing
261
307
 
@@ -283,6 +329,10 @@ module Sentry
283
329
  # @return [Symbol]
284
330
  attr_reader :instrumenter
285
331
 
332
+ # The profiler class
333
+ # @return [Class]
334
+ attr_reader :profiler_class
335
+
286
336
  # Take a float between 0.0 and 1.0 as the sample rate for capturing profiles.
287
337
  # Note that this rate is relative to traces_sample_rate / traces_sampler,
288
338
  # i.e. the profile is sampled by this rate after the transaction is sampled.
@@ -294,6 +344,10 @@ module Sentry
294
344
  # @return [Array<Symbol>]
295
345
  attr_accessor :enabled_patches
296
346
 
347
+ # Maximum number of log events to buffer before sending
348
+ # @return [Integer]
349
+ attr_accessor :max_log_events
350
+
297
351
  # these are not config options
298
352
  # @!visibility private
299
353
  attr_reader :errors, :gem_specs
@@ -302,18 +356,18 @@ module Sentry
302
356
  # But they are mostly considered as noise and should be ignored by default
303
357
  # Please see https://github.com/getsentry/sentry-ruby/pull/2026 for more information
304
358
  PUMA_IGNORE_DEFAULT = [
305
- 'Puma::MiniSSL::SSLError',
306
- 'Puma::HttpParserError',
307
- 'Puma::HttpParserError501'
359
+ "Puma::MiniSSL::SSLError",
360
+ "Puma::HttpParserError",
361
+ "Puma::HttpParserError501"
308
362
  ].freeze
309
363
 
310
364
  # Most of these errors generate 4XX responses. In general, Sentry clients
311
365
  # only automatically report 5xx responses.
312
366
  IGNORE_DEFAULT = [
313
- 'Mongoid::Errors::DocumentNotFound',
314
- 'Rack::QueryParser::InvalidParameterError',
315
- 'Rack::QueryParser::ParameterTypeError',
316
- 'Sinatra::NotFound'
367
+ "Mongoid::Errors::DocumentNotFound",
368
+ "Rack::QueryParser::InvalidParameterError",
369
+ "Rack::QueryParser::ParameterTypeError",
370
+ "Sinatra::NotFound"
317
371
  ].freeze
318
372
 
319
373
  RACK_ENV_WHITELIST_DEFAULT = %w[
@@ -323,18 +377,20 @@ module Sentry
323
377
  ].freeze
324
378
 
325
379
  HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
326
- "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
380
+ "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`"
327
381
 
328
- LOG_PREFIX = "** [Sentry] ".freeze
329
- MODULE_SEPARATOR = "::".freeze
382
+ LOG_PREFIX = "** [Sentry] "
383
+ MODULE_SEPARATOR = "::"
330
384
  SKIP_INSPECTION_ATTRIBUTES = [:@linecache, :@stacktrace_builder]
331
385
 
332
386
  INSTRUMENTERS = [:sentry, :otel]
333
387
 
334
- PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
388
+ PROPAGATION_TARGETS_MATCH_ALL = /.*/
335
389
 
336
390
  DEFAULT_PATCHES = %i[redis puma http].freeze
337
391
 
392
+ APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/
393
+
338
394
  class << self
339
395
  # Post initialization callbacks are called at the end of initialization process
340
396
  # allowing extending the configuration of sentry-ruby by multiple extensions
@@ -344,16 +400,74 @@ module Sentry
344
400
 
345
401
  # allow extensions to add their hooks to the Configuration class
346
402
  def add_post_initialization_callback(&block)
347
- post_initialization_callbacks << block
403
+ callbacks[:initialize][:after] << block
404
+ end
405
+
406
+ def before(event, &block)
407
+ callbacks[event.to_sym][:before] << block
408
+ end
409
+
410
+ def after(event, &block)
411
+ callbacks[event.to_sym][:after] << block
412
+ end
413
+
414
+ # @!visibility private
415
+ def callbacks
416
+ @callbacks ||= {
417
+ initialize: { before: [], after: [] },
418
+ configured: { before: [], after: [] }
419
+ }
420
+ end
421
+
422
+ def validations
423
+ @validations ||= {}
424
+ end
425
+
426
+ def validate(attribute, optional: false, type: nil)
427
+ validations[attribute] = {
428
+ optional: optional,
429
+ type: type,
430
+ proc: build_validation_proc(optional, type)
431
+ }
432
+ end
433
+
434
+ private
435
+
436
+ def build_validation_proc(optional, type)
437
+ case type
438
+ when :numeric
439
+ ->(value) do
440
+ if optional && value.nil?
441
+ true
442
+ else
443
+ unless value.is_a?(Numeric)
444
+ message = "must be a Numeric"
445
+ message += " or nil" if optional
446
+
447
+ { error: message, value: value }
448
+ else
449
+ true
450
+ end
451
+ end
452
+ end
453
+ else
454
+ ->(value) { true }
455
+ end
348
456
  end
349
457
  end
350
458
 
459
+ validate :traces_sample_rate, optional: true, type: :numeric
460
+ validate :profiles_sample_rate, optional: true, type: :numeric
461
+
351
462
  def initialize
352
- self.app_dirs_pattern = nil
353
- self.debug = false
463
+ run_callbacks(:before, :initialize)
464
+
465
+ self.app_dirs_pattern = APP_DIRS_PATTERN
466
+ self.debug = Sentry::Utils::EnvHelper.env_to_bool(ENV["SENTRY_DEBUG"])
354
467
  self.background_worker_threads = (processor_count / 2.0).ceil
355
468
  self.background_worker_max_queue = BackgroundWorker::DEFAULT_MAX_QUEUE
356
469
  self.backtrace_cleanup_callback = nil
470
+ self.strip_backtrace_load_path = true
357
471
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
358
472
  self.breadcrumbs_logger = []
359
473
  self.context_lines = 3
@@ -364,7 +478,7 @@ module Sentry
364
478
  self.excluded_exceptions = IGNORE_DEFAULT + PUMA_IGNORE_DEFAULT
365
479
  self.inspect_exception_causes_for_exclusion = true
366
480
  self.linecache = ::Sentry::LineCache.new
367
- self.logger = ::Sentry::Logger.new(STDOUT)
481
+ self.sdk_logger = ::Sentry::Logger.new(STDOUT)
368
482
  self.project_root = Dir.pwd
369
483
  self.propagate_traces = true
370
484
 
@@ -376,8 +490,11 @@ module Sentry
376
490
  self.auto_session_tracking = true
377
491
  self.enable_backpressure_handling = false
378
492
  self.trusted_proxies = []
379
- self.dsn = ENV['SENTRY_DSN']
380
- self.spotlight = false
493
+ self.dsn = ENV["SENTRY_DSN"]
494
+
495
+ spotlight_env = ENV["SENTRY_SPOTLIGHT"]
496
+ spotlight_bool = Sentry::Utils::EnvHelper.env_to_bool(spotlight_env, strict: true)
497
+ self.spotlight = spotlight_bool.nil? ? (spotlight_env || false) : spotlight_bool
381
498
  self.server_name = server_name_from_env
382
499
  self.instrumenter = :sentry
383
500
  self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
@@ -385,16 +502,45 @@ module Sentry
385
502
 
386
503
  self.before_send = nil
387
504
  self.before_send_transaction = nil
505
+ self.before_send_log = nil
388
506
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
389
507
  self.traces_sampler = nil
390
508
  self.enable_tracing = nil
509
+ self.enable_logs = false
510
+
511
+ self.profiler_class = Sentry::Profiler
391
512
 
392
513
  @transport = Transport::Configuration.new
393
514
  @cron = Cron::Configuration.new
394
- @metrics = Metrics::Configuration.new
515
+ @metrics = Metrics::Configuration.new(self.sdk_logger)
516
+ @structured_logging = StructuredLoggingConfiguration.new
395
517
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
396
518
 
397
- run_post_initialization_callbacks
519
+ self.max_log_events = LogEventBuffer::DEFAULT_MAX_EVENTS
520
+
521
+ run_callbacks(:after, :initialize)
522
+
523
+ yield(self) if block_given?
524
+
525
+ run_callbacks(:after, :configured)
526
+ end
527
+
528
+ def validate
529
+ if profiler_class == Sentry::Profiler && profiles_sample_rate && !Sentry.dependency_installed?(:StackProf)
530
+ log_warn("Please add the 'stackprof' gem to your Gemfile to use the StackProf profiler with Sentry.")
531
+ end
532
+
533
+ if profiler_class == Sentry::Vernier::Profiler && profiles_sample_rate && !Sentry.dependency_installed?(:Vernier)
534
+ log_warn("Please add the 'vernier' gem to your Gemfile to use the Vernier profiler with Sentry.")
535
+ end
536
+
537
+ self.class.validations.each do |attribute, validation|
538
+ value = public_send(attribute)
539
+
540
+ next if (result = validation[:proc].call(value)) === true
541
+
542
+ raise ArgumentError, result[:error]
543
+ end
398
544
  end
399
545
 
400
546
  def dsn=(value)
@@ -465,25 +611,35 @@ module Sentry
465
611
  end
466
612
 
467
613
  def enable_tracing=(enable_tracing)
614
+ unless enable_tracing.nil?
615
+ log_warn <<~MSG
616
+ `enable_tracing` is now deprecated in favor of `traces_sample_rate = 1.0`.
617
+ MSG
618
+ end
619
+
468
620
  @enable_tracing = enable_tracing
469
621
  @traces_sample_rate ||= 1.0 if enable_tracing
470
622
  end
471
623
 
472
- def is_numeric_or_nil?(value)
473
- value.is_a?(Numeric) || value.nil?
474
- end
475
-
476
624
  def traces_sample_rate=(traces_sample_rate)
477
- raise ArgumentError, "traces_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(traces_sample_rate)
478
625
  @traces_sample_rate = traces_sample_rate
479
626
  end
480
627
 
481
628
  def profiles_sample_rate=(profiles_sample_rate)
482
- raise ArgumentError, "profiles_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(profiles_sample_rate)
483
- log_warn("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
484
629
  @profiles_sample_rate = profiles_sample_rate
485
630
  end
486
631
 
632
+ def profiler_class=(profiler_class)
633
+ if profiler_class == Sentry::Vernier::Profiler
634
+ begin
635
+ require "vernier"
636
+ rescue LoadError
637
+ end
638
+ end
639
+
640
+ @profiler_class = profiler_class
641
+ end
642
+
487
643
  def sending_allowed?
488
644
  spotlight || sending_to_dsn_allowed?
489
645
  end
@@ -555,7 +711,8 @@ module Sentry
555
711
  app_dirs_pattern: @app_dirs_pattern,
556
712
  linecache: @linecache,
557
713
  context_lines: @context_lines,
558
- backtrace_cleanup_callback: @backtrace_cleanup_callback
714
+ backtrace_cleanup_callback: @backtrace_cleanup_callback,
715
+ strip_backtrace_load_path: @strip_backtrace_load_path
559
716
  )
560
717
  end
561
718
 
@@ -632,12 +789,12 @@ module Sentry
632
789
  end
633
790
 
634
791
  def environment_from_env
635
- ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
792
+ ENV["SENTRY_CURRENT_ENV"] || ENV["SENTRY_ENVIRONMENT"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
636
793
  end
637
794
 
638
795
  def server_name_from_env
639
796
  if running_on_heroku?
640
- ENV['DYNO']
797
+ ENV["DYNO"]
641
798
  else
642
799
  # Try to resolve the hostname to an FQDN, but fall back to whatever
643
800
  # the load name is.
@@ -649,18 +806,30 @@ module Sentry
649
806
  File.directory?("/etc/heroku") && !ENV["CI"]
650
807
  end
651
808
 
652
- def run_post_initialization_callbacks
653
- self.class.post_initialization_callbacks.each do |hook|
809
+ def run_callbacks(hook, event)
810
+ self.class.callbacks[event][hook].each do |hook|
654
811
  instance_eval(&hook)
655
812
  end
656
813
  end
657
814
 
658
815
  def processor_count
659
- if Concurrent.respond_to?(:usable_processor_count)
660
- Concurrent.usable_processor_count
661
- else
662
- Concurrent.processor_count
663
- end
816
+ available_processor_count = Concurrent.available_processor_count if Concurrent.respond_to?(:available_processor_count)
817
+ available_processor_count || Concurrent.processor_count
818
+ end
819
+ end
820
+
821
+ class StructuredLoggingConfiguration
822
+ # File path for DebugStructuredLogger to log events to
823
+ # @return [String, Pathname, nil]
824
+ attr_accessor :file_path
825
+
826
+ # The class to use as a structured logger.
827
+ # @return [Class]
828
+ attr_accessor :logger_class
829
+
830
+ def initialize
831
+ @file_path = nil
832
+ @logger_class = Sentry::StructuredLogger
664
833
  end
665
834
  end
666
835
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  return if Object.method_defined?(:deep_dup)
4
4
 
5
- require 'sentry/core_ext/object/duplicable'
5
+ require "sentry/core_ext/object/duplicable"
6
6
 
7
7
  #########################################
8
8
  # This file was copied from Rails 5.2 #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Cron
3
5
  module MonitorCheckIns
@@ -12,12 +14,12 @@ module Sentry
12
14
  :in_progress,
13
15
  monitor_config: monitor_config)
14
16
 
15
- start = Sentry.utc_now.to_i
17
+ start = Metrics::Timing.duration_start
16
18
 
17
19
  begin
18
20
  # need to do this on ruby <= 2.6 sadly
19
21
  ret = method(:perform).super_method.arity == 0 ? super() : super
20
- duration = Sentry.utc_now.to_i - start
22
+ duration = Metrics::Timing.duration_end(start)
21
23
 
22
24
  Sentry.capture_check_in(slug,
23
25
  :ok,
@@ -27,7 +29,7 @@ module Sentry
27
29
 
28
30
  ret
29
31
  rescue Exception
30
- duration = Sentry.utc_now.to_i - start
32
+ duration = Metrics::Timing.duration_end(start)
31
33
 
32
34
  Sentry.capture_check_in(slug,
33
35
  :error,
@@ -57,7 +59,7 @@ module Sentry
57
59
 
58
60
  def sentry_monitor_slug(name: self.name)
59
61
  @sentry_monitor_slug ||= begin
60
- slug = name.gsub('::', '-').downcase
62
+ slug = name.gsub("::", "-").downcase
61
63
  slug[-MAX_SLUG_LENGTH..-1] || slug
62
64
  end
63
65
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sentry/cron/monitor_schedule'
3
+ require "sentry/cron/monitor_schedule"
4
4
 
5
5
  module Sentry
6
6
  module Cron
@@ -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
- PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
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
 
@@ -13,7 +17,7 @@ module Sentry
13
17
  @raw_value = dsn_string
14
18
 
15
19
  uri = URI.parse(dsn_string)
16
- uri_path = uri.path.split('/')
20
+ uri_path = uri.path.split("/")
17
21
 
18
22
  if uri.user
19
23
  # DSN-style string
@@ -25,7 +29,7 @@ module Sentry
25
29
  @scheme = uri.scheme
26
30
  @host = uri.host
27
31
  @port = uri.port if uri.port
28
- @path = uri_path.join('/')
32
+ @path = uri_path.join("/")
29
33
  end
30
34
 
31
35
  def valid?
@@ -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