appsignal 3.5.4 → 3.5.6
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.
- checksums.yaml +4 -4
- data/.semaphore/semaphore.yml +147 -9
- data/CHANGELOG.md +23 -0
- data/README.md +2 -0
- data/build_matrix.yml +5 -9
- data/ext/Rakefile +7 -1
- data/ext/agent.rb +27 -27
- data/gemfiles/redis-4.gemfile +5 -0
- data/gemfiles/redis-5.gemfile +6 -0
- data/lib/appsignal/cli/diagnose.rb +1 -1
- data/lib/appsignal/config.rb +10 -5
- data/lib/appsignal/demo.rb +1 -1
- data/lib/appsignal/environment.rb +24 -13
- data/lib/appsignal/event_formatter.rb +1 -1
- data/lib/appsignal/extension/jruby.rb +4 -3
- data/lib/appsignal/extension.rb +1 -1
- data/lib/appsignal/helpers/instrumentation.rb +7 -7
- data/lib/appsignal/helpers/metrics.rb +3 -3
- data/lib/appsignal/hooks/redis.rb +1 -0
- data/lib/appsignal/hooks/redis_client.rb +27 -0
- data/lib/appsignal/hooks.rb +3 -2
- data/lib/appsignal/integrations/hanami.rb +1 -1
- data/lib/appsignal/integrations/padrino.rb +1 -1
- data/lib/appsignal/integrations/railtie.rb +1 -1
- data/lib/appsignal/integrations/redis_client.rb +20 -0
- data/lib/appsignal/integrations/sidekiq.rb +1 -1
- data/lib/appsignal/integrations/sinatra.rb +1 -1
- data/lib/appsignal/minutely.rb +4 -4
- data/lib/appsignal/probes/gvl.rb +1 -1
- data/lib/appsignal/probes/helpers.rb +1 -1
- data/lib/appsignal/probes/mri.rb +1 -1
- data/lib/appsignal/probes/sidekiq.rb +5 -5
- data/lib/appsignal/rack/generic_instrumentation.rb +1 -1
- data/lib/appsignal/rack/rails_instrumentation.rb +2 -2
- data/lib/appsignal/rack/sinatra_instrumentation.rb +2 -2
- data/lib/appsignal/rack/streaming_listener.rb +1 -1
- data/lib/appsignal/span.rb +2 -2
- data/lib/appsignal/transaction.rb +11 -11
- data/lib/appsignal/utils/deprecation_message.rb +2 -2
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +37 -31
- data/spec/lib/appsignal/config_spec.rb +2 -2
- data/spec/lib/appsignal/hooks/activejob_spec.rb +1 -1
- data/spec/lib/appsignal/hooks/redis_client_spec.rb +222 -0
- data/spec/lib/appsignal/hooks/redis_spec.rb +98 -76
- data/spec/lib/appsignal/hooks_spec.rb +4 -4
- data/spec/lib/appsignal/integrations/railtie_spec.rb +2 -2
- data/spec/lib/appsignal/integrations/sidekiq_spec.rb +3 -3
- data/spec/lib/appsignal/integrations/sinatra_spec.rb +2 -2
- data/spec/lib/appsignal/minutely_spec.rb +2 -2
- data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +1 -1
- data/spec/lib/appsignal/transaction_spec.rb +4 -4
- data/spec/lib/appsignal_spec.rb +34 -32
- data/spec/spec_helper.rb +1 -1
- data/spec/support/fixtures/projects/valid/config/appsignal.yml +3 -3
- data/spec/support/helpers/config_helpers.rb +6 -2
- data/spec/support/helpers/dependency_helper.rb +9 -1
- data/spec/support/helpers/log_helpers.rb +2 -2
- metadata +8 -3
    
        data/lib/appsignal.rb
    CHANGED
    
    | @@ -46,7 +46,11 @@ module Appsignal | |
| 46 46 | 
             
                # @see extension_loaded?
         | 
| 47 47 | 
             
                attr_accessor :extension_loaded
         | 
| 48 48 | 
             
                # @!attribute [rw] logger
         | 
| 49 | 
            -
                #   Accessor for the AppSignal logger.
         | 
| 49 | 
            +
                #   Accessor for the internal AppSignal logger.
         | 
| 50 | 
            +
                #
         | 
| 51 | 
            +
                #   Not to be confused with our logging feature.
         | 
| 52 | 
            +
                #   This is part of our private internal API. Do not call this method
         | 
| 53 | 
            +
                #   directly.
         | 
| 50 54 | 
             
                #
         | 
| 51 55 | 
             
                #   If no logger has been set, it will return a "in memory logger", using
         | 
| 52 56 | 
             
                #   `in_memory_log`. Once AppSignal is started (using {.start}) the
         | 
| @@ -57,7 +61,7 @@ module Appsignal | |
| 57 61 | 
             
                #   @api private
         | 
| 58 62 | 
             
                #   @return [Logger]
         | 
| 59 63 | 
             
                #   @see start_logger
         | 
| 60 | 
            -
                attr_writer : | 
| 64 | 
            +
                attr_writer :internal_logger
         | 
| 61 65 |  | 
| 62 66 | 
             
                # @api private
         | 
| 63 67 | 
             
                def testing?
         | 
| @@ -91,11 +95,11 @@ module Appsignal | |
| 91 95 | 
             
                # @since 0.7.0
         | 
| 92 96 | 
             
                def start
         | 
| 93 97 | 
             
                  unless extension_loaded?
         | 
| 94 | 
            -
                     | 
| 98 | 
            +
                    internal_logger.info("Not starting appsignal, extension is not loaded")
         | 
| 95 99 | 
             
                    return
         | 
| 96 100 | 
             
                  end
         | 
| 97 101 |  | 
| 98 | 
            -
                   | 
| 102 | 
            +
                  internal_logger.debug("Starting appsignal")
         | 
| 99 103 |  | 
| 100 104 | 
             
                  @config ||= Config.new(
         | 
| 101 105 | 
             
                    Dir.pwd,
         | 
| @@ -103,9 +107,9 @@ module Appsignal | |
| 103 107 | 
             
                  )
         | 
| 104 108 |  | 
| 105 109 | 
             
                  if config.valid?
         | 
| 106 | 
            -
                     | 
| 110 | 
            +
                    internal_logger.level = config.log_level
         | 
| 107 111 | 
             
                    if config.active?
         | 
| 108 | 
            -
                       | 
| 112 | 
            +
                      internal_logger.info "Starting AppSignal #{Appsignal::VERSION} " \
         | 
| 109 113 | 
             
                        "(#{$PROGRAM_NAME}, Ruby #{RUBY_VERSION}, #{RUBY_PLATFORM})"
         | 
| 110 114 | 
             
                      config.write_to_environment
         | 
| 111 115 | 
             
                      Appsignal::Extension.start
         | 
| @@ -120,10 +124,10 @@ module Appsignal | |
| 120 124 |  | 
| 121 125 | 
             
                      collect_environment_metadata
         | 
| 122 126 | 
             
                    else
         | 
| 123 | 
            -
                       | 
| 127 | 
            +
                      internal_logger.info("Not starting, not active for #{config.env}")
         | 
| 124 128 | 
             
                    end
         | 
| 125 129 | 
             
                  else
         | 
| 126 | 
            -
                     | 
| 130 | 
            +
                    internal_logger.error("Not starting, no valid config for this environment")
         | 
| 127 131 | 
             
                  end
         | 
| 128 132 | 
             
                end
         | 
| 129 133 |  | 
| @@ -143,9 +147,9 @@ module Appsignal | |
| 143 147 | 
             
                # @since 1.0.0
         | 
| 144 148 | 
             
                def stop(called_by = nil)
         | 
| 145 149 | 
             
                  if called_by
         | 
| 146 | 
            -
                     | 
| 150 | 
            +
                    internal_logger.debug("Stopping appsignal (#{called_by})")
         | 
| 147 151 | 
             
                  else
         | 
| 148 | 
            -
                     | 
| 152 | 
            +
                    internal_logger.debug("Stopping appsignal")
         | 
| 149 153 | 
             
                  end
         | 
| 150 154 | 
             
                  Appsignal::Extension.stop
         | 
| 151 155 | 
             
                end
         | 
| @@ -154,7 +158,7 @@ module Appsignal | |
| 154 158 | 
             
                  return unless active?
         | 
| 155 159 |  | 
| 156 160 | 
             
                  Appsignal.start_logger
         | 
| 157 | 
            -
                   | 
| 161 | 
            +
                  internal_logger.debug("Forked process, resubscribing and restarting extension")
         | 
| 158 162 | 
             
                  Appsignal::Extension.start
         | 
| 159 163 | 
             
                end
         | 
| 160 164 |  | 
| @@ -162,7 +166,8 @@ module Appsignal | |
| 162 166 | 
             
                  Appsignal::Extension.get_server_state(key)
         | 
| 163 167 | 
             
                end
         | 
| 164 168 |  | 
| 165 | 
            -
                # In memory logger used before any logger is started with | 
| 169 | 
            +
                # In memory internal logger used before any internal logger is started with
         | 
| 170 | 
            +
                # {.start_logger}.
         | 
| 166 171 | 
             
                #
         | 
| 167 172 | 
             
                # The contents of this logger are flushed to the logger in {.start_logger}.
         | 
| 168 173 | 
             
                #
         | 
| @@ -176,11 +181,12 @@ module Appsignal | |
| 176 181 | 
             
                  end
         | 
| 177 182 | 
             
                end
         | 
| 178 183 |  | 
| 179 | 
            -
                def  | 
| 180 | 
            -
                  @ | 
| 181 | 
            -
                     | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 184 | 
            +
                def internal_logger
         | 
| 185 | 
            +
                  @internal_logger ||=
         | 
| 186 | 
            +
                    Appsignal::Utils::IntegrationLogger.new(in_memory_log).tap do |l|
         | 
| 187 | 
            +
                      l.level = ::Logger::INFO
         | 
| 188 | 
            +
                      l.formatter = log_formatter("appsignal")
         | 
| 189 | 
            +
                    end
         | 
| 184 190 | 
             
                end
         | 
| 185 191 |  | 
| 186 192 | 
             
                # @api private
         | 
| @@ -192,7 +198,7 @@ module Appsignal | |
| 192 198 | 
             
                  end
         | 
| 193 199 | 
             
                end
         | 
| 194 200 |  | 
| 195 | 
            -
                # Start the AppSignal logger.
         | 
| 201 | 
            +
                # Start the AppSignal internal logger.
         | 
| 196 202 | 
             
                #
         | 
| 197 203 | 
             
                # Sets the log level and sets the logger. Uses a file-based logger or the
         | 
| 198 204 | 
             
                # STDOUT-based logger. See the `:log` configuration option.
         | 
| @@ -201,18 +207,18 @@ module Appsignal | |
| 201 207 | 
             
                # @since 0.7.0
         | 
| 202 208 | 
             
                def start_logger
         | 
| 203 209 | 
             
                  if config && config[:log] == "file" && config.log_file_path
         | 
| 204 | 
            -
                     | 
| 210 | 
            +
                    start_internal_file_logger(config.log_file_path)
         | 
| 205 211 | 
             
                  else
         | 
| 206 | 
            -
                     | 
| 212 | 
            +
                    start_internal_stdout_logger
         | 
| 207 213 | 
             
                  end
         | 
| 208 214 |  | 
| 209 | 
            -
                   | 
| 215 | 
            +
                  internal_logger.level =
         | 
| 210 216 | 
             
                    if config
         | 
| 211 217 | 
             
                      config.log_level
         | 
| 212 218 | 
             
                    else
         | 
| 213 219 | 
             
                      Appsignal::Config::DEFAULT_LOG_LEVEL
         | 
| 214 220 | 
             
                    end
         | 
| 215 | 
            -
                   | 
| 221 | 
            +
                  internal_logger << @in_memory_log.string if @in_memory_log
         | 
| 216 222 | 
             
                end
         | 
| 217 223 |  | 
| 218 224 | 
             
                # Returns if the C-extension was loaded properly.
         | 
| @@ -255,18 +261,18 @@ module Appsignal | |
| 255 261 |  | 
| 256 262 | 
             
                private
         | 
| 257 263 |  | 
| 258 | 
            -
                def  | 
| 259 | 
            -
                  @ | 
| 260 | 
            -
                   | 
| 264 | 
            +
                def start_internal_stdout_logger
         | 
| 265 | 
            +
                  @internal_logger = Appsignal::Utils::IntegrationLogger.new($stdout)
         | 
| 266 | 
            +
                  internal_logger.formatter = log_formatter("appsignal")
         | 
| 261 267 | 
             
                end
         | 
| 262 268 |  | 
| 263 | 
            -
                def  | 
| 264 | 
            -
                  @ | 
| 265 | 
            -
                   | 
| 269 | 
            +
                def start_internal_file_logger(path)
         | 
| 270 | 
            +
                  @internal_logger = Appsignal::Utils::IntegrationLogger.new(path)
         | 
| 271 | 
            +
                  internal_logger.formatter = log_formatter
         | 
| 266 272 | 
             
                rescue SystemCallError => error
         | 
| 267 | 
            -
                   | 
| 268 | 
            -
                   | 
| 269 | 
            -
                   | 
| 273 | 
            +
                  start_internal_stdout_logger
         | 
| 274 | 
            +
                  internal_logger.warn "Unable to start internal logger with log path '#{path}'."
         | 
| 275 | 
            +
                  internal_logger.warn error
         | 
| 270 276 | 
             
                end
         | 
| 271 277 |  | 
| 272 278 | 
             
                def collect_environment_metadata
         | 
| @@ -265,7 +265,7 @@ describe Appsignal::Config do | |
| 265 265 |  | 
| 266 266 | 
             
                context "with an overriden config file" do
         | 
| 267 267 | 
             
                  let(:config) do
         | 
| 268 | 
            -
                    project_fixture_config("production", {}, Appsignal. | 
| 268 | 
            +
                    project_fixture_config("production", {}, Appsignal.internal_logger,
         | 
| 269 269 | 
             
                      File.join(project_fixture_path, "config", "appsignal.yml"))
         | 
| 270 270 | 
             
                  end
         | 
| 271 271 |  | 
| @@ -276,7 +276,7 @@ describe Appsignal::Config do | |
| 276 276 |  | 
| 277 277 | 
             
                  context "with an invalid overriden config file" do
         | 
| 278 278 | 
             
                    let(:config) do
         | 
| 279 | 
            -
                      project_fixture_config("production", {}, Appsignal. | 
| 279 | 
            +
                      project_fixture_config("production", {}, Appsignal.internal_logger,
         | 
| 280 280 | 
             
                        File.join(project_fixture_path, "config", "missing.yml"))
         | 
| 281 281 | 
             
                    end
         | 
| 282 282 |  | 
| @@ -76,7 +76,7 @@ if DependencyHelper.active_job_present? | |
| 76 76 | 
             
                  ActiveJob::Base.queue_adapter = :inline
         | 
| 77 77 |  | 
| 78 78 | 
             
                  start_agent
         | 
| 79 | 
            -
                  Appsignal. | 
| 79 | 
            +
                  Appsignal.internal_logger = test_logger(log)
         | 
| 80 80 | 
             
                  class ActiveJobTestJob < ActiveJob::Base
         | 
| 81 81 | 
             
                    def perform(*_args)
         | 
| 82 82 | 
             
                    end
         | 
| @@ -0,0 +1,222 @@ | |
| 1 | 
            +
            describe Appsignal::Hooks::RedisClientHook do
         | 
| 2 | 
            +
              before do
         | 
| 3 | 
            +
                Appsignal.config = project_fixture_config
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              if DependencyHelper.redis_client_present?
         | 
| 7 | 
            +
                context "with redis_client" do
         | 
| 8 | 
            +
                  context "with instrumentation enabled" do
         | 
| 9 | 
            +
                    describe "#dependencies_present?" do
         | 
| 10 | 
            +
                      subject { described_class.new.dependencies_present? }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      it { is_expected.to be_truthy }
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    context "with rest-client gem" do
         | 
| 16 | 
            +
                      describe "integration" do
         | 
| 17 | 
            +
                        before do
         | 
| 18 | 
            +
                          Appsignal.config.config_hash[:instrument_redis] = true
         | 
| 19 | 
            +
                        end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                        context "install" do
         | 
| 22 | 
            +
                          before do
         | 
| 23 | 
            +
                            Appsignal::Hooks.load_hooks
         | 
| 24 | 
            +
                          end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                          it "includes the integration for the ruby connection" do
         | 
| 27 | 
            +
                            # Test if the last included module (prepended module) was our
         | 
| 28 | 
            +
                            # integration. That's not certain with the assertions below
         | 
| 29 | 
            +
                            # because we have to overwrite the `process` method for the test.
         | 
| 30 | 
            +
                            expect(RedisClient::RubyConnection.included_modules.first)
         | 
| 31 | 
            +
                              .to eql(Appsignal::Integrations::RedisClientIntegration)
         | 
| 32 | 
            +
                          end
         | 
| 33 | 
            +
                        end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                        context "requirements" do
         | 
| 36 | 
            +
                          it "driver should have the write method" do
         | 
| 37 | 
            +
                            # Since we stub the driver class below, to make sure that we don't
         | 
| 38 | 
            +
                            # create a real connection, the test won't fail if the method definition
         | 
| 39 | 
            +
                            # is changed.
         | 
| 40 | 
            +
                            method = RedisClient::RubyConnection.instance_method(:write)
         | 
| 41 | 
            +
                            expect(method.arity).to eql(1)
         | 
| 42 | 
            +
                          end
         | 
| 43 | 
            +
                        end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                        context "instrumentation" do
         | 
| 46 | 
            +
                          before do
         | 
| 47 | 
            +
                            start_agent
         | 
| 48 | 
            +
                            # Stub RedisClient::RubyConnection class so that it doesn't perform an actual
         | 
| 49 | 
            +
                            # Redis query. This class will be included (prepended) with the
         | 
| 50 | 
            +
                            # AppSignal Redis integration.
         | 
| 51 | 
            +
                            stub_const("RedisClient::RubyConnection", Class.new do
         | 
| 52 | 
            +
                              def initialize(config)
         | 
| 53 | 
            +
                                @config = config
         | 
| 54 | 
            +
                              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                              def write(_commands)
         | 
| 57 | 
            +
                                "stub_write"
         | 
| 58 | 
            +
                              end
         | 
| 59 | 
            +
                            end)
         | 
| 60 | 
            +
                            # Load the integration again for the stubbed RedisClient::RubyConnection class.
         | 
| 61 | 
            +
                            # Call it directly because {Appsignal::Hooks.load_hooks} keeps
         | 
| 62 | 
            +
                            # track if it was installed already or not.
         | 
| 63 | 
            +
                            Appsignal::Hooks::RedisClientHook.new.install
         | 
| 64 | 
            +
                          end
         | 
| 65 | 
            +
                          let!(:transaction) do
         | 
| 66 | 
            +
                            Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
         | 
| 67 | 
            +
                          end
         | 
| 68 | 
            +
                          let!(:client_config) { RedisClient::Config.new(:id => "stub_id") }
         | 
| 69 | 
            +
                          around { |example| keep_transactions { example.run } }
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                          it "instrument a redis call" do
         | 
| 72 | 
            +
                            connection = RedisClient::RubyConnection.new client_config
         | 
| 73 | 
            +
                            expect(connection.write([:get, "key"])).to eql("stub_write")
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                            transaction_hash = transaction.to_h
         | 
| 76 | 
            +
                            expect(transaction_hash["events"]).to include(
         | 
| 77 | 
            +
                              hash_including(
         | 
| 78 | 
            +
                                "name" => "query.redis",
         | 
| 79 | 
            +
                                "body" => "get ?",
         | 
| 80 | 
            +
                                "title" => "stub_id"
         | 
| 81 | 
            +
                              )
         | 
| 82 | 
            +
                            )
         | 
| 83 | 
            +
                          end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                          it "instrument a redis script call" do
         | 
| 86 | 
            +
                            connection = ::RedisClient::RubyConnection.new client_config
         | 
| 87 | 
            +
                            script = "return redis.call('set',KEYS[1],ARGV[1])"
         | 
| 88 | 
            +
                            keys = ["foo"]
         | 
| 89 | 
            +
                            argv = ["bar"]
         | 
| 90 | 
            +
                            expect(connection.write([:eval, script, keys.size, keys,
         | 
| 91 | 
            +
                                                     argv])).to eql("stub_write")
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                            transaction_hash = transaction.to_h
         | 
| 94 | 
            +
                            expect(transaction_hash["events"]).to include(
         | 
| 95 | 
            +
                              hash_including(
         | 
| 96 | 
            +
                                "name" => "query.redis",
         | 
| 97 | 
            +
                                "body" => "#{script} ? ?",
         | 
| 98 | 
            +
                                "title" => "stub_id"
         | 
| 99 | 
            +
                              )
         | 
| 100 | 
            +
                            )
         | 
| 101 | 
            +
                          end
         | 
| 102 | 
            +
                        end
         | 
| 103 | 
            +
                      end
         | 
| 104 | 
            +
                    end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    if DependencyHelper.hiredis_client_present?
         | 
| 107 | 
            +
                      context "with hiredis driver" do
         | 
| 108 | 
            +
                        describe "integration" do
         | 
| 109 | 
            +
                          before do
         | 
| 110 | 
            +
                            Appsignal.config.config_hash[:instrument_redis] = true
         | 
| 111 | 
            +
                          end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                          context "install" do
         | 
| 114 | 
            +
                            before do
         | 
| 115 | 
            +
                              Appsignal::Hooks.load_hooks
         | 
| 116 | 
            +
                            end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                            it "includes the integration in the HiredisConnection class" do
         | 
| 119 | 
            +
                              # Test if the last included module (prepended module) was our
         | 
| 120 | 
            +
                              # integration. That's not certain with the assertions below
         | 
| 121 | 
            +
                              # because we have to overwrite the `process` method for the test.
         | 
| 122 | 
            +
                              expect(RedisClient::HiredisConnection.included_modules.first)
         | 
| 123 | 
            +
                                .to eql(Appsignal::Integrations::RedisClientIntegration)
         | 
| 124 | 
            +
                            end
         | 
| 125 | 
            +
                          end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                          context "requirements" do
         | 
| 128 | 
            +
                            it "driver should have the write method" do
         | 
| 129 | 
            +
                              # Since we stub the driver class below, to make sure that we don't
         | 
| 130 | 
            +
                              # create a real connection, the test won't fail if the method definition
         | 
| 131 | 
            +
                              # is changed.
         | 
| 132 | 
            +
                              method = RedisClient::HiredisConnection.instance_method(:write)
         | 
| 133 | 
            +
                              expect(method.arity).to eql(1)
         | 
| 134 | 
            +
                            end
         | 
| 135 | 
            +
                          end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                          context "instrumentation" do
         | 
| 138 | 
            +
                            before do
         | 
| 139 | 
            +
                              start_agent
         | 
| 140 | 
            +
                              # Stub RedisClient::HiredisConnection class so that it doesn't perform an actual
         | 
| 141 | 
            +
                              # Redis query. This class will be included (prepended) with the
         | 
| 142 | 
            +
                              # AppSignal Redis integration.
         | 
| 143 | 
            +
                              stub_const("RedisClient::HiredisConnection", Class.new do
         | 
| 144 | 
            +
                                def initialize(config)
         | 
| 145 | 
            +
                                  @config = config
         | 
| 146 | 
            +
                                end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                                def write(_commands)
         | 
| 149 | 
            +
                                  "stub_write"
         | 
| 150 | 
            +
                                end
         | 
| 151 | 
            +
                              end)
         | 
| 152 | 
            +
                              # Load the integration again for the stubbed RedisClient::HiredisConnection class.
         | 
| 153 | 
            +
                              # Call it directly because {Appsignal::Hooks.load_hooks} keeps
         | 
| 154 | 
            +
                              # track if it was installed already or not.
         | 
| 155 | 
            +
                              Appsignal::Hooks::RedisClientHook.new.install
         | 
| 156 | 
            +
                            end
         | 
| 157 | 
            +
                            let!(:transaction) do
         | 
| 158 | 
            +
                              Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST,
         | 
| 159 | 
            +
                                "test")
         | 
| 160 | 
            +
                            end
         | 
| 161 | 
            +
                            let!(:client_config) { RedisClient::Config.new(:id => "stub_id") }
         | 
| 162 | 
            +
                            around { |example| keep_transactions { example.run } }
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                            it "instrument a redis call" do
         | 
| 165 | 
            +
                              connection = RedisClient::HiredisConnection.new client_config
         | 
| 166 | 
            +
                              expect(connection.write([:get, "key"])).to eql("stub_write")
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                              transaction_hash = transaction.to_h
         | 
| 169 | 
            +
                              expect(transaction_hash["events"]).to include(
         | 
| 170 | 
            +
                                hash_including(
         | 
| 171 | 
            +
                                  "name" => "query.redis",
         | 
| 172 | 
            +
                                  "body" => "get ?",
         | 
| 173 | 
            +
                                  "title" => "stub_id"
         | 
| 174 | 
            +
                                )
         | 
| 175 | 
            +
                              )
         | 
| 176 | 
            +
                            end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                            it "instrument a redis script call" do
         | 
| 179 | 
            +
                              connection = ::RedisClient::HiredisConnection.new client_config
         | 
| 180 | 
            +
                              script = "return redis.call('set',KEYS[1],ARGV[1])"
         | 
| 181 | 
            +
                              keys = ["foo"]
         | 
| 182 | 
            +
                              argv = ["bar"]
         | 
| 183 | 
            +
                              expect(connection.write([:eval, script, keys.size, keys,
         | 
| 184 | 
            +
                                                       argv])).to eql("stub_write")
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                              transaction_hash = transaction.to_h
         | 
| 187 | 
            +
                              expect(transaction_hash["events"]).to include(
         | 
| 188 | 
            +
                                hash_including(
         | 
| 189 | 
            +
                                  "name" => "query.redis",
         | 
| 190 | 
            +
                                  "body" => "#{script} ? ?",
         | 
| 191 | 
            +
                                  "title" => "stub_id"
         | 
| 192 | 
            +
                                )
         | 
| 193 | 
            +
                              )
         | 
| 194 | 
            +
                            end
         | 
| 195 | 
            +
                          end
         | 
| 196 | 
            +
                        end
         | 
| 197 | 
            +
                      end
         | 
| 198 | 
            +
                    end
         | 
| 199 | 
            +
                  end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                  context "with instrumentation disabled" do
         | 
| 202 | 
            +
                    before do
         | 
| 203 | 
            +
                      Appsignal.config.config_hash[:instrument_redis] = false
         | 
| 204 | 
            +
                    end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                    describe "#dependencies_present?" do
         | 
| 207 | 
            +
                      subject { described_class.new.dependencies_present? }
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                      it { is_expected.to be_falsy }
         | 
| 210 | 
            +
                    end
         | 
| 211 | 
            +
                  end
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
              else
         | 
| 214 | 
            +
                context "without redis" do
         | 
| 215 | 
            +
                  describe "#dependencies_present?" do
         | 
| 216 | 
            +
                    subject { described_class.new.dependencies_present? }
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                    it { is_expected.to be_falsy }
         | 
| 219 | 
            +
                  end
         | 
| 220 | 
            +
                end
         | 
| 221 | 
            +
              end
         | 
| 222 | 
            +
            end
         | 
| @@ -5,100 +5,122 @@ describe Appsignal::Hooks::RedisHook do | |
| 5 5 |  | 
| 6 6 | 
             
              if DependencyHelper.redis_present?
         | 
| 7 7 | 
             
                context "with redis" do
         | 
| 8 | 
            -
                   | 
| 9 | 
            -
                     | 
| 10 | 
            -
                       | 
| 8 | 
            +
                  if DependencyHelper.redis_client_present?
         | 
| 9 | 
            +
                    context "with redis-client" do
         | 
| 10 | 
            +
                      context "with instrumentation enabled" do
         | 
| 11 | 
            +
                        describe "#dependencies_present?" do
         | 
| 12 | 
            +
                          subject { described_class.new.dependencies_present? }
         | 
| 11 13 |  | 
| 12 | 
            -
             | 
| 14 | 
            +
                          it { is_expected.to be_falsey }
         | 
| 15 | 
            +
                        end
         | 
| 16 | 
            +
                      end
         | 
| 13 17 | 
             
                    end
         | 
| 18 | 
            +
                  else
         | 
| 19 | 
            +
                    context "with instrumentation enabled" do
         | 
| 20 | 
            +
                      describe "#dependencies_present?" do
         | 
| 21 | 
            +
                        subject { described_class.new.dependencies_present? }
         | 
| 14 22 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
                      before do
         | 
| 17 | 
            -
                        Appsignal.config.config_hash[:instrument_redis] = true
         | 
| 23 | 
            +
                        it { is_expected.to be_truthy }
         | 
| 18 24 | 
             
                      end
         | 
| 19 25 |  | 
| 20 | 
            -
                       | 
| 26 | 
            +
                      describe "integration" do
         | 
| 21 27 | 
             
                        before do
         | 
| 22 | 
            -
                          Appsignal | 
| 28 | 
            +
                          Appsignal.config.config_hash[:instrument_redis] = true
         | 
| 23 29 | 
             
                        end
         | 
| 24 30 |  | 
| 25 | 
            -
                         | 
| 26 | 
            -
                           | 
| 27 | 
            -
             | 
| 28 | 
            -
                           | 
| 29 | 
            -
                          expect(Redis::Client.included_modules.first)
         | 
| 30 | 
            -
                            .to eql(Appsignal::Integrations::RedisIntegration)
         | 
| 31 | 
            -
                        end
         | 
| 32 | 
            -
                      end
         | 
| 31 | 
            +
                        context "install" do
         | 
| 32 | 
            +
                          before do
         | 
| 33 | 
            +
                            Appsignal::Hooks.load_hooks
         | 
| 34 | 
            +
                          end
         | 
| 33 35 |  | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
                           | 
| 41 | 
            -
                            def id
         | 
| 42 | 
            -
                              "stub_id"
         | 
| 43 | 
            -
                            end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                            def write(_commands)
         | 
| 46 | 
            -
                              "stub_write"
         | 
| 47 | 
            -
                            end
         | 
| 48 | 
            -
                          end)
         | 
| 49 | 
            -
                          # Load the integration again for the stubbed Redis::Client class.
         | 
| 50 | 
            -
                          # Call it directly because {Appsignal::Hooks.load_hooks} keeps
         | 
| 51 | 
            -
                          # track if it was installed already or not.
         | 
| 52 | 
            -
                          Appsignal::Hooks::RedisHook.new.install
         | 
| 53 | 
            -
                        end
         | 
| 54 | 
            -
                        let!(:transaction) do
         | 
| 55 | 
            -
                          Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
         | 
| 36 | 
            +
                          it "prepends instrumentation module" do
         | 
| 37 | 
            +
                            # Test if the last included module (prepended module) was our
         | 
| 38 | 
            +
                            # integration. That's not certain with the assertions below
         | 
| 39 | 
            +
                            # because we have to overwrite the `process` method for the test.
         | 
| 40 | 
            +
                            expect(Redis::Client.included_modules.first)
         | 
| 41 | 
            +
                              .to eql(Appsignal::Integrations::RedisIntegration)
         | 
| 42 | 
            +
                          end
         | 
| 56 43 | 
             
                        end
         | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
                              "name" => "query.redis",
         | 
| 67 | 
            -
                              "body" => "get ?",
         | 
| 68 | 
            -
                              "title" => "stub_id"
         | 
| 69 | 
            -
                            )
         | 
| 70 | 
            -
                          )
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                        context "requirements" do
         | 
| 46 | 
            +
                          it "driver should have the write method" do
         | 
| 47 | 
            +
                            # Since we stub the client class below, to make sure that we don't
         | 
| 48 | 
            +
                            # create a real connection, the test won't fail if the method definition
         | 
| 49 | 
            +
                            # is changed.
         | 
| 50 | 
            +
                            method = Redis::Client.instance_method(:call)
         | 
| 51 | 
            +
                            expect(method.arity).to eql(1)
         | 
| 52 | 
            +
                          end
         | 
| 71 53 | 
             
                        end
         | 
| 72 54 |  | 
| 73 | 
            -
                         | 
| 74 | 
            -
                           | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
                               | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 55 | 
            +
                        context "instrumentation" do
         | 
| 56 | 
            +
                          before do
         | 
| 57 | 
            +
                            start_agent
         | 
| 58 | 
            +
                            # Stub Redis::Client class so that it doesn't perform an actual
         | 
| 59 | 
            +
                            # Redis query. This class will be included (prepended) with the
         | 
| 60 | 
            +
                            # AppSignal Redis integration.
         | 
| 61 | 
            +
                            stub_const("Redis::Client", Class.new do
         | 
| 62 | 
            +
                              def id
         | 
| 63 | 
            +
                                "stub_id"
         | 
| 64 | 
            +
                              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                              def write(_commands)
         | 
| 67 | 
            +
                                "stub_write"
         | 
| 68 | 
            +
                              end
         | 
| 69 | 
            +
                            end)
         | 
| 70 | 
            +
                            # Load the integration again for the stubbed Redis::Client class.
         | 
| 71 | 
            +
                            # Call it directly because {Appsignal::Hooks.load_hooks} keeps
         | 
| 72 | 
            +
                            # track if it was installed already or not.
         | 
| 73 | 
            +
                            Appsignal::Hooks::RedisHook.new.install
         | 
| 74 | 
            +
                          end
         | 
| 75 | 
            +
                          let!(:transaction) do
         | 
| 76 | 
            +
                            Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
         | 
| 77 | 
            +
                          end
         | 
| 78 | 
            +
                          around { |example| keep_transactions { example.run } }
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                          it "instrument a redis call" do
         | 
| 81 | 
            +
                            client = Redis::Client.new
         | 
| 82 | 
            +
                            expect(client.write([:get, "key"])).to eql("stub_write")
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                            transaction_hash = transaction.to_h
         | 
| 85 | 
            +
                            expect(transaction_hash["events"]).to include(
         | 
| 86 | 
            +
                                                                    hash_including(
         | 
| 87 | 
            +
                                                                      "name" => "query.redis",
         | 
| 88 | 
            +
                                                                      "body" => "get ?",
         | 
| 89 | 
            +
                                                                      "title" => "stub_id"
         | 
| 90 | 
            +
                                                                    )
         | 
| 91 | 
            +
                                                                  )
         | 
| 92 | 
            +
                          end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                          it "instrument a redis script call" do
         | 
| 95 | 
            +
                            client = Redis::Client.new
         | 
| 96 | 
            +
                            script = "return redis.call('set',KEYS[1],ARGV[1])"
         | 
| 97 | 
            +
                            keys = ["foo"]
         | 
| 98 | 
            +
                            argv = ["bar"]
         | 
| 99 | 
            +
                            expect(client.write([:eval, script, keys.size, keys, argv])).to eql("stub_write")
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                            transaction_hash = transaction.to_h
         | 
| 102 | 
            +
                            expect(transaction_hash["events"]).to include(
         | 
| 103 | 
            +
                                                                    hash_including(
         | 
| 104 | 
            +
                                                                      "name" => "query.redis",
         | 
| 105 | 
            +
                                                                      "body" => "#{script} ? ?",
         | 
| 106 | 
            +
                                                                      "title" => "stub_id"
         | 
| 107 | 
            +
                                                                    )
         | 
| 108 | 
            +
                                                                  )
         | 
| 109 | 
            +
                          end
         | 
| 88 110 | 
             
                        end
         | 
| 89 111 | 
             
                      end
         | 
| 90 112 | 
             
                    end
         | 
| 91 | 
            -
                  end
         | 
| 92 113 |  | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 114 | 
            +
                    context "with instrumentation disabled" do
         | 
| 115 | 
            +
                      before do
         | 
| 116 | 
            +
                        Appsignal.config.config_hash[:instrument_redis] = false
         | 
| 117 | 
            +
                      end
         | 
| 97 118 |  | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 119 | 
            +
                      describe "#dependencies_present?" do
         | 
| 120 | 
            +
                        subject { described_class.new.dependencies_present? }
         | 
| 100 121 |  | 
| 101 | 
            -
             | 
| 122 | 
            +
                        it { is_expected.to be_falsy }
         | 
| 123 | 
            +
                      end
         | 
| 102 124 | 
             
                    end
         | 
| 103 125 | 
             
                  end
         | 
| 104 126 | 
             
                end
         | 
| @@ -67,12 +67,12 @@ describe Appsignal::Hooks do | |
| 67 67 | 
             
                expect(Appsignal::Hooks.hooks[:mock_error_hook]).to be_instance_of(MockErrorHook)
         | 
| 68 68 | 
             
                expect(Appsignal::Hooks.hooks[:mock_error_hook].installed?).to be_falsy
         | 
| 69 69 |  | 
| 70 | 
            -
                expect(Appsignal. | 
| 70 | 
            +
                expect(Appsignal.internal_logger).to receive(:error)
         | 
| 71 71 | 
             
                  .with("Error while installing mock_error_hook hook: error").once
         | 
| 72 | 
            -
                expect(Appsignal. | 
| 72 | 
            +
                expect(Appsignal.internal_logger).to receive(:debug).ordered do |message|
         | 
| 73 73 | 
             
                  expect(message).to eq("Installing mock_error_hook hook")
         | 
| 74 74 | 
             
                end
         | 
| 75 | 
            -
                expect(Appsignal. | 
| 75 | 
            +
                expect(Appsignal.internal_logger).to receive(:debug).ordered do |message|
         | 
| 76 76 | 
             
                  # Start of the error backtrace as debug log
         | 
| 77 77 | 
             
                  expect(message).to start_with(File.expand_path("../../..", __dir__))
         | 
| 78 78 | 
             
                end
         | 
| @@ -89,7 +89,7 @@ describe Appsignal::Hooks do | |
| 89 89 | 
             
                let(:log_stream) { std_stream }
         | 
| 90 90 | 
             
                let(:log) { log_contents(log_stream) }
         | 
| 91 91 | 
             
                before do
         | 
| 92 | 
            -
                  Appsignal. | 
| 92 | 
            +
                  Appsignal.internal_logger = test_logger(log_stream)
         | 
| 93 93 | 
             
                end
         | 
| 94 94 |  | 
| 95 95 | 
             
                def call_constant(&block)
         | 
| @@ -18,9 +18,9 @@ if DependencyHelper.rails_present? | |
| 18 18 | 
             
                describe "#initialize_appsignal" do
         | 
| 19 19 | 
             
                  let(:app) { MyApp::Application.new }
         | 
| 20 20 |  | 
| 21 | 
            -
                  describe ". | 
| 21 | 
            +
                  describe ".internal_logger" do
         | 
| 22 22 | 
             
                    before  { Appsignal::Integrations::Railtie.initialize_appsignal(app) }
         | 
| 23 | 
            -
                    subject { Appsignal. | 
| 23 | 
            +
                    subject { Appsignal.internal_logger }
         | 
| 24 24 |  | 
| 25 25 | 
             
                    it { is_expected.to be_a Logger }
         | 
| 26 26 | 
             
                  end
         |