sentry-rails-5.28.0/0000755000004100000410000000000015070254562014300 5ustar www-datawww-datasentry-rails-5.28.0/bin/0000755000004100000410000000000015070254562015050 5ustar www-datawww-datasentry-rails-5.28.0/bin/setup0000755000004100000410000000020315070254562016131 0ustar www-datawww-data#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here sentry-rails-5.28.0/bin/test0000755000004100000410000002751615070254562015770 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true # Standalone CLI script to test sentry-rails against multiple Rails versions # # FEATURES: # - Dedicated lock files for each Ruby/Rails version combination # - Prevents dependency conflicts between different Rails versions # - Automatic lock file management and restoration # - Clean up functionality for old lock files # # LOCK FILE STRATEGY: # Each Ruby/Rails combination gets its own lock file: # - Ruby 3.4.5 + Rails 6.1 → Gemfile-ruby-3.4.5-rails-6.1.lock # - Ruby 3.4.5 + Rails 7.0 → Gemfile-ruby-3.4.5-rails-7.0.lock # # Usage: # ./bin/test --version 5.0 # ./bin/test --all # ./bin/test --help require 'optparse' require 'fileutils' class RailsVersionTester SUPPORTED_VERSIONS = %w[5.0 5.1 5.2 6.0 6.1 7.0 7.1 7.2 8.0].freeze def initialize @options = {} @failed_versions = [] @ruby_version = RUBY_VERSION @spec_paths = [] end def run(args) parse_options(args) case when @options[:help] show_help when @options[:list] list_versions when @options[:clean] clean_lock_files when @options[:all] test_all_versions when @options[:version] test_single_version(@options[:version]) else puts "Error: No action specified. Use --help for usage information." exit(1) end end private def parse_options(args) OptionParser.new do |opts| opts.banner = "Usage: #{$0} [options] [spec_paths...]" opts.on("-v", "--version VERSION", "Test specific Rails version") do |version| unless SUPPORTED_VERSIONS.include?(version) puts "Error: Unsupported Rails version '#{version}'" puts "Supported versions: #{SUPPORTED_VERSIONS.join(', ')}" exit(1) end @options[:version] = version end opts.on("-a", "--all", "Test all supported Rails versions") do @options[:all] = true end opts.on("-l", "--list", "List supported Rails versions and lock file status") do @options[:list] = true end opts.on("-c", "--clean", "Clean up old lock files for current Ruby version") do @options[:clean] = true end opts.on("-h", "--help", "Show this help message") do @options[:help] = true end end.parse!(args) # Remaining arguments are spec paths @spec_paths = args end def show_help puts <<~HELP Rails Version Tester for sentry-rails This script tests sentry-rails against different Rails versions by: 1. Setting the RAILS_VERSION environment variable 2. Managing bundle dependencies with dedicated lock files per Ruby/Rails combination 3. Running the test suite in isolated processes 4. Providing proper exit codes for CI/CD integration Each Ruby/Rails version combination gets its own Gemfile.lock to prevent conflicts: - Ruby #{@ruby_version} + Rails 6.1 → Gemfile-ruby-#{@ruby_version}-rails-6.1.lock - Ruby #{@ruby_version} + Rails 7.0 → Gemfile-ruby-#{@ruby_version}-rails-7.0.lock Usage: #{$0} --version 6.1 # Test specific Rails version (all specs) #{$0} --version 7.0 spec/sentry/rails/log_subscribers # Test specific Rails version with specific specs #{$0} --all # Test all supported versions #{$0} --list # List supported versions and lock file status #{$0} --clean # Clean up old lock files for current Ruby version #{$0} --help # Show this help Supported Rails versions: #{SUPPORTED_VERSIONS.join(', ')} Examples: #{$0} -v 7.1 # Test Rails 7.1 (all specs) #{$0} -v 7.0 spec/sentry/rails/log_subscribers # Test Rails 7.0 log subscriber specs only #{$0} -v 7.0 spec/sentry/rails/tracing # Test Rails 7.0 tracing specs only #{$0} -a # Test all versions #{$0} -c # Clean up old lock files HELP end def list_versions puts "Supported Rails versions:" SUPPORTED_VERSIONS.each do |version| lock_file = generate_lock_file_name(version) status = File.exist?(lock_file) ? "(has lock file)" : "(no lock file)" puts " - #{version} #{status}" end puts puts "Current Ruby version: #{@ruby_version}" puts "Lock files are stored as: Gemfile-ruby-X.X.X-rails-Y.Y.lock" end def test_all_versions puts "Testing sentry-rails against all supported Rails versions: #{SUPPORTED_VERSIONS.join(', ')}" puts SUPPORTED_VERSIONS.each do |version| puts "=" * 60 puts "Testing Rails #{version}" puts "=" * 60 exit_code = test_rails_version(version) if exit_code == 0 puts "✓ Rails #{version} - PASSED" else puts "✗ Rails #{version} - FAILED (exit code: #{exit_code})" @failed_versions << version end puts end print_summary end def test_single_version(version) puts "Testing sentry-rails against Rails #{version}..." exit_code = test_rails_version(version) exit(exit_code) unless exit_code == 0 end def test_rails_version(version) puts "Setting up environment for Rails #{version}..." # Generate dedicated lock file name for this Ruby/Rails combination dedicated_lock_file = generate_lock_file_name(version) current_lock_file = "Gemfile.lock" # Set up environment variables env = { "RAILS_VERSION" => version, "BUNDLE_GEMFILE" => File.expand_path("Gemfile", Dir.pwd) } puts "Using dedicated lock file: #{dedicated_lock_file}" # Manage lock file switching setup_lock_file(dedicated_lock_file, current_lock_file) begin # Check if bundle update is needed if bundle_update_needed?(env, dedicated_lock_file) puts "Dependencies need to be updated for Rails #{version}..." unless update_bundle(env, dedicated_lock_file) puts "✗ Failed to update bundle for Rails #{version}" return 1 end end # Run the tests in a separate process puts "Running test suite..." run_tests(env, @spec_paths) ensure # Save the current lock file back to the dedicated location save_lock_file(dedicated_lock_file, current_lock_file) end end def generate_lock_file_name(rails_version) # Create a unique lock file name for this Ruby/Rails combination ruby_version_clean = @ruby_version.gsub(/[^\d\.]/, '') rails_version_clean = rails_version.gsub(/[^\d\.]/, '') "Gemfile-ruby-#{ruby_version_clean}-rails-#{rails_version_clean}.lock" end def setup_lock_file(dedicated_lock_file, current_lock_file) # If we have a dedicated lock file, copy it to the current location if File.exist?(dedicated_lock_file) puts "Restoring lock file from #{dedicated_lock_file}" FileUtils.cp(dedicated_lock_file, current_lock_file) elsif File.exist?(current_lock_file) # If no dedicated lock file exists but current one does, remove it # so we get a fresh resolution puts "Removing existing lock file for fresh dependency resolution" File.delete(current_lock_file) end end def save_lock_file(dedicated_lock_file, current_lock_file) # Save the current lock file to the dedicated location if File.exist?(current_lock_file) puts "Saving lock file to #{dedicated_lock_file}" FileUtils.cp(current_lock_file, dedicated_lock_file) end end def bundle_update_needed?(env, dedicated_lock_file) # Check if current Gemfile.lock exists current_lock_file = "Gemfile.lock" gemfile_path = env["BUNDLE_GEMFILE"] || "Gemfile" return true unless File.exist?(current_lock_file) # Check if Gemfile is newer than the current lock file return true if File.mtime(gemfile_path) > File.mtime(current_lock_file) # For Rails version changes, check if lockfile has incompatible Rails version if env["RAILS_VERSION"] && lockfile_has_incompatible_rails_version?(current_lock_file, env["RAILS_VERSION"]) return true end # Check if bundle check passes system(env, "bundle check > /dev/null 2>&1") == false end def lockfile_has_incompatible_rails_version?(lockfile_path, target_rails_version) return false unless File.exist?(lockfile_path) lockfile_content = File.read(lockfile_path) # Extract Rails version from lockfile if lockfile_content =~ /^\s*rails \(([^)]+)\)/ locked_rails_version = $1 target_major_minor = target_rails_version.split('.')[0..1].join('.') locked_major_minor = locked_rails_version.split('.')[0..1].join('.') # If major.minor versions don't match, we need to update return target_major_minor != locked_major_minor end # If we can't determine the Rails version, assume update is needed true end def update_bundle(env, dedicated_lock_file) puts "Updating bundle for Rails #{env['RAILS_VERSION']}..." current_lock_file = "Gemfile.lock" # Try bundle update first if system(env, "bundle update --quiet") puts "Bundle updated successfully" return true end puts "Bundle update failed, trying clean install..." # Remove the current lockfile and try fresh install File.delete(current_lock_file) if File.exist?(current_lock_file) if system(env, "bundle install --quiet") puts "Bundle installed successfully" return true end puts "Bundle install failed" false end def run_tests(env, spec_paths = []) # Determine the command to run if spec_paths.empty? # Run all tests via rake command = "bundle exec rake" else # Run specific specs via rspec command = "bundle exec rspec #{spec_paths.join(' ')}" end puts "Executing: #{command}" # Run the tests in a separate process with proper signal handling pid = spawn(env, command, out: $stdout, err: $stderr, pgroup: true) begin _, status = Process.wait2(pid) status.exitstatus rescue Interrupt puts "\nInterrupted! Terminating test process..." terminate_process_group(pid) 130 # Standard exit code for SIGINT end end def terminate_process_group(pid) begin Process.kill("TERM", -pid) # Kill the process group sleep(2) Process.kill("KILL", -pid) if process_running?(pid) rescue Errno::ESRCH # Process already terminated end end def process_running?(pid) Process.getpgid(pid) true rescue Errno::ESRCH false end def clean_lock_files puts "Cleaning up lock files for Ruby #{@ruby_version}..." # Find all lock files matching our pattern pattern = "Gemfile-ruby-#{@ruby_version.gsub(/[^\d\.]/, '')}-rails-*.lock" lock_files = Dir.glob(pattern) if lock_files.empty? puts "No lock files found matching pattern: #{pattern}" return end puts "Found #{lock_files.length} lock file(s):" lock_files.each { |file| puts " - #{file}" } print "Delete these files? [y/N]: " response = $stdin.gets.chomp.downcase if response == 'y' || response == 'yes' lock_files.each do |file| File.delete(file) puts "Deleted: #{file}" end puts "Cleanup complete!" else puts "Cleanup cancelled." end end def print_summary puts "=" * 60 puts "SUMMARY" puts "=" * 60 if @failed_versions.empty? puts "✓ All Rails versions passed!" exit(0) else puts "✗ Failed versions: #{@failed_versions.join(', ')}" puts puts "Some Rails versions failed. See output above for details." exit(1) end end end # Run the script if called directly if __FILE__ == $0 tester = RailsVersionTester.new tester.run(ARGV) end sentry-rails-5.28.0/bin/console0000755000004100000410000000057015070254562016442 0ustar www-datawww-data#!/usr/bin/env ruby # frozen_string_literal: true require "bundler/setup" require "sentry/ruby" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start(__FILE__) sentry-rails-5.28.0/.gitignore0000644000004100000410000000022015070254562016262 0ustar www-datawww-data/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /spec/dummy/test_rails_app/db* /tmp/ # rspec failure tracking .rspec_status sentry-rails-5.28.0/lib/0000755000004100000410000000000015070254562015046 5ustar www-datawww-datasentry-rails-5.28.0/lib/generators/0000755000004100000410000000000015070254562017217 5ustar www-datawww-datasentry-rails-5.28.0/lib/generators/sentry_generator.rb0000644000004100000410000000156415070254562023144 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators/base" class SentryGenerator < ::Rails::Generators::Base class_option :dsn, type: :string, desc: "Sentry DSN" class_option :inject_meta, type: :boolean, default: true, desc: "Inject meta tag into layout" def copy_initializer_file dsn = options[:dsn] ? "'#{options[:dsn]}'" : "ENV['SENTRY_DSN']" create_file "config/initializers/sentry.rb", <<~RUBY # frozen_string_literal: true Sentry.init do |config| config.breadcrumbs_logger = [:active_support_logger] config.dsn = #{dsn} config.traces_sample_rate = 1.0 end RUBY end def inject_code_into_layout return unless options[:inject_meta] inject_into_file "app/views/layouts/application.html.erb", before: "\n" do " <%= Sentry.get_trace_propagation_meta.html_safe %>\n " end end end sentry-rails-5.28.0/lib/sentry/0000755000004100000410000000000015070254562016372 5ustar www-datawww-datasentry-rails-5.28.0/lib/sentry/rails.rb0000644000004100000410000000062215070254562020031 0ustar www-datawww-data# frozen_string_literal: true require "rails" require "sentry-ruby" require "sentry/integrable" require "sentry/rails/tracing" require "sentry/rails/configuration" require "sentry/rails/structured_logging" require "sentry/rails/engine" require "sentry/rails/railtie" module Sentry module Rails extend Integrable register_integration name: "rails", version: Sentry::Rails::VERSION end end sentry-rails-5.28.0/lib/sentry/rails/0000755000004100000410000000000015070254562017504 5ustar www-datawww-datasentry-rails-5.28.0/lib/sentry/rails/configuration.rb0000644000004100000410000002242415070254562022704 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/tracing/action_controller_subscriber" require "sentry/rails/tracing/action_view_subscriber" require "sentry/rails/tracing/active_record_subscriber" require "sentry/rails/tracing/active_storage_subscriber" require "sentry/rails/tracing/active_support_subscriber" require "sentry/rails/log_subscribers/active_record_subscriber" require "sentry/rails/log_subscribers/action_controller_subscriber" module Sentry class Configuration attr_reader :rails after(:initialize) do @rails = Sentry::Rails::Configuration.new @excluded_exceptions = @excluded_exceptions.concat(Sentry::Rails::IGNORE_DEFAULT) if ::Rails.logger if defined?(::ActiveSupport::BroadcastLogger) && ::Rails.logger.is_a?(::ActiveSupport::BroadcastLogger) dupped_broadcasts = ::Rails.logger.broadcasts.map(&:dup) self.sdk_logger = ::ActiveSupport::BroadcastLogger.new(*dupped_broadcasts) else self.sdk_logger = ::Rails.logger.dup end else sdk_logger.warn(Sentry::LOGGER_PROGNAME) do <<~MSG sentry-rails can't detect Rails.logger. it may be caused by misplacement of the SDK initialization code please make sure you place the Sentry.init block under the `config/initializers` folder, e.g. `config/initializers/sentry.rb` MSG end end end after(:configured) do rails.structured_logging.enabled = enable_logs if rails.structured_logging.enabled.nil? end end module Rails IGNORE_DEFAULT = [ "AbstractController::ActionNotFound", "ActionController::BadRequest", "ActionController::InvalidAuthenticityToken", "ActionController::InvalidCrossOriginRequest", "ActionController::MethodNotAllowed", "ActionController::NotImplemented", "ActionController::ParameterMissing", "ActionController::RoutingError", "ActionController::UnknownAction", "ActionController::UnknownFormat", "ActionDispatch::Http::MimeNegotiation::InvalidType", "ActionController::UnknownHttpMethod", "ActionDispatch::Http::Parameters::ParseError", "ActiveRecord::RecordNotFound" ].freeze ACTIVE_SUPPORT_LOGGER_SUBSCRIPTION_ITEMS_DEFAULT = { # action_controller "write_fragment.action_controller" => %i[key], "read_fragment.action_controller" => %i[key], "exist_fragment?.action_controller" => %i[key], "expire_fragment.action_controller" => %i[key], "start_processing.action_controller" => %i[controller action params format method path], "process_action.action_controller" => %i[controller action params format method path status view_runtime db_runtime], "send_file.action_controller" => %i[path], "redirect_to.action_controller" => %i[status location], "halted_callback.action_controller" => %i[filter], # action_dispatch "process_middleware.action_dispatch" => %i[middleware], # action_view "render_template.action_view" => %i[identifier layout], "render_partial.action_view" => %i[identifier], "render_collection.action_view" => %i[identifier count cache_hits], "render_layout.action_view" => %i[identifier], # active_record "sql.active_record" => %i[sql name statement_name cached], "instantiation.active_record" => %i[record_count class_name], # action_mailer # not including to, from, or subject..etc. because of PII concern "deliver.action_mailer" => %i[mailer date perform_deliveries], "process.action_mailer" => %i[mailer action params], # active_support "cache_read.active_support" => %i[key store hit], "cache_generate.active_support" => %i[key store], "cache_fetch_hit.active_support" => %i[key store], "cache_write.active_support" => %i[key store], "cache_delete.active_support" => %i[key store], "cache_exist?.active_support" => %i[key store], # active_job "enqueue_at.active_job" => %i[], "enqueue.active_job" => %i[], "enqueue_retry.active_job" => %i[], "perform_start.active_job" => %i[], "perform.active_job" => %i[], "retry_stopped.active_job" => %i[], "discard.active_job" => %i[], # action_cable "perform_action.action_cable" => %i[channel_class action], "transmit.action_cable" => %i[channel_class], "transmit_subscription_confirmation.action_cable" => %i[channel_class], "transmit_subscription_rejection.action_cable" => %i[channel_class], "broadcast.action_cable" => %i[broadcasting], # active_storage "service_upload.active_storage" => %i[service key checksum], "service_streaming_download.active_storage" => %i[service key], "service_download_chunk.active_storage" => %i[service key], "service_download.active_storage" => %i[service key], "service_delete.active_storage" => %i[service key], "service_delete_prefixed.active_storage" => %i[service prefix], "service_exist.active_storage" => %i[service key exist], "service_url.active_storage" => %i[service key url], "service_update_metadata.active_storage" => %i[service key], "preview.active_storage" => %i[key], "analyze.active_storage" => %i[analyzer] }.freeze class Configuration # Rails 7.0 introduced a new error reporter feature, which the SDK once opted-in by default. # But after receiving multiple issue reports, the integration seemed to cause serious troubles to some users. # So the integration is now controlled by this configuration, which is disabled (false) by default. # More information can be found from: https://github.com/rails/rails/pull/43625#issuecomment-1072514175 attr_accessor :register_error_subscriber # Rails catches exceptions in the ActionDispatch::ShowExceptions or # ActionDispatch::DebugExceptions middlewares, depending on the environment. # When `report_rescued_exceptions` is true (it is by default), Sentry will # report exceptions even when they are rescued by these middlewares. attr_accessor :report_rescued_exceptions # Some adapters, like sidekiq, already have their own sentry integration. # In those cases, we should skip ActiveJob's reporting to avoid duplicated reports. attr_accessor :skippable_job_adapters attr_accessor :tracing_subscribers # When the ActiveRecordSubscriber is enabled, capture the source location of the query in the span data. # This is enabled by default, but can be disabled by setting this to false. attr_accessor :enable_db_query_source # The threshold in milliseconds for the ActiveRecordSubscriber to capture the source location of the query # in the span data. Default is 100ms. attr_accessor :db_query_source_threshold_ms # sentry-rails by default skips asset request' transactions by checking if the path matches # # ```rb # %r(\A/{0,2}#{::Rails.application.config.assets.prefix}) # ``` # # If you want to use a different pattern, you can configure the `assets_regexp` option like: # # ```rb # Sentry.init do |config| # config.rails.assets_regexp = /my_regexp/ # end # ``` attr_accessor :assets_regexp # Hash of subscription items that will be shown in breadcrumbs active support logger. # @return [Hash>] attr_accessor :active_support_logger_subscription_items # Set this option to true if you want Sentry to capture each retry failure attr_accessor :active_job_report_on_retry_error # Configuration for structured logging feature # @return [StructuredLoggingConfiguration] attr_reader :structured_logging def initialize @register_error_subscriber = false @report_rescued_exceptions = true @skippable_job_adapters = [] @assets_regexp = if defined?(::Sprockets::Rails) %r(\A/{0,2}#{::Rails.application.config.assets.prefix}) end @tracing_subscribers = Set.new([ Sentry::Rails::Tracing::ActionViewSubscriber, Sentry::Rails::Tracing::ActiveSupportSubscriber, Sentry::Rails::Tracing::ActiveRecordSubscriber, Sentry::Rails::Tracing::ActiveStorageSubscriber ]) @enable_db_query_source = true @db_query_source_threshold_ms = 100 @active_support_logger_subscription_items = Sentry::Rails::ACTIVE_SUPPORT_LOGGER_SUBSCRIPTION_ITEMS_DEFAULT.dup @active_job_report_on_retry_error = false @structured_logging = StructuredLoggingConfiguration.new end end class StructuredLoggingConfiguration # Enable or disable structured logging # @return [Boolean] attr_accessor :enabled # Hash of components to subscriber classes for structured logging # @return [Hash] attr_accessor :subscribers DEFAULT_SUBSCRIBERS = { active_record: Sentry::Rails::LogSubscribers::ActiveRecordSubscriber, action_controller: Sentry::Rails::LogSubscribers::ActionControllerSubscriber }.freeze def initialize @enabled = nil @subscribers = DEFAULT_SUBSCRIBERS.dup end # Returns true if structured logging should be enabled. # @return [Boolean] def enabled? enabled end end end end sentry-rails-5.28.0/lib/sentry/rails/controller_transaction.rb0000644000004100000410000000243415070254562024624 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails module ControllerTransaction SPAN_ORIGIN = "auto.view.rails" def self.included(base) base.prepend_around_action(:sentry_around_action) end private def sentry_around_action if Sentry.initialized? transaction_name = "#{self.class}##{action_name}" Sentry.get_current_scope.set_transaction_name(transaction_name, source: :view) Sentry.with_child_span(op: "view.process_action.action_controller", description: transaction_name, origin: SPAN_ORIGIN) do |child_span| if child_span begin result = yield ensure child_span.set_http_status(response.status) child_span.set_data(:format, request.format) child_span.set_data(:method, request.method) pii = Sentry.configuration.send_default_pii child_span.set_data(:path, pii ? request.fullpath : request.filtered_path) child_span.set_data(:params, pii ? request.params : request.filtered_parameters) end result else yield end end else yield end end end end end sentry-rails-5.28.0/lib/sentry/rails/tracing/0000755000004100000410000000000015070254562021133 5ustar www-datawww-datasentry-rails-5.28.0/lib/sentry/rails/tracing/active_storage_subscriber.rb0000644000004100000410000000266415070254562026712 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/tracing/abstract_subscriber" module Sentry module Rails module Tracing class ActiveStorageSubscriber < AbstractSubscriber EVENT_NAMES = %w[ service_upload.active_storage service_download.active_storage service_streaming_download.active_storage service_download_chunk.active_storage service_delete.active_storage service_delete_prefixed.active_storage service_exist.active_storage service_url.active_storage service_mirror.active_storage service_update_metadata.active_storage preview.active_storage analyze.active_storage ].freeze SPAN_ORIGIN = "auto.file.rails" def self.subscribe! subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload| record_on_current_span( op: "file.#{event_name}", origin: SPAN_ORIGIN, start_timestamp: payload[START_TIMESTAMP_NAME], description: payload[:service], duration: duration ) do |span| payload.each do |key, value| next if key == START_TIMESTAMP_NAME next if key == :key && !Sentry.configuration.send_default_pii span.set_data(key, value) end end end end end end end end sentry-rails-5.28.0/lib/sentry/rails/tracing/action_controller_subscriber.rb0000644000004100000410000000266515070254562027434 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/tracing/abstract_subscriber" require "sentry/rails/instrument_payload_cleanup_helper" module Sentry module Rails module Tracing class ActionControllerSubscriber < AbstractSubscriber extend InstrumentPayloadCleanupHelper EVENT_NAMES = ["process_action.action_controller"].freeze OP_NAME = "view.process_action.action_controller" SPAN_ORIGIN = "auto.view.rails" def self.subscribe! Sentry.sdk_logger.warn <<~MSG DEPRECATION WARNING: sentry-rails has changed its approach on controller span recording and #{self.name} is now depreacted. Please stop using or referencing #{self.name} as it will be removed in the next major release. MSG subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload| controller = payload[:controller] action = payload[:action] record_on_current_span( op: OP_NAME, origin: SPAN_ORIGIN, start_timestamp: payload[START_TIMESTAMP_NAME], description: "#{controller}##{action}", duration: duration ) do |span| payload = payload.dup cleanup_data(payload) span.set_data(:payload, payload) span.set_http_status(payload[:status]) end end end end end end end sentry-rails-5.28.0/lib/sentry/rails/tracing/active_support_subscriber.rb0000644000004100000410000000347415070254562026762 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/tracing/abstract_subscriber" module Sentry module Rails module Tracing class ActiveSupportSubscriber < AbstractSubscriber READ_EVENT_NAMES = %w[ cache_read.active_support ].freeze WRITE_EVENT_NAMES = %w[ cache_write.active_support cache_increment.active_support cache_decrement.active_support ].freeze REMOVE_EVENT_NAMES = %w[ cache_delete.active_support ].freeze FLUSH_EVENT_NAMES = %w[ cache_prune.active_support ].freeze EVENT_NAMES = READ_EVENT_NAMES + WRITE_EVENT_NAMES + REMOVE_EVENT_NAMES + FLUSH_EVENT_NAMES SPAN_ORIGIN = "auto.cache.rails" def self.subscribe! subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload| record_on_current_span( op: operation_name(event_name), origin: SPAN_ORIGIN, start_timestamp: payload[START_TIMESTAMP_NAME], description: payload[:store], duration: duration ) do |span| span.set_data("cache.key", [*payload[:key]].select { |key| Utils::EncodingHelper.valid_utf_8?(key) }) span.set_data("cache.hit", payload[:hit] == true) # Handle nil case end end end def self.operation_name(event_name) case when READ_EVENT_NAMES.include?(event_name) "cache.get" when WRITE_EVENT_NAMES.include?(event_name) "cache.put" when REMOVE_EVENT_NAMES.include?(event_name) "cache.remove" when FLUSH_EVENT_NAMES.include?(event_name) "cache.flush" else "other" end end end end end end sentry-rails-5.28.0/lib/sentry/rails/tracing/abstract_subscriber.rb0000644000004100000410000000333715070254562025514 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails module Tracing class AbstractSubscriber class << self def subscribe! raise NotImplementedError end def unsubscribe! self::EVENT_NAMES.each do |name| ActiveSupport::Notifications.unsubscribe(name) end end if ::Rails.version.to_i == 5 def subscribe_to_event(event_names) event_names.each do |event_name| ActiveSupport::Notifications.subscribe(event_name) do |*args| next unless Tracing.get_current_transaction event = ActiveSupport::Notifications::Event.new(*args) yield(event_name, event.duration, event.payload) end end end else def subscribe_to_event(event_names) event_names.each do |event_name| ActiveSupport::Notifications.subscribe(event_name) do |event| next unless Tracing.get_current_transaction yield(event_name, event.duration, event.payload) end end end end def record_on_current_span(duration:, **options) return unless options[:start_timestamp] Sentry.with_child_span(**options) do |child_span| # duration in ActiveSupport is computed in millisecond # so we need to covert it as second before calculating the timestamp child_span.set_timestamp(child_span.start_timestamp + duration / 1000) yield(child_span) if block_given? end end end end end end end sentry-rails-5.28.0/lib/sentry/rails/tracing/active_record_subscriber.rb0000644000004100000410000001121415070254562026513 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/tracing/abstract_subscriber" module Sentry module Rails module Tracing class ActiveRecordSubscriber < AbstractSubscriber EVENT_NAMES = ["sql.active_record"].freeze SPAN_PREFIX = "db." SPAN_ORIGIN = "auto.db.rails" EXCLUDED_EVENTS = ["SCHEMA", "TRANSACTION"].freeze SUPPORT_SOURCE_LOCATION = ActiveSupport::BacktraceCleaner.method_defined?(:clean_frame) if SUPPORT_SOURCE_LOCATION class_attribute :backtrace_cleaner, default: (ActiveSupport::BacktraceCleaner.new.tap do |cleaner| cleaner.add_silencer { |line| line.include?("sentry-ruby/lib") || line.include?("sentry-rails/lib") } end) end class << self def subscribe! record_query_source = SUPPORT_SOURCE_LOCATION && Sentry.configuration.rails.enable_db_query_source query_source_threshold = Sentry.configuration.rails.db_query_source_threshold_ms subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload| next if EXCLUDED_EVENTS.include? payload[:name] record_on_current_span( op: SPAN_PREFIX + event_name, origin: SPAN_ORIGIN, start_timestamp: payload[START_TIMESTAMP_NAME], description: payload[:sql], duration: duration ) do |span| span.set_tag(:cached, true) if payload.fetch(:cached, false) # cached key is only set for hits in the QueryCache, from Rails 5.1 connection = payload[:connection] if payload[:connection_id] span.set_data(:connection_id, payload[:connection_id]) # we fallback to the base connection on rails < 6.0.0 since the payload doesn't have it connection ||= ActiveRecord::Base.connection_pool.connections.find { |conn| conn.object_id == payload[:connection_id] } end next unless connection db_config = if connection.pool.respond_to?(:db_config) connection.pool.db_config.configuration_hash elsif connection.pool.respond_to?(:spec) connection.pool.spec.config end next unless db_config span.set_data(Span::DataConventions::DB_SYSTEM, db_config[:adapter]) if db_config[:adapter] span.set_data(Span::DataConventions::DB_NAME, db_config[:database]) if db_config[:database] span.set_data(Span::DataConventions::SERVER_ADDRESS, db_config[:host]) if db_config[:host] span.set_data(Span::DataConventions::SERVER_PORT, db_config[:port]) if db_config[:port] span.set_data(Span::DataConventions::SERVER_SOCKET_ADDRESS, db_config[:socket]) if db_config[:socket] next unless record_query_source # both duration and query_source_threshold are in ms next unless duration >= query_source_threshold source_location = query_source_location if source_location backtrace_line = Sentry::Backtrace::Line.parse(source_location) span.set_data(Span::DataConventions::FILEPATH, backtrace_line.file) if backtrace_line.file span.set_data(Span::DataConventions::LINENO, backtrace_line.number) if backtrace_line.number span.set_data(Span::DataConventions::FUNCTION, backtrace_line.method) if backtrace_line.method # Only JRuby has namespace in the backtrace span.set_data(Span::DataConventions::NAMESPACE, backtrace_line.module_name) if backtrace_line.module_name end end end end # Thread.each_caller_location is an API added in Ruby 3.2 that doesn't always collect the entire stack like # Kernel#caller or #caller_locations do. See https://github.com/rails/rails/pull/49095 for more context. if SUPPORT_SOURCE_LOCATION && Thread.respond_to?(:each_caller_location) def query_source_location Thread.each_caller_location do |location| frame = backtrace_cleaner.clean_frame(location) return frame if frame end nil end else # Since Sentry is mostly used in production, we don't want to fallback to the slower implementation # and adds potentially big overhead to the application. def query_source_location nil end end end end end end end sentry-rails-5.28.0/lib/sentry/rails/tracing/action_view_subscriber.rb0000644000004100000410000000136515070254562026217 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/tracing/abstract_subscriber" module Sentry module Rails module Tracing class ActionViewSubscriber < AbstractSubscriber EVENT_NAMES = ["render_template.action_view"].freeze SPAN_PREFIX = "template." SPAN_ORIGIN = "auto.template.rails" def self.subscribe! subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload| record_on_current_span( op: SPAN_PREFIX + event_name, origin: SPAN_ORIGIN, start_timestamp: payload[START_TIMESTAMP_NAME], description: payload[:identifier], duration: duration ) end end end end end end sentry-rails-5.28.0/lib/sentry/rails/action_cable.rb0000644000004100000410000000670315070254562022442 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails module ActionCableExtensions class ErrorHandler OP_NAME = "websocket.server" SPAN_ORIGIN = "auto.http.rails.actioncable" class << self def capture(connection, transaction_name:, extra_context: nil, &block) return block.call unless Sentry.initialized? # ActionCable's ConnectionStub (for testing) doesn't implement the exact same interfaces as Connection::Base. # One thing that's missing is `env`. So calling `connection.env` direclty will fail in test environments when `stub_connection` is used. # See https://github.com/getsentry/sentry-ruby/pull/1684 for more information. env = connection.respond_to?(:env) ? connection.env : {} Sentry.with_scope do |scope| scope.set_rack_env(env) scope.set_context("action_cable", extra_context) if extra_context scope.set_transaction_name(transaction_name, source: :view) transaction = start_transaction(env, scope) scope.set_span(transaction) if transaction begin result = block.call finish_transaction(transaction, 200) result rescue Exception => e # rubocop:disable Lint/RescueException Sentry::Rails.capture_exception(e) finish_transaction(transaction, 500) raise end end end def start_transaction(env, scope) options = { name: scope.transaction_name, source: scope.transaction_source, op: OP_NAME, origin: SPAN_ORIGIN } transaction = Sentry.continue_trace(env, **options) Sentry.start_transaction(transaction: transaction, **options) end def finish_transaction(transaction, status_code) return unless transaction transaction.set_http_status(status_code) transaction.finish end end end module Connection private def handle_open ErrorHandler.capture(self, transaction_name: "#{self.class.name}#connect") do super end end def handle_close ErrorHandler.capture(self, transaction_name: "#{self.class.name}#disconnect") do super end end end module Channel module Subscriptions def self.included(base) base.class_eval do set_callback :subscribe, :around, ->(_, block) { sentry_capture(:subscribed, &block) }, prepend: true set_callback :unsubscribe, :around, ->(_, block) { sentry_capture(:unsubscribed, &block) }, prepend: true end end private def sentry_capture(hook, &block) extra_context = { params: params } ErrorHandler.capture(connection, transaction_name: "#{self.class.name}##{hook}", extra_context: extra_context, &block) end end module Actions private def dispatch_action(action, data) extra_context = { params: params, data: data } ErrorHandler.capture(connection, transaction_name: "#{self.class.name}##{action}", extra_context: extra_context) do super end end end end end end end sentry-rails-5.28.0/lib/sentry/rails/engine.rb0000644000004100000410000000016515070254562021300 0ustar www-datawww-data# frozen_string_literal: true module Sentry class Engine < ::Rails::Engine isolate_namespace Sentry end end sentry-rails-5.28.0/lib/sentry/rails/log_subscribers/0000755000004100000410000000000015070254562022673 5ustar www-datawww-datasentry-rails-5.28.0/lib/sentry/rails/log_subscribers/action_controller_subscriber.rb0000644000004100000410000000676215070254562031176 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/log_subscriber" require "sentry/rails/log_subscribers/parameter_filter" module Sentry module Rails module LogSubscribers # LogSubscriber for ActionController events that captures HTTP request processing # and logs them using Sentry's structured logging system. # # This subscriber captures process_action.action_controller events and formats them # with relevant request information including controller, action, HTTP status, # request parameters, and performance metrics. # # @example Usage # # Enable structured logging for ActionController # Sentry.init do |config| # config.enable_logs = true # config.rails.structured_logging = true # config.rails.structured_logging.subscribers = { action_controller: Sentry::Rails::LogSubscribers::ActionControllerSubscriber } # end class ActionControllerSubscriber < Sentry::Rails::LogSubscriber include ParameterFilter # Handle process_action.action_controller events # # @param event [ActiveSupport::Notifications::Event] The controller action event def process_action(event) payload = event.payload controller = payload[:controller] action = payload[:action] status = extract_status(payload) attributes = { controller: controller, action: action, duration_ms: duration_ms(event), method: payload[:method], path: payload[:path], format: payload[:format] } attributes[:status] = status if status if payload[:view_runtime] attributes[:view_runtime_ms] = payload[:view_runtime].round(2) end if payload[:db_runtime] attributes[:db_runtime_ms] = payload[:db_runtime].round(2) end if Sentry.configuration.send_default_pii && payload[:params] filtered_params = filter_sensitive_params(payload[:params]) attributes[:params] = filtered_params unless filtered_params.empty? end level = level_for_request(payload) message = "#{controller}##{action}" log_structured_event( message: message, level: level, attributes: attributes ) end private def extract_status(payload) if payload[:status] payload[:status] elsif payload[:exception] case payload[:exception].first when "ActionController::RoutingError" 404 when "ActionController::BadRequest" 400 else 500 end end end def level_for_request(payload) status = payload[:status] # In Rails < 6.0 status is not set when an action raised an exception if status.nil? && payload[:exception] case payload[:exception].first when "ActionController::RoutingError" :warn when "ActionController::BadRequest" :warn else :error end elsif status.nil? :info elsif status >= 200 && status < 400 :info elsif status >= 400 && status < 500 :warn elsif status >= 500 :error else :info end end end end end end sentry-rails-5.28.0/lib/sentry/rails/log_subscribers/active_job_subscriber.rb0000644000004100000410000001112315070254562027546 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/log_subscriber" require "sentry/rails/log_subscribers/parameter_filter" module Sentry module Rails module LogSubscribers # LogSubscriber for ActiveJob events that captures background job execution # and logs them using Sentry's structured logging system. # # This subscriber captures various ActiveJob events including job execution, # enqueueing, retries, and failures with relevant job information. # # @example Usage # # Enable structured logging for ActiveJob # Sentry.init do |config| # config.enable_logs = true # config.rails.structured_logging = true # config.rails.structured_logging.subscribers = { active_job: Sentry::Rails::LogSubscribers::ActiveJobSubscriber } # end class ActiveJobSubscriber < Sentry::Rails::LogSubscriber include ParameterFilter # Handle perform.active_job events # # @param event [ActiveSupport::Notifications::Event] The job performance event def perform(event) job = event.payload[:job] duration = duration_ms(event) attributes = { job_class: job.class.name, job_id: job.job_id, queue_name: job.queue_name, duration_ms: duration, executions: job.executions, priority: job.priority } attributes[:adapter] = job.class.queue_adapter.class.name if job.scheduled_at attributes[:scheduled_at] = job.scheduled_at.iso8601 attributes[:delay_ms] = ((Time.current - job.scheduled_at) * 1000).round(2) end if Sentry.configuration.send_default_pii && job.arguments.present? filtered_args = filter_sensitive_arguments(job.arguments) attributes[:arguments] = filtered_args unless filtered_args.empty? end message = "Job performed: #{job.class.name}" log_structured_event( message: message, level: :info, attributes: attributes ) end # Handle enqueue.active_job events # # @param event [ActiveSupport::Notifications::Event] The job enqueue event def enqueue(event) job = event.payload[:job] attributes = { job_class: job.class.name, job_id: job.job_id, queue_name: job.queue_name, priority: job.priority } attributes[:adapter] = job.class.queue_adapter.class.name if job.class.respond_to?(:queue_adapter) if job.scheduled_at attributes[:scheduled_at] = job.scheduled_at.iso8601 attributes[:delay_seconds] = (job.scheduled_at - Time.current).round(2) end message = "Job enqueued: #{job.class.name}" log_structured_event( message: message, level: :info, attributes: attributes ) end def retry_stopped(event) job = event.payload[:job] error = event.payload[:error] attributes = { job_class: job.class.name, job_id: job.job_id, queue_name: job.queue_name, executions: job.executions, error_class: error.class.name, error_message: error.message } message = "Job retry stopped: #{job.class.name}" log_structured_event( message: message, level: :error, attributes: attributes ) end def discard(event) job = event.payload[:job] error = event.payload[:error] attributes = { job_class: job.class.name, job_id: job.job_id, queue_name: job.queue_name, executions: job.executions } attributes[:error_class] = error.class.name if error attributes[:error_message] = error.message if error message = "Job discarded: #{job.class.name}" log_structured_event( message: message, level: :warn, attributes: attributes ) end private def filter_sensitive_arguments(arguments) return [] unless arguments.is_a?(Array) arguments.map do |arg| case arg when Hash filter_sensitive_params(arg) when String arg.length > 100 ? "[FILTERED: #{arg.length} chars]" : arg else arg end end end end end end end sentry-rails-5.28.0/lib/sentry/rails/log_subscribers/parameter_filter.rb0000644000004100000410000000353215070254562026550 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails module LogSubscribers # Shared utility module for filtering sensitive parameters in log subscribers. # # This module provides consistent parameter filtering across all Sentry Rails # log subscribers, leveraging Rails' built-in parameter filtering when available. # It automatically detects the correct Rails parameter filtering API based on # the Rails version and includes the appropriate implementation module. # # @example Usage in a log subscriber # class MySubscriber < Sentry::Rails::LogSubscriber # include Sentry::Rails::LogSubscribers::ParameterFilter # # def my_event(event) # if Sentry.configuration.send_default_pii && event.payload[:params] # filtered_params = filter_sensitive_params(event.payload[:params]) # attributes[:params] = filtered_params unless filtered_params.empty? # end # end # end module ParameterFilter EMPTY_HASH = {}.freeze if ::Rails.version.to_f >= 6.0 def self.backend ActiveSupport::ParameterFilter end else def self.backend ActionDispatch::Http::ParameterFilter end end # Filter sensitive parameters from a hash, respecting Rails configuration. # # @param params [Hash] The parameters to filter # @return [Hash] Filtered parameters with sensitive data removed def filter_sensitive_params(params) return EMPTY_HASH unless params.is_a?(Hash) filter_parameters = ::Rails.application.config.filter_parameters parameter_filter = ParameterFilter.backend.new(filter_parameters) parameter_filter.filter(params) end end end end end sentry-rails-5.28.0/lib/sentry/rails/log_subscribers/active_record_subscriber.rb0000644000004100000410000001046015070254562030255 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/log_subscriber" require "sentry/rails/log_subscribers/parameter_filter" module Sentry module Rails module LogSubscribers # LogSubscriber for ActiveRecord events that captures database queries # and logs them using Sentry's structured logging system. # # This subscriber captures sql.active_record events and formats them # with relevant database information including SQL queries, duration, # database configuration, and caching information. # # @example Usage # # Automatically attached when structured logging is enabled for :active_record # Sentry.init do |config| # config.enable_logs = true # config.rails.structured_logging = true # config.rails.structured_logging.subscribers = { active_record: Sentry::Rails::LogSubscribers::ActiveRecordSubscriber } # end class ActiveRecordSubscriber < Sentry::Rails::LogSubscriber include ParameterFilter EXCLUDED_NAMES = ["SCHEMA", "TRANSACTION"].freeze # Handle sql.active_record events # # @param event [ActiveSupport::Notifications::Event] The SQL event def sql(event) return if EXCLUDED_NAMES.include?(event.payload[:name]) sql = event.payload[:sql] statement_name = event.payload[:name] # Rails 5.0.0 doesn't include :cached in the payload, it was added in Rails 5.1 cached = event.payload.fetch(:cached, false) connection_id = event.payload[:connection_id] db_config = extract_db_config(event.payload) attributes = { sql: sql, duration_ms: duration_ms(event), cached: cached } attributes[:statement_name] = statement_name if statement_name && statement_name != "SQL" attributes[:connection_id] = connection_id if connection_id add_db_config_attributes(attributes, db_config) message = build_log_message(statement_name) log_structured_event( message: message, level: :info, attributes: attributes ) end private def build_log_message(statement_name) if statement_name && statement_name != "SQL" "Database query: #{statement_name}" else "Database query" end end def extract_db_config(payload) connection = payload[:connection] return unless connection extract_db_config_from_connection(connection) end def add_db_config_attributes(attributes, db_config) return unless db_config attributes[:db_system] = db_config[:adapter] if db_config[:adapter] if db_config[:database] db_name = db_config[:database] if db_config[:adapter] == "sqlite3" && db_name.include?("/") db_name = File.basename(db_name) end attributes[:db_name] = db_name end attributes[:server_address] = db_config[:host] if db_config[:host] attributes[:server_port] = db_config[:port] if db_config[:port] attributes[:server_socket_address] = db_config[:socket] if db_config[:socket] end if ::Rails.version.to_f >= 6.1 def extract_db_config_from_connection(connection) if connection.pool.respond_to?(:db_config) db_config = connection.pool.db_config if db_config.respond_to?(:configuration_hash) return db_config.configuration_hash elsif db_config.respond_to?(:config) return db_config.config end end extract_db_config_fallback(connection) end else # Rails 6.0 and earlier use spec API def extract_db_config_from_connection(connection) if connection.pool.respond_to?(:spec) spec = connection.pool.spec if spec.respond_to?(:config) return spec.config end end extract_db_config_fallback(connection) end end def extract_db_config_fallback(connection) connection.config if connection.respond_to?(:config) end end end end end sentry-rails-5.28.0/lib/sentry/rails/log_subscribers/action_mailer_subscriber.rb0000644000004100000410000000542115070254562030253 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/log_subscriber" require "sentry/rails/log_subscribers/parameter_filter" module Sentry module Rails module LogSubscribers # LogSubscriber for ActionMailer events that captures email delivery # and processing events using Sentry's structured logging system. # # This subscriber captures deliver.action_mailer and process.action_mailer events # and formats them with relevant email information while respecting PII settings. # # @example Usage # # Enable structured logging for ActionMailer # Sentry.init do |config| # config.enable_logs = true # config.rails.structured_logging = true # config.rails.structured_logging.subscribers = { action_mailer: Sentry::Rails::LogSubscribers::ActionMailerSubscriber } # end class ActionMailerSubscriber < Sentry::Rails::LogSubscriber include ParameterFilter # Handle deliver.action_mailer events # # @param event [ActiveSupport::Notifications::Event] The email delivery event def deliver(event) payload = event.payload mailer = payload[:mailer] attributes = { mailer: mailer, duration_ms: duration_ms(event), perform_deliveries: payload[:perform_deliveries] } attributes[:delivery_method] = payload[:delivery_method] if payload[:delivery_method] attributes[:date] = payload[:date].to_s if payload[:date] if Sentry.configuration.send_default_pii attributes[:message_id] = payload[:message_id] if payload[:message_id] end message = "Email delivered via #{mailer}" # Log the structured event log_structured_event( message: message, level: :info, attributes: attributes ) end # Handle process.action_mailer events # # @param event [ActiveSupport::Notifications::Event] The email processing event def process(event) payload = event.payload mailer = payload[:mailer] action = payload[:action] duration = duration_ms(event) attributes = { mailer: mailer, action: action, duration_ms: duration } if Sentry.configuration.send_default_pii && payload[:params] filtered_params = filter_sensitive_params(payload[:params]) attributes[:params] = filtered_params unless filtered_params.empty? end message = "#{mailer}##{action}" log_structured_event( message: message, level: :info, attributes: attributes ) end end end end end sentry-rails-5.28.0/lib/sentry/rails/error_subscriber.rb0000644000004100000410000000223615070254562023410 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails # This is not a user-facing class. You should use it with Rails 7.0's error reporter feature and its interfaces. # See https://github.com/rails/rails/blob/main/activesupport/lib/active_support/error_reporter.rb to learn more about reporting APIs. # If you want Sentry to subscribe to the error reporter, please set `config.rails.register_error_subscriber` to `true`. class ErrorSubscriber SKIP_SOURCES = Regexp.union([/.*_cache_store.active_support/]) def report(error, handled:, severity:, context:, source: nil) tags = { handled: handled } if source return if SKIP_SOURCES.match?(source) tags[:source] = source end if context[:tags].is_a?(Hash) context = context.dup tags.merge!(context.delete(:tags)) end hint = {} if context[:hint].is_a?(Hash) context = context.dup hint.merge!(context.delete(:hint)) end Sentry::Rails.capture_exception(error, level: severity, contexts: { "rails.error" => context }, tags: tags, hint: hint) end end end end sentry-rails-5.28.0/lib/sentry/rails/tracing.rb0000644000004100000410000000451515070254562021465 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails module Tracing START_TIMESTAMP_NAME = :sentry_start_timestamp def self.register_subscribers(subscribers) @subscribers = subscribers end def self.subscribers @subscribers end def self.subscribed_tracing_events @subscribed_tracing_events ||= [] end def self.subscribe_tracing_events # need to avoid duplicated subscription return if @subscribed subscribers.each do |subscriber| subscriber.subscribe! @subscribed_tracing_events ||= [] @subscribed_tracing_events += subscriber::EVENT_NAMES end @subscribed = true end def self.unsubscribe_tracing_events return unless @subscribed subscribers.each(&:unsubscribe!) subscribed_tracing_events.clear @subscribed = false end # this is necessary because instrumentation events don't record absolute start/finish time # so we need to retrieve the correct time this way def self.patch_active_support_notifications unless ::ActiveSupport::Notifications::Instrumenter.ancestors.include?(SentryNotificationExtension) ::ActiveSupport::Notifications::Instrumenter.send(:prepend, SentryNotificationExtension) end SentryNotificationExtension.module_eval do def instrument(name, payload = {}, &block) # only inject timestamp to the events the SDK subscribes to if Tracing.subscribed_tracing_events.include?(name) payload[START_TIMESTAMP_NAME] = Time.now.utc.to_f if name[0] != "!" && payload.is_a?(Hash) end super(name, payload, &block) end end end def self.remove_active_support_notifications_patch if ::ActiveSupport::Notifications::Instrumenter.ancestors.include?(SentryNotificationExtension) SentryNotificationExtension.module_eval do def instrument(name, payload = {}, &block) super end end end end def self.get_current_transaction Sentry.get_current_scope.get_transaction if Sentry.initialized? end # it's just a container for the extended method module SentryNotificationExtension end end end end sentry-rails-5.28.0/lib/sentry/rails/backtrace_cleaner.rb0000644000004100000410000000147015070254562023443 0ustar www-datawww-data# frozen_string_literal: true require "active_support/backtrace_cleaner" require "active_support/core_ext/string/access" module Sentry module Rails class BacktraceCleaner < ActiveSupport::BacktraceCleaner APP_DIRS_PATTERN = /\A(?:\.\/)?(?:app|config|lib|test|\(\w*\))/ RENDER_TEMPLATE_PATTERN = /:in (?:`|').*_\w+_{2,3}\d+_\d+'/ def initialize super # We don't want any default silencers because they're too aggressive remove_silencers! # We don't want any default filters because Rails 7.2 starts shortening the paths. See #2472 remove_filters! add_filter do |line| if line =~ RENDER_TEMPLATE_PATTERN line.sub(RENDER_TEMPLATE_PATTERN, "") else line end end end end end end sentry-rails-5.28.0/lib/sentry/rails/active_job.rb0000644000004100000410000001064015070254562022137 0ustar www-datawww-data# frozen_string_literal: true require "set" module Sentry module Rails module ActiveJobExtensions def perform_now if !Sentry.initialized? || already_supported_by_sentry_integration? super else SentryReporter.record(self) do super end end end def already_supported_by_sentry_integration? Sentry.configuration.rails.skippable_job_adapters.include?(self.class.queue_adapter.class.to_s) end class SentryReporter OP_NAME = "queue.active_job" SPAN_ORIGIN = "auto.queue.active_job" EVENT_HANDLERS = { "enqueue_retry.active_job" => :retry_handler } class << self def record(job, &block) Sentry.with_scope do |scope| begin scope.set_transaction_name(job.class.name, source: :task) transaction = if job.is_a?(::Sentry::SendEventJob) nil else Sentry.start_transaction( name: scope.transaction_name, source: scope.transaction_source, op: OP_NAME, origin: SPAN_ORIGIN ) end scope.set_span(transaction) if transaction yield.tap do finish_sentry_transaction(transaction, 200) end rescue Exception => e # rubocop:disable Lint/RescueException finish_sentry_transaction(transaction, 500) capture_exception(job, e) raise end end end def capture_exception(job, e) Sentry::Rails.capture_exception( e, extra: sentry_context(job), tags: { job_id: job.job_id, provider_job_id: job.provider_job_id } ) end def register_event_handlers EVENT_HANDLERS.each do |name, handler| subscribers << ActiveSupport::Notifications.subscribe(name) do |*args| public_send(handler, *args) end end end def detach_event_handlers subscribers.each do |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) end subscribers.clear end # This handler does not capture error unless `active_job_report_on_retry_error` is true def retry_handler(*args) handle_error_event(*args) do |job, error| return if !Sentry.initialized? || job.already_supported_by_sentry_integration? return unless Sentry.configuration.rails.active_job_report_on_retry_error capture_exception(job, error) end end def handle_error_event(*args) event = ActiveSupport::Notifications::Event.new(*args) yield(event.payload[:job], event.payload[:error]) end def finish_sentry_transaction(transaction, status) return unless transaction transaction.set_http_status(status) transaction.finish end def sentry_context(job) { active_job: job.class.name, arguments: sentry_serialize_arguments(job.arguments), scheduled_at: job.scheduled_at, job_id: job.job_id, provider_job_id: job.provider_job_id, locale: job.locale } end def sentry_serialize_arguments(argument) case argument when Range if (argument.begin || argument.end).is_a?(ActiveSupport::TimeWithZone) argument.to_s else argument.map { |v| sentry_serialize_arguments(v) } end when Hash argument.transform_values { |v| sentry_serialize_arguments(v) } when Array, Enumerable argument.map { |v| sentry_serialize_arguments(v) } when ->(v) { v.respond_to?(:to_global_id) } argument.to_global_id.to_s rescue argument else argument end end private def subscribers @__subscribers__ ||= Set.new end end end end end end sentry-rails-5.28.0/lib/sentry/rails/background_worker.rb0000644000004100000410000000111515070254562023537 0ustar www-datawww-data# frozen_string_literal: true module Sentry class BackgroundWorker module ActiveRecordConnectionPatch def _perform(&block) super(&block) ensure # some applications have partial or even no AR connection if ActiveRecord::Base.connected? # make sure the background worker returns AR connection if it accidentally acquire one during serialization ActiveRecord::Base.connection_pool.release_connection end end end end end Sentry::BackgroundWorker.prepend(Sentry::BackgroundWorker::ActiveRecordConnectionPatch) sentry-rails-5.28.0/lib/sentry/rails/rescued_exception_interceptor.rb0000644000004100000410000000207415070254562026162 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails class RescuedExceptionInterceptor def initialize(app) @app = app end def call(env) return @app.call(env) unless Sentry.initialized? begin @app.call(env) rescue => e env["sentry.rescued_exception"] = e if report_rescued_exceptions? raise e end end def report_rescued_exceptions? # In rare edge cases, `Sentry.configuration` might be `nil` here. # Hence, we use a safe navigation and fallback to a reasonable default # of `true` in case the configuration couldn't be loaded. # See https://github.com/getsentry/sentry-ruby/issues/2386 report_rescued_exceptions = Sentry.configuration&.rails&.report_rescued_exceptions return report_rescued_exceptions unless report_rescued_exceptions.nil? # `true` is the default for `report_rescued_exceptions`, as specified in # `sentry-rails/lib/sentry/rails/configuration.rb`. true end end end end sentry-rails-5.28.0/lib/sentry/rails/breadcrumb/0000755000004100000410000000000015070254562021612 5ustar www-datawww-datasentry-rails-5.28.0/lib/sentry/rails/breadcrumb/monotonic_active_support_logger.rb0000644000004100000410000000242415070254562030634 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/instrument_payload_cleanup_helper" module Sentry module Rails module Breadcrumb module MonotonicActiveSupportLogger class << self include InstrumentPayloadCleanupHelper def add(name, started, _finished, _unique_id, data) # skip Rails' internal events return if name.start_with?("!") if data.is_a?(Hash) # we should only mutate the copy of the data data = data.dup cleanup_data(data) end crumb = Sentry::Breadcrumb.new( data: data, category: name, timestamp: started.to_i ) Sentry.add_breadcrumb(crumb) end def inject @subscriber = ::ActiveSupport::Notifications.monotonic_subscribe(/.*/) do |name, started, finished, unique_id, data| # we only record events that has a float as started timestamp if started.is_a?(Float) add(name, started, finished, unique_id, data) end end end def detach ::ActiveSupport::Notifications.unsubscribe(@subscriber) end end end end end end sentry-rails-5.28.0/lib/sentry/rails/breadcrumb/active_support_logger.rb0000644000004100000410000000217715070254562026554 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails module Breadcrumb module ActiveSupportLogger class << self def add(name, started, _finished, _unique_id, data) # skip Rails' internal events return if name.start_with?("!") if data.is_a?(Hash) data = data.slice(*@allowed_keys[name]) end crumb = Sentry::Breadcrumb.new( data: data, category: name, timestamp: started.to_i ) Sentry.add_breadcrumb(crumb) end def inject(allowed_keys) @allowed_keys = allowed_keys @subscriber = ::ActiveSupport::Notifications.subscribe(/.*/) do |name, started, finished, unique_id, data| # we only record events that has a started timestamp if started.is_a?(Time) add(name, started, finished, unique_id, data) end end end def detach ::ActiveSupport::Notifications.unsubscribe(@subscriber) end end end end end end sentry-rails-5.28.0/lib/sentry/rails/version.rb0000644000004100000410000000013515070254562021515 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails VERSION = "5.28.0" end end sentry-rails-5.28.0/lib/sentry/rails/capture_exceptions.rb0000644000004100000410000000346315070254562023743 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails class CaptureExceptions < Sentry::Rack::CaptureExceptions RAILS_7_1 = Gem::Version.new(::Rails.version) >= Gem::Version.new("7.1.0.alpha") SPAN_ORIGIN = "auto.http.rails" def initialize(_) super if Sentry.initialized? @assets_regexp = Sentry.configuration.rails.assets_regexp end end private def collect_exception(env) return nil if env["sentry.already_captured"] super || env["action_dispatch.exception"] || env["sentry.rescued_exception"] end def transaction_op "http.server" end def capture_exception(exception, env) # the exception will be swallowed by ShowExceptions middleware return if show_exceptions?(exception, env) && !Sentry.configuration.rails.report_rescued_exceptions Sentry::Rails.capture_exception(exception).tap do |event| env[ERROR_EVENT_ID_KEY] = event.event_id if event end end def start_transaction(env, scope) options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op, origin: SPAN_ORIGIN } if @assets_regexp && scope.transaction_name.match?(@assets_regexp) options.merge!(sampled: false) end transaction = Sentry.continue_trace(env, **options) Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options) end def show_exceptions?(exception, env) request = ActionDispatch::Request.new(env) if RAILS_7_1 ActionDispatch::ExceptionWrapper.new(nil, exception).show?(request) else request.show_exceptions? end end end end end sentry-rails-5.28.0/lib/sentry/rails/log_subscriber.rb0000644000004100000410000000516015070254562023037 0ustar www-datawww-data# frozen_string_literal: true require "active_support/log_subscriber" module Sentry module Rails # Base class for Sentry log subscribers that extends ActiveSupport::LogSubscriber # to provide structured logging capabilities for Rails components. # # This class follows Rails' LogSubscriber pattern and provides common functionality # for capturing Rails instrumentation events and logging them through Sentry's # structured logging system. # # @example Creating a custom log subscriber # class MySubscriber < Sentry::Rails::LogSubscriber # attach_to :my_component # # def my_event(event) # log_structured_event( # message: "My event occurred", # level: :info, # attributes: { # duration_ms: event.duration, # custom_data: event.payload[:custom_data] # } # ) # end # end class LogSubscriber < ActiveSupport::LogSubscriber ORIGIN = "auto.logger.rails.log_subscriber" class << self if ::Rails.version.to_f < 6.0 # Rails 5.x does not provide detach_from def detach_from(namespace, notifications = ActiveSupport::Notifications) listeners = public_instance_methods(false) .flat_map { |key| notifications.notifier.listeners_for("#{key}.#{namespace}") } .select { |listener| listener.instance_variable_get(:@delegate).is_a?(self) } listeners.map do |listener| notifications.notifier.unsubscribe(listener) end end end end protected # Log a structured event using Sentry's structured logger # # @param message [String] The log message # @param level [Symbol] The log level (:trace, :debug, :info, :warn, :error, :fatal) # @param attributes [Hash] Additional structured attributes to include # @param origin [String] The origin of the log event def log_structured_event(message:, level: :info, attributes: {}, origin: ORIGIN) Sentry.logger.public_send(level, message, **attributes, origin: origin) rescue => e # Silently handle any errors in logging to avoid breaking the application Sentry.configuration.sdk_logger.debug("Failed to log structured event: #{e.message}") end # Calculate duration in milliseconds from an event # # @param event [ActiveSupport::Notifications::Event] The event # @return [Float] Duration in milliseconds def duration_ms(event) event.duration.round(2) end end end end sentry-rails-5.28.0/lib/sentry/rails/railtie.rb0000644000004100000410000001406715070254562021472 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/capture_exceptions" require "sentry/rails/rescued_exception_interceptor" require "sentry/rails/backtrace_cleaner" module Sentry class Railtie < ::Rails::Railtie # middlewares can't be injected after initialize initializer "sentry.use_rack_middleware" do |app| # placed after all the file-sending middlewares so we can avoid unnecessary transactions app.config.middleware.insert_after ActionDispatch::ShowExceptions, Sentry::Rails::CaptureExceptions # need to place as close to DebugExceptions as possible to intercept most of the exceptions, including those raised by middlewares app.config.middleware.insert_after ActionDispatch::DebugExceptions, Sentry::Rails::RescuedExceptionInterceptor end # because the extension works by registering the around_perform callback, it should always be run # before the application is eager-loaded (before user's jobs register their own callbacks) # See https://github.com/getsentry/sentry-ruby/issues/1249#issuecomment-853871871 for the detail explanation initializer "sentry.extend_active_job", before: :eager_load! do |app| ActiveSupport.on_load(:active_job) do require "sentry/rails/active_job" prepend Sentry::Rails::ActiveJobExtensions end end initializer "sentry.extend_action_cable", before: :eager_load! do |app| ActiveSupport.on_load(:action_cable_connection) do require "sentry/rails/action_cable" prepend Sentry::Rails::ActionCableExtensions::Connection end ActiveSupport.on_load(:action_cable_channel) do require "sentry/rails/action_cable" include Sentry::Rails::ActionCableExtensions::Channel::Subscriptions prepend Sentry::Rails::ActionCableExtensions::Channel::Actions end end config.after_initialize do |app| next unless Sentry.initialized? configure_project_root configure_trusted_proxies configure_cron_timezone extend_controller_methods if defined?(ActionController) patch_background_worker if defined?(ActiveRecord) override_streaming_reporter if defined?(ActionView) setup_backtrace_cleanup_callback inject_breadcrumbs_logger activate_tracing activate_structured_logging register_error_subscriber(app) if ::Rails.version.to_f >= 7.0 && Sentry.configuration.rails.register_error_subscriber # Presence of ActiveJob is no longer a reliable cue if defined?(Sentry::Rails::ActiveJobExtensions) Sentry::Rails::ActiveJobExtensions::SentryReporter.register_event_handlers end end runner do next unless Sentry.initialized? Sentry.configuration.background_worker_threads = 0 at_exit do # TODO: Add a condition for Rails 7.1 to avoid confliction with https://github.com/rails/rails/pull/44999 if $ERROR_INFO && !($ERROR_INFO.is_a?(SystemExit) && $ERROR_INFO.success?) Sentry::Rails.capture_exception($ERROR_INFO, tags: { source: "runner" }) end end end def configure_project_root Sentry.configuration.project_root = ::Rails.root.to_s end def configure_trusted_proxies Sentry.configuration.trusted_proxies += Array(::Rails.application.config.action_dispatch.trusted_proxies) end def configure_cron_timezone tz_info = ::ActiveSupport::TimeZone.find_tzinfo(::Rails.application.config.time_zone) Sentry.configuration.cron.default_timezone = tz_info.name end def extend_controller_methods require "sentry/rails/controller_methods" require "sentry/rails/controller_transaction" require "sentry/rails/overrides/streaming_reporter" ActiveSupport.on_load :action_controller do include Sentry::Rails::ControllerMethods include Sentry::Rails::ControllerTransaction ActionController::Live.send(:prepend, Sentry::Rails::Overrides::StreamingReporter) end end def patch_background_worker require "sentry/rails/background_worker" end def inject_breadcrumbs_logger if Sentry.configuration.breadcrumbs_logger.include?(:active_support_logger) require "sentry/rails/breadcrumb/active_support_logger" Sentry::Rails::Breadcrumb::ActiveSupportLogger.inject(Sentry.configuration.rails.active_support_logger_subscription_items) end if Sentry.configuration.breadcrumbs_logger.include?(:monotonic_active_support_logger) return warn "Usage of `monotonic_active_support_logger` require a version of Rails >= 6.1, please upgrade your Rails version or use another logger" if ::Rails.version.to_f < 6.1 require "sentry/rails/breadcrumb/monotonic_active_support_logger" Sentry::Rails::Breadcrumb::MonotonicActiveSupportLogger.inject end end def setup_backtrace_cleanup_callback backtrace_cleaner = Sentry::Rails::BacktraceCleaner.new Sentry.configuration.backtrace_cleanup_callback ||= lambda do |backtrace| backtrace_cleaner.clean(backtrace) end end def override_streaming_reporter require "sentry/rails/overrides/streaming_reporter" ActiveSupport.on_load :action_view do ActionView::StreamingTemplateRenderer::Body.send(:prepend, Sentry::Rails::Overrides::StreamingReporter) end end def activate_tracing if Sentry.configuration.tracing_enabled? && Sentry.configuration.instrumenter == :sentry subscribers = Sentry.configuration.rails.tracing_subscribers Sentry::Rails::Tracing.register_subscribers(subscribers) Sentry::Rails::Tracing.subscribe_tracing_events Sentry::Rails::Tracing.patch_active_support_notifications end end def activate_structured_logging if Sentry.configuration.rails.structured_logging.enabled? && Sentry.configuration.enable_logs Sentry::Rails::StructuredLogging.attach(Sentry.configuration.rails.structured_logging) end end def register_error_subscriber(app) require "sentry/rails/error_subscriber" app.executor.error_reporter.subscribe(Sentry::Rails::ErrorSubscriber.new) end end end sentry-rails-5.28.0/lib/sentry/rails/instrument_payload_cleanup_helper.rb0000644000004100000410000000057315070254562027025 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails module InstrumentPayloadCleanupHelper IGNORED_DATA_TYPES = [:request, :response, :headers, :exception, :exception_object, Tracing::START_TIMESTAMP_NAME] def cleanup_data(data) IGNORED_DATA_TYPES.each do |key| data.delete(key) if data.key?(key) end end end end end sentry-rails-5.28.0/lib/sentry/rails/structured_logging.rb0000644000004100000410000000207315070254562023745 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/log_subscriber" require "sentry/rails/log_subscribers/action_controller_subscriber" require "sentry/rails/log_subscribers/active_record_subscriber" require "sentry/rails/log_subscribers/active_job_subscriber" require "sentry/rails/log_subscribers/action_mailer_subscriber" module Sentry module Rails module StructuredLogging class << self def attach(config) config.subscribers.each do |component, subscriber_class| subscriber_class.attach_to component end rescue => e Sentry.configuration.sdk_logger.error("Failed to attach structured loggers: #{e.message}") Sentry.configuration.sdk_logger.error(e.backtrace.join("\n")) end def detach(config) config.subscribers.each do |component, subscriber_class| subscriber_class.detach_from component end rescue => e Sentry.configuration.sdk_logger.debug("Error during detaching loggers: #{e.message}") end end end end end sentry-rails-5.28.0/lib/sentry/rails/overrides/0000755000004100000410000000000015070254562021506 5ustar www-datawww-datasentry-rails-5.28.0/lib/sentry/rails/overrides/streaming_reporter.rb0000644000004100000410000000105715070254562025751 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails module Overrides module StreamingReporter def log_error(exception) Sentry::Rails.capture_exception(exception) super end end module OldStreamingReporter def self.included(base) base.send(:alias_method_chain, :log_error, :raven) end def log_error_with_raven(exception) Sentry::Rails.capture_exception(exception) log_error_without_raven(exception) end end end end end sentry-rails-5.28.0/lib/sentry/rails/controller_methods.rb0000644000004100000410000000113015070254562023732 0ustar www-datawww-data# frozen_string_literal: true module Sentry module Rails module ControllerMethods def capture_message(message, options = {}) with_request_scope do Sentry::Rails.capture_message(message, **options) end end def capture_exception(exception, options = {}) with_request_scope do Sentry::Rails.capture_exception(exception, **options) end end private def with_request_scope Sentry.with_scope do |scope| scope.set_rack_env(request.env) yield end end end end end sentry-rails-5.28.0/lib/sentry-rails.rb0000644000004100000410000000012515070254562020025 0ustar www-datawww-data# frozen_string_literal: true require "sentry/rails/version" require "sentry/rails" sentry-rails-5.28.0/LICENSE.txt0000644000004100000410000000206115070254562016122 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2020 Sentry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sentry-rails-5.28.0/.rspec0000644000004100000410000000006515070254562015416 0ustar www-datawww-data--format documentation --color --require spec_helper sentry-rails-5.28.0/Rakefile0000644000004100000410000000100115070254562015735 0ustar www-datawww-data# frozen_string_literal: true require "bundler/gem_tasks" require_relative "../lib/sentry/test/rake_tasks" Sentry::Test::RakeTasks.define_spec_tasks( spec_pattern: "spec/sentry/**/*_spec.rb", spec_rspec_opts: "--order rand --format progress", isolated_specs_pattern: "spec/isolated/**/*_spec.rb", isolated_rspec_opts: "--format progress" ) Sentry::Test::RakeTasks.define_versioned_specs_task( rspec_opts: "--order rand --format progress" ) task default: [:spec, :"spec:versioned", :"spec:isolated"] sentry-rails-5.28.0/Gemfile0000644000004100000410000000343015070254562015573 0ustar www-datawww-data# frozen_string_literal: true source "https://rubygems.org" git_source(:github) { |name| "https://github.com/#{name}.git" } eval_gemfile "../Gemfile.dev" # Specify your gem's dependencies in sentry-ruby.gemspec gemspec gem "sentry-ruby", path: "../sentry-ruby" platform :jruby do gem "activerecord-jdbcmysql-adapter" gem "jdbc-sqlite3" end ruby_version = Gem::Version.new(RUBY_VERSION) rails_version = ENV.fetch("RAILS_VERSION") do if ruby_version >= Gem::Version.new("3.2") "8.0" elsif ruby_version >= Gem::Version.new("3.1") "7.2" elsif ruby_version >= Gem::Version.new("2.7") "7.1" elsif ruby_version >= Gem::Version.new("2.4") "5.2" end end rails_version = Gem::Version.new(rails_version) gem "rails", "~> #{rails_version}" if rails_version >= Gem::Version.new("8.0.0") gem "rspec-rails" gem "sqlite3", "~> 2.1.1", platform: :ruby elsif rails_version >= Gem::Version.new("7.1.0") gem "psych", "~> 4.0.0" gem "rspec-rails" gem "sqlite3", "~> 1.7.3", platform: :ruby elsif rails_version >= Gem::Version.new("6.1.0") gem "rspec-rails", "~> 4.0" if ruby_version >= Gem::Version.new("2.7.0") gem "sqlite3", "~> 1.7.3", platform: :ruby else gem "sqlite3", "~> 1.6.9", platform: :ruby end else gem "psych", "~> 3.0.0" gem "rspec-rails", "~> 4.0" if rails_version >= Gem::Version.new("6.0.0") gem "sqlite3", "~> 1.4.0", platform: :ruby else gem "sqlite3", "~> 1.3.0", platform: :ruby end end if ruby_version < Gem::Version.new("2.5.0") # https://github.com/flavorjones/loofah/pull/267 # loofah changed the required ruby version in a patch so we need to explicitly pin it gem "loofah", "2.20.0" end gem "mini_magick" gem "sprockets-rails" gem "benchmark-ips" gem "benchmark_driver" gem "benchmark-ipsa" gem "benchmark-memory" sentry-rails-5.28.0/sentry-rails.gemspec0000644000004100000410000000244115070254562020302 0ustar www-datawww-data# frozen_string_literal: true require_relative "lib/sentry/rails/version" Gem::Specification.new do |spec| spec.name = "sentry-rails" spec.version = Sentry::Rails::VERSION spec.authors = ["Sentry Team"] spec.description = spec.summary = "A gem that provides Rails integration for the Sentry error logger" spec.email = "accounts@sentry.io" spec.license = 'MIT' spec.platform = Gem::Platform::RUBY spec.required_ruby_version = '>= 2.4' spec.extra_rdoc_files = ["README.md", "LICENSE.txt"] spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples|\.rubocop\.yml)'`.split("\n") github_root_uri = 'https://github.com/getsentry/sentry-ruby' spec.homepage = "#{github_root_uri}/tree/#{spec.version}/#{spec.name}" spec.metadata = { "homepage_uri" => spec.homepage, "source_code_uri" => spec.homepage, "changelog_uri" => "#{github_root_uri}/blob/#{spec.version}/CHANGELOG.md", "bug_tracker_uri" => "#{github_root_uri}/issues", "documentation_uri" => "http://www.rubydoc.info/gems/#{spec.name}/#{spec.version}" } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.add_dependency "railties", ">= 5.0" spec.add_dependency "sentry-ruby", "~> 5.28.0" end sentry-rails-5.28.0/app/0000755000004100000410000000000015070254562015060 5ustar www-datawww-datasentry-rails-5.28.0/app/jobs/0000755000004100000410000000000015070254562016015 5ustar www-datawww-datasentry-rails-5.28.0/app/jobs/sentry/0000755000004100000410000000000015070254562017341 5ustar www-datawww-datasentry-rails-5.28.0/app/jobs/sentry/send_event_job.rb0000644000004100000410000000200215070254562022644 0ustar www-datawww-data# frozen_string_literal: true if defined?(ActiveJob) module Sentry parent_job = if defined?(::ApplicationJob) && ::ApplicationJob.ancestors.include?(::ActiveJob::Base) ::ApplicationJob else ::ActiveJob::Base end class SendEventJob < parent_job # the event argument is usually large and creates noise self.log_arguments = false if respond_to?(:log_arguments=) # this will prevent infinite loop when there's an issue deserializing SentryJob if respond_to?(:discard_on) discard_on ActiveJob::DeserializationError else # mimic what discard_on does for Rails 5.0 rescue_from ActiveJob::DeserializationError do |exception| logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{exception.cause.inspect}." end end def perform(event, hint = {}) Sentry.send_event(event, hint) end end end else module Sentry class SendEventJob; end end end sentry-rails-5.28.0/README.md0000644000004100000410000000575515070254562015573 0ustar www-datawww-data


# sentry-rails, the Rails integration for Sentry's Ruby client --- [![Gem Version](https://img.shields.io/gem/v/sentry-rails.svg)](https://rubygems.org/gems/sentry-rails) ![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/sentry_rails_test.yml/badge.svg) [![Coverage Status](https://img.shields.io/codecov/c/github/getsentry/sentry-ruby/master?logo=codecov)](https://codecov.io/gh/getsentry/sentry-ruby/branch/master) [![Gem](https://img.shields.io/gem/dt/sentry-rails.svg)](https://rubygems.org/gems/sentry-rails/) [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sentry-rails&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=sentry-rails&package-manager=bundler&version-scheme=semver) [Documentation](https://docs.sentry.io/platforms/ruby/guides/rails/) | [Bug Tracker](https://github.com/getsentry/sentry-ruby/issues) | [Forum](https://forum.sentry.io/) | IRC: irc.freenode.net, #sentry The official Ruby-language client and integration layer for the [Sentry](https://github.com/getsentry/sentry) error reporting API. ## Requirements This integration requires Rails version >= 5.0 and Ruby version >= 2.4 ## Getting Started ### Install ```ruby gem "sentry-rails" ``` ### Integration Specific Configuration This gem has a few Rails-specific configuration options ```ruby Sentry.init do |config| # report exceptions rescued by ActionDispatch::ShowExceptions or ActionDispatch::DebugExceptions middlewares # the default value is true config.rails.report_rescued_exceptions = true # this gem also provides a new breadcrumb logger that accepts instrumentations from ActiveSupport # it's not activated by default, but you can enable it with config.breadcrumbs_logger = [:active_support_logger] end ``` ### Performance Monitoring You can activate performance monitoring by enabling traces sampling: ```ruby Sentry.init do |config| # set a uniform sample rate between 0.0 and 1.0 config.traces_sample_rate = 0.2 # or control sampling dynamically config.traces_sampler = lambda do |sampling_context| # sampling_context[:transaction_context] contains the information about the transaction # sampling_context[:parent_sampled] contains the transaction's parent's sample decision true # return value can be a boolean or a float between 0.0 and 1.0 end end ``` Currently, it tracks the following Rails instrumentation events: - ActiveRecord - `sql.active_record` - ActionController - `process_action.action_controller` - ActionView - `render_template.action_view` - `render_partial.action_view` - `render_collection.action_view` To lean more about performance monitoring, please visit the [official documentation](https://docs.sentry.io/platforms/ruby/guides/rails/performance/). sentry-rails-5.28.0/CHANGELOG.md0000644000004100000410000001726315070254562016122 0ustar www-datawww-data# Changelog Individual gem's changelog has been deprecated. Please check the [project changelog](https://github.com/getsentry/sentry-ruby/blob/master/CHANGELOG.md). ## 4.4.0 ### Features - Make tracing subscribers configurable [#1344](https://github.com/getsentry/sentry-ruby/pull/1344) ```ruby # current default: # - Sentry::Rails::Tracing::ActionControllerSubscriber # - Sentry::Rails::Tracing::ActionViewSubscriber # - Sentry::Rails::Tracing::ActiveRecordSubscriber # you can add a new subscriber config.rails.tracing_subscribers << MySubscriber # or replace the set completely config.rails.tracing_subscribers = [MySubscriber] ``` ### Bug Fixes - Report exceptions from the interceptor middleware for exceptions app [#1379](https://github.com/getsentry/sentry-ruby/pull/1379) - Fixes [#1371](https://github.com/getsentry/sentry-ruby/issues/1371) - Re-position CaptureExceptions middleware to reduce tracing noise [#1405](https://github.com/getsentry/sentry-ruby/pull/1405) ## 4.3.4 - Don't assign Rails.logger if it's not present [#1387](https://github.com/getsentry/sentry-ruby/pull/1387) - Fixes [#1386](https://github.com/getsentry/sentry-ruby/issues/1386) ## 4.3.3 - Correctly set the SDK's logger in sentry-rails [#1363](https://github.com/getsentry/sentry-ruby/pull/1363) - Fixes [#1361](https://github.com/getsentry/sentry-ruby/issues/1361) ## 4.3.3-beta.0 - Minimize sentry-rails' dependency requirement [#1352](https://github.com/getsentry/sentry-ruby/pull/1352) ## 4.3.2 - Avoid recording SendEventJob's transaction [#1351](https://github.com/getsentry/sentry-ruby/pull/1351) - Fixes [#1348](https://github.com/getsentry/sentry-ruby/issues/1348) ## 4.3.1 - Only apply background worker patch if ActiveRecord is loaded [#1350](https://github.com/getsentry/sentry-ruby/pull/1350) - Fixes [#1342](https://github.com/getsentry/sentry-ruby/issues/1342) and [#1346](https://github.com/getsentry/sentry-ruby/issues/1346) ## 4.3.0 ### Features - Support performance monitoring on ActiveJob execution [#1304](https://github.com/getsentry/sentry-ruby/pull/1304) ### Bug Fixes - Prevent background workers from holding ActiveRecord connections [#1320](https://github.com/getsentry/sentry-ruby/pull/1320) ## 4.2.2 - Always define Sentry::SendEventJob to avoid eager load issues [#1286](https://github.com/getsentry/sentry-ruby/pull/1286) - Fixes [#1283](https://github.com/getsentry/sentry-ruby/issues/1283) ## 4.2.1 - Add additional checks to SendEventJob's definition [#1275](https://github.com/getsentry/sentry-ruby/pull/1275) - Fixes [#1270](https://github.com/getsentry/sentry-ruby/issues/1270) - Fixes [#1277](https://github.com/getsentry/sentry-ruby/issues/1277) ## 4.2.0 ### Features - Make sentry-rails a Rails engine and provide default job class for async [#1181](https://github.com/getsentry/sentry-ruby/pull/1181) `sentry-rails` now provides a default ActiveJob class for sending events asynchronously. You can use it directly without define your own one: ```ruby config.async = lambda { |event, hint| Sentry::SendEventJob.perform_later(event, hint) } ``` - Add configuration option for trusted proxies [#1126](https://github.com/getsentry/sentry-ruby/pull/1126) `sentry-rails` now injects `Rails.application.config.action_dispatch.trusted_proxies` into `Sentry.configuration.trusted_proxies` automatically. - Allow users to configure ActiveJob adapters to ignore [#1256](https://github.com/getsentry/sentry-ruby/pull/1256) ```ruby # sentry-rails will skip active_job reporting for jobs that use ActiveJob::QueueAdapters::SidekiqAdapter # you should use this option when: # - you don't want to see events from a certain adapter # - you already have a better reporting setup for the adapter (like having `sentry-sidekiq` installed) config.rails.skippable_job_adapters = ["ActiveJob::QueueAdapters::SidekiqAdapter"] ``` - Tag `job_id` and `provider_job_id` on ActiveJob events [#1259](https://github.com/getsentry/sentry-ruby/pull/1259) example of tagged event - Use another method for post initialization callback [#1261](https://github.com/getsentry/sentry-ruby/pull/1261) ### Bug Fixes - Inspect exception cause by default & don't exclude ActiveJob::DeserializationError [#1180](https://github.com/getsentry/sentry-ruby/pull/1180) - Fixes [#1071](https://github.com/getsentry/sentry-ruby/issues/1071) ## 4.1.7 - Use env to carry original transaction name [#1255](https://github.com/getsentry/sentry-ruby/pull/1255) - Fix duration of tracing event in Rails 5 [#1254](https://github.com/getsentry/sentry-ruby/pull/1254) (by @abcang) - Filter out static file transaction [#1247](https://github.com/getsentry/sentry-ruby/pull/1247) ## 4.1.6 - Prevent exceptions app from overriding event's transaction name [#1230](https://github.com/getsentry/sentry-ruby/pull/1230) - Fix project root detection [#1242](https://github.com/getsentry/sentry-ruby/pull/1242) - Use sentry-ruby-core as the main SDK dependency [#1244](https://github.com/getsentry/sentry-ruby/pull/1244) ## 4.1.5 - Add `ActionDispatch::Http::MimeNegotiation::InvalidType` to the list of default ignored Rails exceptions [#1215](https://github.com/getsentry/sentry-ruby/pull/1215) (by @agrobbin) - Continue ActiveJob execution if Sentry is not initialized [#1217](https://github.com/getsentry/sentry-ruby/pull/1217) - Fixes [#1211](https://github.com/getsentry/sentry-ruby/issues/1211) and [#1216](https://github.com/getsentry/sentry-ruby/issues/1216) - Only extend ActiveJob when it's defined [#1218](https://github.com/getsentry/sentry-ruby/pull/1218) - Fixes [#1210](https://github.com/getsentry/sentry-ruby/issues/1210) - Filter out redundant event/payload from breadcrumbs logger [#1222](https://github.com/getsentry/sentry-ruby/pull/1222) - Copy request env before Rails' ShowExceptions middleware [#1223](https://github.com/getsentry/sentry-ruby/pull/1223) - Don't subscribe render_partial and render_collection events [#1224](https://github.com/getsentry/sentry-ruby/pull/1224) ## 4.1.4 - Don't include headers & request info in tracing span or breadcrumb [#1199](https://github.com/getsentry/sentry-ruby/pull/1199) - Don't run RescuedExceptionInterceptor unless Sentry is initialized [#1204](https://github.com/getsentry/sentry-ruby/pull/1204) ## 4.1.3 - Remove DelayedJobAdapter from ignored list [#1179](https://github.com/getsentry/sentry-ruby/pull/1179) ## 4.1.2 - Use middleware instead of method override to handle rescued exceptions [#1168](https://github.com/getsentry/sentry-ruby/pull/1168) - Fixes [#738](https://github.com/getsentry/sentry-ruby/issues/738) - Adopt Integrable module [#1177](https://github.com/getsentry/sentry-ruby/pull/1177) ## 4.1.1 - Use stricter dependency declaration [#1159](https://github.com/getsentry/sentry-ruby/pull/1159) ## 4.1.0 - Merge & rename 2 Rack middlewares [#1147](https://github.com/getsentry/sentry-ruby/pull/1147) - Fixes [#1153](https://github.com/getsentry/sentry-ruby/pull/1153) - Removed `Sentry::Rack::Tracing` middleware and renamed `Sentry::Rack::CaptureException` to `Sentry::Rack::CaptureExceptions` - Tidy up rails integration [#1150](https://github.com/getsentry/sentry-ruby/pull/1150) - Check SDK initialization before running integrations [#1151](https://github.com/getsentry/sentry-ruby/pull/1151) - Fixes [#1145](https://github.com/getsentry/sentry-ruby/pull/1145) ## 4.0.0 - Only documents update for the official release and no API/feature changes. ## 0.3.0 - Major API changes: [1123](https://github.com/getsentry/sentry-ruby/pull/1123) ## 0.2.0 - Multiple fixes and refactorings - Tracing support ## 0.1.2 Fix require reference ## 0.1.1 Release test ## 0.1.0 First version sentry-rails-5.28.0/Makefile0000644000004100000410000000006715070254562015743 0ustar www-datawww-databuild: bundle install gem build sentry-rails.gemspec