sentry-ruby 5.26.0 → 6.4.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +27 -5
  3. data/README.md +3 -3
  4. data/lib/sentry/background_worker.rb +1 -4
  5. data/lib/sentry/backtrace/line.rb +99 -0
  6. data/lib/sentry/backtrace.rb +44 -76
  7. data/lib/sentry/baggage.rb +1 -1
  8. data/lib/sentry/breadcrumb.rb +1 -1
  9. data/lib/sentry/breadcrumb_buffer.rb +2 -2
  10. data/lib/sentry/check_in_event.rb +2 -2
  11. data/lib/sentry/client.rb +59 -136
  12. data/lib/sentry/configuration.rb +176 -78
  13. data/lib/sentry/cron/monitor_check_ins.rb +3 -3
  14. data/lib/sentry/cron/monitor_config.rb +2 -2
  15. data/lib/sentry/cron/monitor_schedule.rb +2 -2
  16. data/lib/sentry/debug_structured_logger.rb +94 -0
  17. data/lib/sentry/dsn.rb +52 -0
  18. data/lib/sentry/envelope/item.rb +3 -3
  19. data/lib/sentry/error_event.rb +3 -3
  20. data/lib/sentry/event.rb +4 -10
  21. data/lib/sentry/graphql.rb +1 -1
  22. data/lib/sentry/hub.rb +29 -5
  23. data/lib/sentry/interface.rb +1 -1
  24. data/lib/sentry/interfaces/exception.rb +2 -2
  25. data/lib/sentry/interfaces/request.rb +2 -0
  26. data/lib/sentry/interfaces/single_exception.rb +4 -4
  27. data/lib/sentry/interfaces/stacktrace.rb +3 -3
  28. data/lib/sentry/interfaces/stacktrace_builder.rb +0 -8
  29. data/lib/sentry/interfaces/threads.rb +2 -2
  30. data/lib/sentry/log_event.rb +33 -138
  31. data/lib/sentry/log_event_buffer.rb +13 -60
  32. data/lib/sentry/metric_event.rb +49 -0
  33. data/lib/sentry/metric_event_buffer.rb +28 -0
  34. data/lib/sentry/metrics.rb +47 -42
  35. data/lib/sentry/profiler.rb +4 -5
  36. data/lib/sentry/propagation_context.rb +55 -18
  37. data/lib/sentry/rack/capture_exceptions.rb +88 -2
  38. data/lib/sentry/rspec.rb +1 -1
  39. data/lib/sentry/scope.rb +50 -18
  40. data/lib/sentry/sequel.rb +35 -0
  41. data/lib/sentry/span.rb +5 -17
  42. data/lib/sentry/std_lib_logger.rb +10 -1
  43. data/lib/sentry/telemetry_event_buffer.rb +130 -0
  44. data/lib/sentry/test_helper.rb +30 -0
  45. data/lib/sentry/transaction.rb +72 -95
  46. data/lib/sentry/transaction_event.rb +4 -9
  47. data/lib/sentry/transport/debug_transport.rb +70 -0
  48. data/lib/sentry/transport/dummy_transport.rb +1 -0
  49. data/lib/sentry/transport/http_transport.rb +10 -16
  50. data/lib/sentry/transport.rb +4 -6
  51. data/lib/sentry/utils/encoding_helper.rb +6 -0
  52. data/lib/sentry/utils/logging_helper.rb +25 -9
  53. data/lib/sentry/utils/sample_rand.rb +97 -0
  54. data/lib/sentry/utils/telemetry_attributes.rb +30 -0
  55. data/lib/sentry/vernier/profiler.rb +4 -3
  56. data/lib/sentry/version.rb +1 -1
  57. data/lib/sentry-ruby.rb +57 -30
  58. data/sentry-ruby-core.gemspec +1 -1
  59. data/sentry-ruby.gemspec +2 -1
  60. metadata +31 -17
  61. data/lib/sentry/metrics/aggregator.rb +0 -248
  62. data/lib/sentry/metrics/configuration.rb +0 -47
  63. data/lib/sentry/metrics/counter_metric.rb +0 -25
  64. data/lib/sentry/metrics/distribution_metric.rb +0 -25
  65. data/lib/sentry/metrics/gauge_metric.rb +0 -35
  66. data/lib/sentry/metrics/local_aggregator.rb +0 -53
  67. data/lib/sentry/metrics/metric.rb +0 -19
  68. data/lib/sentry/metrics/set_metric.rb +0 -28
  69. data/lib/sentry/metrics/timing.rb +0 -51
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cgi/escape"
3
4
  require "concurrent/utility/processor_counter"
4
5
 
5
6
  require "sentry/utils/exception_cause_chain"
@@ -9,11 +10,12 @@ require "sentry/dsn"
9
10
  require "sentry/release_detector"
10
11
  require "sentry/transport/configuration"
11
12
  require "sentry/cron/configuration"
12
- require "sentry/metrics/configuration"
13
13
  require "sentry/linecache"
14
14
  require "sentry/interfaces/stacktrace_builder"
15
15
  require "sentry/logger"
16
+ require "sentry/structured_logger"
16
17
  require "sentry/log_event_buffer"
18
+ require "sentry/metric_event_buffer"
17
19
 
18
20
  module Sentry
19
21
  class Configuration
@@ -30,13 +32,6 @@ module Sentry
30
32
  # @return [Regexp, nil]
31
33
  attr_accessor :app_dirs_pattern
32
34
 
33
- # Provide an object that responds to `call` to send events asynchronously.
34
- # E.g.: lambda { |event| Thread.new { Sentry.send_event(event) } }
35
- #
36
- # @deprecated It will be removed in the next major release. Please read https://github.com/getsentry/sentry-ruby/issues/1522 for more information
37
- # @return [Proc, nil]
38
- attr_reader :async
39
-
40
35
  # to send events in a non-blocking way, sentry-ruby has its own background worker
41
36
  # by default, the worker holds a thread pool that has [the number of processors] threads
42
37
  # but you can configure it with this configuration option
@@ -74,11 +69,10 @@ module Sentry
74
69
  # @return [Proc]
75
70
  attr_reader :before_breadcrumb
76
71
 
77
- # Optional Proc, called before sending an event to the server
72
+ # Optional Proc, called before sending an error event to the server
78
73
  # @example
79
74
  # config.before_send = lambda do |event, hint|
80
75
  # # skip ZeroDivisionError exceptions
81
- # # note: hint[:exception] would be a String if you use async callback
82
76
  # if hint[:exception].is_a?(ZeroDivisionError)
83
77
  # nil
84
78
  # else
@@ -88,7 +82,7 @@ module Sentry
88
82
  # @return [Proc]
89
83
  attr_reader :before_send
90
84
 
91
- # Optional Proc, called before sending an event to the server
85
+ # Optional Proc, called before sending a transaction event to the server
92
86
  # @example
93
87
  # config.before_send_transaction = lambda do |event, hint|
94
88
  # # skip unimportant transactions or strip sensitive data
@@ -101,6 +95,18 @@ module Sentry
101
95
  # @return [Proc]
102
96
  attr_reader :before_send_transaction
103
97
 
98
+ # Optional Proc, called before sending a check-in event to the server
99
+ # @example
100
+ # config.before_send_check_in = lambda do |event, hint|
101
+ # if event.monitor_slug == "unimportant_job"
102
+ # nil
103
+ # else
104
+ # event
105
+ # end
106
+ # end
107
+ # @return [Proc]
108
+ attr_reader :before_send_check_in
109
+
104
110
  # Optional Proc, called before sending an event to the server
105
111
  # @example
106
112
  # config.before_send_log = lambda do |log|
@@ -117,7 +123,6 @@ module Sentry
117
123
  #
118
124
  # And if you also use sentry-rails:
119
125
  # - :active_support_logger
120
- # - :monotonic_active_support_logger
121
126
  #
122
127
  # @return [Array<Symbol>]
123
128
  attr_reader :breadcrumbs_logger
@@ -144,7 +149,7 @@ module Sentry
144
149
  attr_reader :dsn
145
150
 
146
151
  # Whitelist of enabled_environments that will send notifications to Sentry. Array of Strings.
147
- # @return [Array<String>]
152
+ # @return [Array<String>, nil]
148
153
  attr_accessor :enabled_environments
149
154
 
150
155
  # Logger 'progname's to exclude from breadcrumbs
@@ -173,18 +178,6 @@ module Sentry
173
178
  # @return [Boolean, String]
174
179
  attr_accessor :spotlight
175
180
 
176
- # @deprecated Use {#include_local_variables} instead.
177
- alias_method :capture_exception_frame_locals, :include_local_variables
178
-
179
- # @deprecated Use {#include_local_variables=} instead.
180
- def capture_exception_frame_locals=(value)
181
- log_warn <<~MSG
182
- `capture_exception_frame_locals` is now deprecated in favor of `include_local_variables`.
183
- MSG
184
-
185
- self.include_local_variables = value
186
- end
187
-
188
181
  # You may provide your own LineCache for matching paths with source files.
189
182
  # This may be useful if you need to get source code from places other than the disk.
190
183
  # @see LineCache
@@ -196,17 +189,10 @@ module Sentry
196
189
  # @return [Logger]
197
190
  attr_accessor :sdk_logger
198
191
 
199
- # @deprecated Use {#sdk_logger=} instead.
200
- def logger=(logger)
201
- warn "[sentry] `config.logger=` is deprecated. Please use `config.sdk_logger=` instead."
202
- self.sdk_logger = logger
203
- end
204
-
205
- # @deprecated Use {#sdk_logger} instead.
206
- def logger
207
- warn "[sentry] `config.logger` is deprecated. Please use `config.sdk_logger` instead."
208
- self.sdk_logger
209
- end
192
+ # File path for DebugTransport to log events to. If not set, defaults to a temporary file.
193
+ # This is useful for debugging and testing purposes.
194
+ # @return [String, nil]
195
+ attr_accessor :sdk_debug_transport_log_file
210
196
 
211
197
  # Project directory root for in_app detection. Could be Rails root, etc.
212
198
  # Set automatically for Rails.
@@ -249,6 +235,12 @@ module Sentry
249
235
  # @return [Boolean]
250
236
  attr_accessor :send_default_pii
251
237
 
238
+ # Capture queue time from X-Request-Start header set by reverse proxies.
239
+ # Works with any Rack app behind Nginx, HAProxy, Heroku router, etc.
240
+ # Defaults to true.
241
+ # @return [Boolean]
242
+ attr_accessor :capture_queue_time
243
+
252
244
  # Allow to skip Sentry emails within rake tasks
253
245
  # @return [Boolean]
254
246
  attr_accessor :skip_rake_integration
@@ -267,10 +259,6 @@ module Sentry
267
259
  # @return [Cron::Configuration]
268
260
  attr_reader :cron
269
261
 
270
- # Metrics related configuration.
271
- # @return [Metrics::Configuration]
272
- attr_reader :metrics
273
-
274
262
  # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
275
263
  # @return [Float, nil]
276
264
  attr_reader :traces_sample_rate
@@ -289,11 +277,9 @@ module Sentry
289
277
  # @return [Boolean]
290
278
  attr_accessor :enable_logs
291
279
 
292
- # Easier way to use performance tracing
293
- # If set to true, will set traces_sample_rate to 1.0
294
- # @deprecated It will be removed in the next major release.
295
- # @return [Boolean, nil]
296
- attr_reader :enable_tracing
280
+ # Structured logging configuration.
281
+ # @return [StructuredLoggingConfiguration]
282
+ attr_reader :structured_logging
297
283
 
298
284
  # Send diagnostic client reports about dropped events, true by default
299
285
  # tries to attach to an existing envelope max once every 30s
@@ -315,6 +301,18 @@ module Sentry
315
301
  # @return [Array<String, Regexp>]
316
302
  attr_accessor :trace_propagation_targets
317
303
 
304
+ # Collection of HTTP status codes or ranges of codes to ignore when tracing incoming requests.
305
+ # If a transaction's http.response.status_code matches one of these values,
306
+ # the transaction will be dropped and marked as not sampled.
307
+ # Defaults to TRACE_IGNORE_STATUS_CODES_DEFAULT.
308
+ #
309
+ # @example
310
+ # # ignore 404 and 502 <= status_code <= 511
311
+ # config.trace_ignore_status_codes = [404, (502..511)]
312
+ #
313
+ # @return [Array<Integer>, Array<Range>]
314
+ attr_reader :trace_ignore_status_codes
315
+
318
316
  # The instrumenter to use, :sentry or :otel
319
317
  # @return [Symbol]
320
318
  attr_reader :instrumenter
@@ -329,6 +327,15 @@ module Sentry
329
327
  # @return [Float, nil]
330
328
  attr_reader :profiles_sample_rate
331
329
 
330
+ # Interval in microseconds at which to take samples.
331
+ # The default is 1e6 / 101, or 101Hz.
332
+ # Note that the 101 is intentional to avoid lockstep sampling.
333
+ #
334
+ # @example
335
+ # config.profiles_sample_interval = 1e5 / 101
336
+ # @return [Float]
337
+ attr_accessor :profiles_sample_interval
338
+
332
339
  # Array of patches to apply.
333
340
  # Default is {DEFAULT_PATCHES}
334
341
  # @return [Array<Symbol>]
@@ -338,6 +345,32 @@ module Sentry
338
345
  # @return [Integer]
339
346
  attr_accessor :max_log_events
340
347
 
348
+ # Enable metrics collection, defaults to true
349
+ # @return [Boolean]
350
+ attr_accessor :enable_metrics
351
+
352
+ # Maximum number of metric events to buffer before sending
353
+ # @return [Integer]
354
+ attr_accessor :max_metric_events
355
+
356
+ # Optional Proc, called before sending a metric
357
+ # @example
358
+ # config.before_send_metric = lambda do |metric|
359
+ # # return nil to drop the metric
360
+ # metric
361
+ # end
362
+ # @return [Proc, nil]
363
+ attr_reader :before_send_metric
364
+
365
+ # Optional Proc, called to filter log messages before sending to Sentry
366
+ # @example
367
+ # config.std_lib_logger_filter = lambda do |logger, message, level|
368
+ # # Only send error and fatal logs to Sentry
369
+ # [:error, :fatal].include?(level)
370
+ # end
371
+ # @return [Proc, nil]
372
+ attr_reader :std_lib_logger_filter
373
+
341
374
  # these are not config options
342
375
  # @!visibility private
343
376
  attr_reader :errors, :gem_specs
@@ -366,6 +399,8 @@ module Sentry
366
399
  SERVER_PORT
367
400
  ].freeze
368
401
 
402
+ TRACE_IGNORE_STATUS_CODES_DEFAULT = [(301..303), (305..399), (401..404)]
403
+
369
404
  HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
370
405
  "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`"
371
406
 
@@ -381,6 +416,9 @@ module Sentry
381
416
 
382
417
  APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/
383
418
 
419
+ # 101 Hz in microseconds
420
+ DEFAULT_PROFILES_SAMPLE_INTERVAL = 1e6 / 101
421
+
384
422
  class << self
385
423
  # Post initialization callbacks are called at the end of initialization process
386
424
  # allowing extending the configuration of sentry-ruby by multiple extensions
@@ -390,7 +428,23 @@ module Sentry
390
428
 
391
429
  # allow extensions to add their hooks to the Configuration class
392
430
  def add_post_initialization_callback(&block)
393
- post_initialization_callbacks << block
431
+ callbacks[:initialize][:after] << block
432
+ end
433
+
434
+ def before(event, &block)
435
+ callbacks[event.to_sym][:before] << block
436
+ end
437
+
438
+ def after(event, &block)
439
+ callbacks[event.to_sym][:after] << block
440
+ end
441
+
442
+ # @!visibility private
443
+ def callbacks
444
+ @callbacks ||= {
445
+ initialize: { before: [], after: [] },
446
+ configured: { before: [], after: [] }
447
+ }
394
448
  end
395
449
 
396
450
  def validations
@@ -434,6 +488,8 @@ module Sentry
434
488
  validate :profiles_sample_rate, optional: true, type: :numeric
435
489
 
436
490
  def initialize
491
+ run_callbacks(:before, :initialize)
492
+
437
493
  self.app_dirs_pattern = APP_DIRS_PATTERN
438
494
  self.debug = Sentry::Utils::EnvHelper.env_to_bool(ENV["SENTRY_DEBUG"])
439
495
  self.background_worker_threads = (processor_count / 2.0).ceil
@@ -445,7 +501,7 @@ module Sentry
445
501
  self.context_lines = 3
446
502
  self.include_local_variables = false
447
503
  self.environment = environment_from_env
448
- self.enabled_environments = []
504
+ self.enabled_environments = nil
449
505
  self.exclude_loggers = []
450
506
  self.excluded_exceptions = IGNORE_DEFAULT + PUMA_IGNORE_DEFAULT
451
507
  self.inspect_exception_causes_for_exclusion = true
@@ -463,6 +519,7 @@ module Sentry
463
519
  self.enable_backpressure_handling = false
464
520
  self.trusted_proxies = []
465
521
  self.dsn = ENV["SENTRY_DSN"]
522
+ self.capture_queue_time = true
466
523
 
467
524
  spotlight_env = ENV["SENTRY_SPOTLIGHT"]
468
525
  spotlight_bool = Sentry::Utils::EnvHelper.env_to_bool(spotlight_env, strict: true)
@@ -470,26 +527,36 @@ module Sentry
470
527
  self.server_name = server_name_from_env
471
528
  self.instrumenter = :sentry
472
529
  self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
530
+ self.trace_ignore_status_codes = TRACE_IGNORE_STATUS_CODES_DEFAULT
473
531
  self.enabled_patches = DEFAULT_PATCHES.dup
474
532
 
475
533
  self.before_send = nil
476
534
  self.before_send_transaction = nil
535
+ self.before_send_check_in = nil
477
536
  self.before_send_log = nil
537
+ self.before_send_metric = nil
538
+ self.std_lib_logger_filter = nil
478
539
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
479
540
  self.traces_sampler = nil
480
- self.enable_tracing = nil
481
541
  self.enable_logs = false
542
+ self.enable_metrics = true
482
543
 
483
544
  self.profiler_class = Sentry::Profiler
545
+ self.profiles_sample_interval = DEFAULT_PROFILES_SAMPLE_INTERVAL
484
546
 
485
547
  @transport = Transport::Configuration.new
486
548
  @cron = Cron::Configuration.new
487
- @metrics = Metrics::Configuration.new
549
+ @structured_logging = StructuredLoggingConfiguration.new
488
550
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
489
551
 
490
- run_post_initialization_callbacks
491
-
492
552
  self.max_log_events = LogEventBuffer::DEFAULT_MAX_EVENTS
553
+ self.max_metric_events = MetricEventBuffer::DEFAULT_MAX_METRICS
554
+
555
+ run_callbacks(:after, :initialize)
556
+
557
+ yield(self) if block_given?
558
+
559
+ run_callbacks(:after, :configured)
493
560
  end
494
561
 
495
562
  def validate
@@ -522,22 +589,6 @@ module Sentry
522
589
  @release = value
523
590
  end
524
591
 
525
- def async=(value)
526
- check_callable!("async", value)
527
-
528
- log_warn <<~MSG
529
-
530
- sentry-ruby now sends events asynchronously by default with its background worker (supported since 4.1.0).
531
- The `config.async` callback has become redundant while continuing to cause issues.
532
- (The problems of `async` are detailed in https://github.com/getsentry/sentry-ruby/issues/1522)
533
-
534
- Therefore, we encourage you to remove it and let the background worker take care of async job sending.
535
- It's deprecation is planned in the next major release (6.0), which is scheduled around the 3rd quarter of 2022.
536
- MSG
537
-
538
- @async = value
539
- end
540
-
541
592
  def breadcrumbs_logger=(logger)
542
593
  loggers =
543
594
  if logger.is_a?(Array)
@@ -563,12 +614,30 @@ module Sentry
563
614
  @before_send_transaction = value
564
615
  end
565
616
 
617
+ def before_send_check_in=(value)
618
+ check_callable!("before_send_check_in", value)
619
+
620
+ @before_send_check_in = value
621
+ end
622
+
623
+ def before_send_metric=(value)
624
+ check_callable!("before_send_metric", value)
625
+
626
+ @before_send_metric = value
627
+ end
628
+
566
629
  def before_breadcrumb=(value)
567
630
  check_callable!("before_breadcrumb", value)
568
631
 
569
632
  @before_breadcrumb = value
570
633
  end
571
634
 
635
+ def std_lib_logger_filter=(value)
636
+ check_callable!("std_lib_logger_filter", value)
637
+
638
+ @std_lib_logger_filter = value
639
+ end
640
+
572
641
  def environment=(environment)
573
642
  @environment = environment.to_s
574
643
  end
@@ -577,15 +646,12 @@ module Sentry
577
646
  @instrumenter = INSTRUMENTERS.include?(instrumenter) ? instrumenter : :sentry
578
647
  end
579
648
 
580
- def enable_tracing=(enable_tracing)
581
- unless enable_tracing.nil?
582
- log_warn <<~MSG
583
- `enable_tracing` is now deprecated in favor of `traces_sample_rate = 1.0`.
584
- MSG
649
+ def trace_ignore_status_codes=(codes)
650
+ unless codes.is_a?(Array) && codes.all? { |code| valid_status_code_entry?(code) }
651
+ raise ArgumentError, "trace_ignore_status_codes must be an Array of integers or ranges between (100-599) where begin <= end"
585
652
  end
586
653
 
587
- @enable_tracing = enable_tracing
588
- @traces_sample_rate ||= 1.0 if enable_tracing
654
+ @trace_ignore_status_codes = codes
589
655
  end
590
656
 
591
657
  def traces_sample_rate=(traces_sample_rate)
@@ -641,7 +707,7 @@ module Sentry
641
707
  end
642
708
 
643
709
  def enabled_in_current_env?
644
- enabled_environments.empty? || enabled_environments.include?(environment)
710
+ enabled_environments.nil? || enabled_environments.include?(environment)
645
711
  end
646
712
 
647
713
  def valid_sample_rate?(sample_rate)
@@ -652,7 +718,7 @@ module Sentry
652
718
  def tracing_enabled?
653
719
  valid_sampler = !!((valid_sample_rate?(@traces_sample_rate)) || @traces_sampler)
654
720
 
655
- (@enable_tracing != false) && valid_sampler && sending_allowed?
721
+ valid_sampler && sending_allowed?
656
722
  end
657
723
 
658
724
  def profiling_enabled?
@@ -773,8 +839,8 @@ module Sentry
773
839
  File.directory?("/etc/heroku") && !ENV["CI"]
774
840
  end
775
841
 
776
- def run_post_initialization_callbacks
777
- self.class.post_initialization_callbacks.each do |hook|
842
+ def run_callbacks(hook, event)
843
+ self.class.callbacks[event][hook].each do |hook|
778
844
  instance_eval(&hook)
779
845
  end
780
846
  end
@@ -783,5 +849,37 @@ module Sentry
783
849
  available_processor_count = Concurrent.available_processor_count if Concurrent.respond_to?(:available_processor_count)
784
850
  available_processor_count || Concurrent.processor_count
785
851
  end
852
+
853
+ def valid_http_status_code?(code)
854
+ code.is_a?(Integer) && code >= 100 && code <= 599
855
+ end
856
+
857
+ def valid_status_code_entry?(entry)
858
+ case entry
859
+ when Integer
860
+ valid_http_status_code?(entry)
861
+ when Range
862
+ valid_http_status_code?(entry.begin) &&
863
+ valid_http_status_code?(entry.end) &&
864
+ entry.begin <= entry.end
865
+ else
866
+ false
867
+ end
868
+ end
869
+ end
870
+
871
+ class StructuredLoggingConfiguration
872
+ # File path for DebugStructuredLogger to log events to
873
+ # @return [String, Pathname, nil]
874
+ attr_accessor :file_path
875
+
876
+ # The class to use as a structured logger.
877
+ # @return [Class]
878
+ attr_accessor :logger_class
879
+
880
+ def initialize
881
+ @file_path = nil
882
+ @logger_class = Sentry::StructuredLogger
883
+ end
786
884
  end
787
885
  end
@@ -14,12 +14,12 @@ module Sentry
14
14
  :in_progress,
15
15
  monitor_config: monitor_config)
16
16
 
17
- start = Metrics::Timing.duration_start
17
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
18
18
 
19
19
  begin
20
20
  # need to do this on ruby <= 2.6 sadly
21
21
  ret = method(:perform).super_method.arity == 0 ? super() : super
22
- duration = Metrics::Timing.duration_end(start)
22
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
23
23
 
24
24
  Sentry.capture_check_in(slug,
25
25
  :ok,
@@ -29,7 +29,7 @@ module Sentry
29
29
 
30
30
  ret
31
31
  rescue Exception
32
- duration = Metrics::Timing.duration_end(start)
32
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
33
33
 
34
34
  Sentry.capture_check_in(slug,
35
35
  :error,
@@ -40,9 +40,9 @@ module Sentry
40
40
  new(MonitorSchedule::Interval.new(num, unit), **options)
41
41
  end
42
42
 
43
- def to_hash
43
+ def to_h
44
44
  {
45
- schedule: schedule.to_hash,
45
+ schedule: schedule.to_h,
46
46
  checkin_margin: checkin_margin,
47
47
  max_runtime: max_runtime,
48
48
  timezone: timezone
@@ -12,7 +12,7 @@ module Sentry
12
12
  @value = value
13
13
  end
14
14
 
15
- def to_hash
15
+ def to_h
16
16
  { type: :crontab, value: value }
17
17
  end
18
18
  end
@@ -33,7 +33,7 @@ module Sentry
33
33
  @unit = unit
34
34
  end
35
35
 
36
- def to_hash
36
+ def to_h
37
37
  { type: :interval, value: value, unit: unit }
38
38
  end
39
39
  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,16 @@
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
9
+ PROTOCOL_VERSION = "7"
7
10
  PORT_MAP = { "http" => 80, "https" => 443 }.freeze
8
11
  REQUIRED_ATTRIBUTES = %w[host path public_key project_id].freeze
12
+ LOCALHOST_NAMES = %w[localhost 127.0.0.1 ::1 [::1]].freeze
13
+ LOCALHOST_PATTERN = /\.local(host|domain)?$/i
9
14
 
10
15
  attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
11
16
 
@@ -49,5 +54,52 @@ module Sentry
49
54
  def envelope_endpoint
50
55
  "#{path}/api/#{project_id}/envelope/"
51
56
  end
57
+
58
+ def otlp_traces_endpoint
59
+ "#{path}/api/#{project_id}/integration/otlp/v1/traces/"
60
+ end
61
+
62
+ def local?
63
+ @local ||= (localhost? || private_ip? || resolved_ips_private?)
64
+ end
65
+
66
+ def localhost?
67
+ LOCALHOST_NAMES.include?(host.downcase) || LOCALHOST_PATTERN.match?(host)
68
+ end
69
+
70
+ def private_ip?
71
+ @private_ip ||= begin
72
+ begin
73
+ IPAddr.new(host).private?
74
+ rescue IPAddr::InvalidAddressError
75
+ false
76
+ end
77
+ end
78
+ end
79
+
80
+ def resolved_ips_private?
81
+ @resolved_ips_private ||= begin
82
+ begin
83
+ Resolv.getaddresses(host).any? { |ip| IPAddr.new(ip).private? }
84
+ rescue Resolv::ResolvError, IPAddr::InvalidAddressError
85
+ false
86
+ end
87
+ end
88
+ end
89
+
90
+ def generate_auth_header(client: nil)
91
+ now = Sentry.utc_now.to_i
92
+
93
+ fields = {
94
+ "sentry_version" => PROTOCOL_VERSION,
95
+ "sentry_timestamp" => now,
96
+ "sentry_key" => @public_key
97
+ }
98
+
99
+ fields["sentry_client"] = client if client
100
+ fields["sentry_secret"] = @secret_key if @secret_key
101
+
102
+ "Sentry " + fields.map { |key, value| "#{key}=#{value}" }.join(", ")
103
+ end
52
104
  end
53
105
  end