appsignal 3.8.0-java → 3.9.0-java
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/CHANGELOG.md +29 -0
- data/lib/appsignal/config.rb +10 -2
- data/lib/appsignal/hooks/sidekiq.rb +18 -1
- data/lib/appsignal/integrations/sidekiq.rb +37 -18
- data/lib/appsignal/integrations/sinatra.rb +9 -7
- data/lib/appsignal/rack/abstract_middleware.rb +127 -0
- data/lib/appsignal/rack/event_handler.rb +29 -1
- data/lib/appsignal/rack/generic_instrumentation.rb +6 -33
- data/lib/appsignal/rack/sinatra_instrumentation.rb +16 -42
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +1 -0
- data/spec/lib/appsignal/config_spec.rb +28 -0
- data/spec/lib/appsignal/integrations/sidekiq_spec.rb +175 -17
- data/spec/lib/appsignal/integrations/sinatra_spec.rb +57 -33
- data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +250 -0
- data/spec/lib/appsignal/rack/event_handler_spec.rb +56 -3
- data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +26 -79
- data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +80 -164
- data/support/install_deps +6 -0
- metadata +4 -2
| @@ -1,19 +1,83 @@ | |
| 1 1 | 
             
            require "appsignal/integrations/sidekiq"
         | 
| 2 2 |  | 
| 3 | 
            -
            describe Appsignal::Integrations:: | 
| 4 | 
            -
               | 
| 5 | 
            -
              before do
         | 
| 6 | 
            -
                start_agent
         | 
| 7 | 
            -
                Appsignal.internal_logger = test_logger(log)
         | 
| 8 | 
            -
              end
         | 
| 3 | 
            +
            describe Appsignal::Integrations::SidekiqDeathHandler do
         | 
| 4 | 
            +
              before { start_agent }
         | 
| 9 5 | 
             
              around { |example| keep_transactions { example.run } }
         | 
| 10 6 |  | 
| 11 | 
            -
               | 
| 12 | 
            -
                 | 
| 13 | 
            -
             | 
| 14 | 
            -
                 | 
| 15 | 
            -
             | 
| 7 | 
            +
              let(:exception) do
         | 
| 8 | 
            +
                raise ExampleStandardError, "uh oh"
         | 
| 9 | 
            +
              rescue => error
         | 
| 10 | 
            +
                error
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
              let(:job_context) { {} }
         | 
| 13 | 
            +
              let(:transaction) { http_request_transaction }
         | 
| 14 | 
            +
              before { set_current_transaction(transaction) }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def call_handler
         | 
| 17 | 
            +
                expect do
         | 
| 18 | 
            +
                  described_class.new.call(job_context, exception)
         | 
| 19 | 
            +
                end.to_not(change { created_transactions.count })
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def expect_error_on_transaction
         | 
| 23 | 
            +
                expect(last_transaction.to_h).to include(
         | 
| 24 | 
            +
                  "error" => hash_including(
         | 
| 25 | 
            +
                    "name" => "ExampleStandardError",
         | 
| 26 | 
            +
                    "message" => "uh oh",
         | 
| 27 | 
            +
                    "backtrace" => kind_of(String)
         | 
| 28 | 
            +
                  )
         | 
| 29 | 
            +
                )
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              def expect_no_error_on_transaction
         | 
| 33 | 
            +
                expect(last_transaction.to_h).to include("error" => nil)
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              context "when sidekiq_report_errors = none" do
         | 
| 37 | 
            +
                before do
         | 
| 38 | 
            +
                  Appsignal.config[:sidekiq_report_errors] = "none"
         | 
| 39 | 
            +
                  call_handler
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                it "doesn't track the error on the transaction" do
         | 
| 43 | 
            +
                  expect_no_error_on_transaction
         | 
| 16 44 | 
             
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              context "when sidekiq_report_errors = all" do
         | 
| 48 | 
            +
                before do
         | 
| 49 | 
            +
                  Appsignal.config[:sidekiq_report_errors] = "all"
         | 
| 50 | 
            +
                  call_handler
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                it "doesn't track the error on the transaction" do
         | 
| 54 | 
            +
                  expect_no_error_on_transaction
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              context "when sidekiq_report_errors = discard" do
         | 
| 59 | 
            +
                before do
         | 
| 60 | 
            +
                  Appsignal.config[:sidekiq_report_errors] = "discard"
         | 
| 61 | 
            +
                  call_handler
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                it "records each occurrence of the error on the transaction" do
         | 
| 65 | 
            +
                  expect_error_on_transaction
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
            end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            describe Appsignal::Integrations::SidekiqErrorHandler do
         | 
| 71 | 
            +
              before { start_agent }
         | 
| 72 | 
            +
              around { |example| keep_transactions { example.run } }
         | 
| 73 | 
            +
             | 
| 74 | 
            +
              let(:exception) do
         | 
| 75 | 
            +
                raise ExampleStandardError, "uh oh"
         | 
| 76 | 
            +
              rescue => error
         | 
| 77 | 
            +
                error
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              context "when error is an internal error" do
         | 
| 17 81 | 
             
                let(:job_context) do
         | 
| 18 82 | 
             
                  {
         | 
| 19 83 | 
             
                    :context => "Sidekiq internal error!",
         | 
| @@ -21,14 +85,19 @@ describe Appsignal::Integrations::SidekiqErrorHandler do | |
| 21 85 | 
             
                  }
         | 
| 22 86 | 
             
                end
         | 
| 23 87 |  | 
| 24 | 
            -
                 | 
| 25 | 
            -
                   | 
| 88 | 
            +
                def expect_report_internal_error
         | 
| 89 | 
            +
                  expect do
         | 
| 90 | 
            +
                    described_class.new.call(exception, job_context)
         | 
| 91 | 
            +
                  end.to(change { created_transactions.count }.by(1))
         | 
| 26 92 |  | 
| 27 93 | 
             
                  transaction_hash = last_transaction.to_h
         | 
| 28 | 
            -
                  expect(transaction_hash | 
| 29 | 
            -
                    " | 
| 30 | 
            -
                    " | 
| 31 | 
            -
             | 
| 94 | 
            +
                  expect(transaction_hash).to include(
         | 
| 95 | 
            +
                    "action" => "SidekiqInternal",
         | 
| 96 | 
            +
                    "error" => hash_including(
         | 
| 97 | 
            +
                      "name" => "ExampleStandardError",
         | 
| 98 | 
            +
                      "message" => "uh oh",
         | 
| 99 | 
            +
                      "backtrace" => kind_of(String)
         | 
| 100 | 
            +
                    )
         | 
| 32 101 | 
             
                  )
         | 
| 33 102 | 
             
                  expect(transaction_hash["sample_data"]).to include(
         | 
| 34 103 | 
             
                    "params" => {
         | 
| @@ -39,6 +108,95 @@ describe Appsignal::Integrations::SidekiqErrorHandler do | |
| 39 108 | 
             
                    "sidekiq_error" => "Sidekiq internal error!"
         | 
| 40 109 | 
             
                  )
         | 
| 41 110 | 
             
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                context "when sidekiq_report_errors = none" do
         | 
| 113 | 
            +
                  before { Appsignal.config[:sidekiq_report_errors] = "none" }
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  it "tracks the error on a new transaction" do
         | 
| 116 | 
            +
                    expect_report_internal_error
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                context "when sidekiq_report_errors = all" do
         | 
| 121 | 
            +
                  before { Appsignal.config[:sidekiq_report_errors] = "all" }
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  it "tracks the error on a new transaction" do
         | 
| 124 | 
            +
                    expect_report_internal_error
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                context "when sidekiq_report_errors = discard" do
         | 
| 129 | 
            +
                  before { Appsignal.config[:sidekiq_report_errors] = "discard" }
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  it "tracks the error on a new transaction" do
         | 
| 132 | 
            +
                    expect_report_internal_error
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
              end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
              context "when error is a job error" do
         | 
| 138 | 
            +
                let(:sidekiq_context) { { :job => {} } }
         | 
| 139 | 
            +
                let(:transaction) { http_request_transaction }
         | 
| 140 | 
            +
                before do
         | 
| 141 | 
            +
                  transaction.set_action("existing transaction action")
         | 
| 142 | 
            +
                  set_current_transaction(transaction)
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                def call_handler
         | 
| 146 | 
            +
                  expect do
         | 
| 147 | 
            +
                    described_class.new.call(exception, sidekiq_context)
         | 
| 148 | 
            +
                  end.to_not(change { created_transactions.count })
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                def expect_error_on_transaction
         | 
| 152 | 
            +
                  expect(last_transaction.to_h).to include(
         | 
| 153 | 
            +
                    "error" => hash_including(
         | 
| 154 | 
            +
                      "name" => "ExampleStandardError",
         | 
| 155 | 
            +
                      "message" => "uh oh",
         | 
| 156 | 
            +
                      "backtrace" => kind_of(String)
         | 
| 157 | 
            +
                    )
         | 
| 158 | 
            +
                  )
         | 
| 159 | 
            +
                end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                def expect_no_error_on_transaction
         | 
| 162 | 
            +
                  expect(last_transaction.to_h).to include("error" => nil)
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                context "when sidekiq_report_errors = none" do
         | 
| 166 | 
            +
                  before do
         | 
| 167 | 
            +
                    Appsignal.config[:sidekiq_report_errors] = "none"
         | 
| 168 | 
            +
                    call_handler
         | 
| 169 | 
            +
                  end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  it "doesn't track the error on the transaction" do
         | 
| 172 | 
            +
                    expect_no_error_on_transaction
         | 
| 173 | 
            +
                    expect(last_transaction).to be_completed
         | 
| 174 | 
            +
                  end
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                context "when sidekiq_report_errors = all" do
         | 
| 178 | 
            +
                  before do
         | 
| 179 | 
            +
                    Appsignal.config[:sidekiq_report_errors] = "all"
         | 
| 180 | 
            +
                    call_handler
         | 
| 181 | 
            +
                  end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                  it "records each occurrence of the error on the transaction" do
         | 
| 184 | 
            +
                    expect_error_on_transaction
         | 
| 185 | 
            +
                    expect(last_transaction).to be_completed
         | 
| 186 | 
            +
                  end
         | 
| 187 | 
            +
                end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                context "when sidekiq_report_errors = discard" do
         | 
| 190 | 
            +
                  before do
         | 
| 191 | 
            +
                    Appsignal.config[:sidekiq_report_errors] = "discard"
         | 
| 192 | 
            +
                    call_handler
         | 
| 193 | 
            +
                  end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                  it "doesn't track the error on the transaction" do
         | 
| 196 | 
            +
                    expect_no_error_on_transaction
         | 
| 197 | 
            +
                    expect(last_transaction).to be_completed
         | 
| 198 | 
            +
                  end
         | 
| 199 | 
            +
                end
         | 
| 42 200 | 
             
              end
         | 
| 43 201 | 
             
            end
         | 
| 44 202 |  | 
| @@ -13,59 +13,83 @@ if DependencyHelper.sinatra_present? | |
| 13 13 | 
             
              end
         | 
| 14 14 |  | 
| 15 15 | 
             
              describe "Sinatra integration" do
         | 
| 16 | 
            -
                before  | 
| 16 | 
            +
                before do
         | 
| 17 | 
            +
                  Appsignal.config = nil
         | 
| 18 | 
            +
                end
         | 
| 17 19 | 
             
                after { uninstall_sinatra_integration }
         | 
| 18 20 |  | 
| 19 | 
            -
                context " | 
| 20 | 
            -
                   | 
| 21 | 
            +
                context "when active" do
         | 
| 22 | 
            +
                  before { allow(Appsignal).to receive(:active?).and_return(true) }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  it "does not start AppSignal again" do
         | 
| 25 | 
            +
                    expect(Appsignal::Config).to_not receive(:new)
         | 
| 26 | 
            +
                    expect(Appsignal).to_not receive(:start)
         | 
| 27 | 
            +
                    expect(Appsignal).to_not receive(:start_logger)
         | 
| 28 | 
            +
                    install_sinatra_integration
         | 
| 29 | 
            +
                  end
         | 
| 21 30 |  | 
| 22 | 
            -
                  it " | 
| 31 | 
            +
                  it "adds the instrumentation middleware to Sinatra::Base" do
         | 
| 23 32 | 
             
                    install_sinatra_integration
         | 
| 24 | 
            -
                     | 
| 33 | 
            +
                    expect(Sinatra::Base.middleware.to_a).to include(
         | 
| 34 | 
            +
                      [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
         | 
| 35 | 
            +
                    )
         | 
| 25 36 | 
             
                  end
         | 
| 26 37 | 
             
                end
         | 
| 27 38 |  | 
| 28 | 
            -
                 | 
| 29 | 
            -
                  context " | 
| 30 | 
            -
                     | 
| 39 | 
            +
                context "when not active" do
         | 
| 40 | 
            +
                  context "Appsignal.internal_logger" do
         | 
| 41 | 
            +
                    subject { Appsignal.internal_logger }
         | 
| 31 42 |  | 
| 32 | 
            -
                    it " | 
| 43 | 
            +
                    it "sets a logger" do
         | 
| 33 44 | 
             
                      install_sinatra_integration
         | 
| 34 | 
            -
                       | 
| 35 | 
            -
                        [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
         | 
| 36 | 
            -
                      )
         | 
| 45 | 
            +
                      is_expected.to be_a Logger
         | 
| 37 46 | 
             
                    end
         | 
| 38 47 | 
             
                  end
         | 
| 39 48 |  | 
| 40 | 
            -
                   | 
| 41 | 
            -
                     | 
| 42 | 
            -
                       | 
| 43 | 
            -
             | 
| 44 | 
            -
                         | 
| 45 | 
            -
             | 
| 49 | 
            +
                  describe "middleware" do
         | 
| 50 | 
            +
                    context "when AppSignal is not active" do
         | 
| 51 | 
            +
                      it "does not add the instrumentation middleware to Sinatra::Base" do
         | 
| 52 | 
            +
                        install_sinatra_integration
         | 
| 53 | 
            +
                        expect(Sinatra::Base.middleware.to_a).to_not include(
         | 
| 54 | 
            +
                          [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
         | 
| 55 | 
            +
                        )
         | 
| 56 | 
            +
                      end
         | 
| 46 57 | 
             
                    end
         | 
| 47 | 
            -
                  end
         | 
| 48 | 
            -
                end
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                describe "environment" do
         | 
| 51 | 
            -
                  subject { Appsignal.config.env }
         | 
| 52 58 |  | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 59 | 
            +
                    context "when the new AppSignal config is active" do
         | 
| 60 | 
            +
                      it "adds the instrumentation middleware to Sinatra::Base" do
         | 
| 61 | 
            +
                        ENV["APPSIGNAL_APP_NAME"] = "My Sinatra app name"
         | 
| 62 | 
            +
                        ENV["APPSIGNAL_APP_ENV"] = "test"
         | 
| 63 | 
            +
                        ENV["APPSIGNAL_PUSH_API_KEY"] = "my-key"
         | 
| 55 64 |  | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 65 | 
            +
                        install_sinatra_integration
         | 
| 66 | 
            +
                        expect(Sinatra::Base.middleware.to_a).to include(
         | 
| 67 | 
            +
                          [Appsignal::Rack::SinatraBaseInstrumentation, [], nil]
         | 
| 68 | 
            +
                        )
         | 
| 69 | 
            +
                      end
         | 
| 58 70 | 
             
                    end
         | 
| 59 71 | 
             
                  end
         | 
| 60 72 |  | 
| 61 | 
            -
                   | 
| 62 | 
            -
                     | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 73 | 
            +
                  describe "environment" do
         | 
| 74 | 
            +
                    subject { Appsignal.config.env }
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    context "without APPSIGNAL_APP_ENV" do
         | 
| 77 | 
            +
                      before { install_sinatra_integration }
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                      it "uses the app environment" do
         | 
| 80 | 
            +
                        expect(subject).to eq("test")
         | 
| 81 | 
            +
                      end
         | 
| 65 82 | 
             
                    end
         | 
| 66 83 |  | 
| 67 | 
            -
                     | 
| 68 | 
            -
                       | 
| 84 | 
            +
                    context "with APPSIGNAL_APP_ENV" do
         | 
| 85 | 
            +
                      before do
         | 
| 86 | 
            +
                        ENV["APPSIGNAL_APP_ENV"] = "env-staging"
         | 
| 87 | 
            +
                        install_sinatra_integration
         | 
| 88 | 
            +
                      end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                      it "uses the environment variable" do
         | 
| 91 | 
            +
                        expect(subject).to eq("env-staging")
         | 
| 92 | 
            +
                      end
         | 
| 69 93 | 
             
                    end
         | 
| 70 94 | 
             
                  end
         | 
| 71 95 | 
             
                end
         | 
| @@ -0,0 +1,250 @@ | |
| 1 | 
            +
            describe Appsignal::Rack::AbstractMiddleware do
         | 
| 2 | 
            +
              let(:app) { double(:call => true) }
         | 
| 3 | 
            +
              let(:request_path) { "/some/path" }
         | 
| 4 | 
            +
              let(:env) do
         | 
| 5 | 
            +
                Rack::MockRequest.env_for(
         | 
| 6 | 
            +
                  request_path,
         | 
| 7 | 
            +
                  "REQUEST_METHOD" => "GET",
         | 
| 8 | 
            +
                  :params => { "page" => 2, "query" => "lorem" }
         | 
| 9 | 
            +
                )
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
              let(:options) { {} }
         | 
| 12 | 
            +
              let(:middleware) { Appsignal::Rack::AbstractMiddleware.new(app, options) }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              before(:context) { start_agent }
         | 
| 15 | 
            +
              around { |example| keep_transactions { example.run } }
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def make_request(env)
         | 
| 18 | 
            +
                middleware.call(env)
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              def make_request_with_error(env, error)
         | 
| 22 | 
            +
                expect { make_request(env) }.to raise_error(error)
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              describe "#call" do
         | 
| 26 | 
            +
                context "when appsignal is not active" do
         | 
| 27 | 
            +
                  before { allow(Appsignal).to receive(:active?).and_return(false) }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  it "does not instrument requests" do
         | 
| 30 | 
            +
                    expect { make_request(env) }.to_not(change { created_transactions.count })
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  it "calls the next middleware in the stack" do
         | 
| 34 | 
            +
                    expect(app).to receive(:call).with(env)
         | 
| 35 | 
            +
                    make_request(env)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                context "when appsignal is active" do
         | 
| 40 | 
            +
                  before { allow(Appsignal).to receive(:active?).and_return(true) }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  it "calls the next middleware in the stack" do
         | 
| 43 | 
            +
                    make_request(env)
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    expect(app).to have_received(:call).with(env)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  context "without an exception" do
         | 
| 49 | 
            +
                    it "create a transaction for the request" do
         | 
| 50 | 
            +
                      expect { make_request(env) }.to(change { created_transactions.count }.by(1))
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                      expect(last_transaction.to_h).to include(
         | 
| 53 | 
            +
                        "namespace" => Appsignal::Transaction::HTTP_REQUEST,
         | 
| 54 | 
            +
                        "action" => nil,
         | 
| 55 | 
            +
                        "error" => nil
         | 
| 56 | 
            +
                      )
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    it "reports a process.abstract event" do
         | 
| 60 | 
            +
                      make_request(env)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                      expect(last_transaction.to_h).to include(
         | 
| 63 | 
            +
                        "events" => [
         | 
| 64 | 
            +
                          hash_including(
         | 
| 65 | 
            +
                            "body" => "",
         | 
| 66 | 
            +
                            "body_format" => Appsignal::EventFormatter::DEFAULT,
         | 
| 67 | 
            +
                            "count" => 1,
         | 
| 68 | 
            +
                            "name" => "process.abstract",
         | 
| 69 | 
            +
                            "title" => ""
         | 
| 70 | 
            +
                          )
         | 
| 71 | 
            +
                        ]
         | 
| 72 | 
            +
                      )
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    it "completes the transaction" do
         | 
| 76 | 
            +
                      make_request(env)
         | 
| 77 | 
            +
                      expect(last_transaction).to be_completed
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  context "with an exception" do
         | 
| 82 | 
            +
                    let(:error) { ExampleException.new("error message") }
         | 
| 83 | 
            +
                    before do
         | 
| 84 | 
            +
                      allow(app).to receive(:call).and_raise(error)
         | 
| 85 | 
            +
                      expect { make_request_with_error(env, error) }
         | 
| 86 | 
            +
                        .to(change { created_transactions.count }.by(1))
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    it "creates a transaction for the request and records the exception" do
         | 
| 90 | 
            +
                      expect(last_transaction.to_h).to include(
         | 
| 91 | 
            +
                        "error" => hash_including(
         | 
| 92 | 
            +
                          "name" => "ExampleException",
         | 
| 93 | 
            +
                          "message" => "error message"
         | 
| 94 | 
            +
                        )
         | 
| 95 | 
            +
                      )
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    it "completes the transaction" do
         | 
| 99 | 
            +
                      expect(last_transaction).to be_completed
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  context "without action name metadata" do
         | 
| 104 | 
            +
                    it "reports no action name" do
         | 
| 105 | 
            +
                      make_request(env)
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                      expect(last_transaction.to_h).to include("action" => nil)
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  context "with appsignal.route env" do
         | 
| 112 | 
            +
                    before do
         | 
| 113 | 
            +
                      env["appsignal.route"] = "POST /my-route"
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    it "reports the appsignal.route value as the action name" do
         | 
| 117 | 
            +
                      make_request(env)
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                      expect(last_transaction.to_h).to include("action" => "POST /my-route")
         | 
| 120 | 
            +
                    end
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  context "with appsignal.action env" do
         | 
| 124 | 
            +
                    before do
         | 
| 125 | 
            +
                      env["appsignal.action"] = "POST /my-action"
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    it "reports the appsignal.route value as the action name" do
         | 
| 129 | 
            +
                      make_request(env)
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                      expect(last_transaction.to_h).to include("action" => "POST /my-action")
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  describe "request metadata" do
         | 
| 136 | 
            +
                    before do
         | 
| 137 | 
            +
                      env.merge("PATH_INFO" => "/some/path", "REQUEST_METHOD" => "GET")
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                    it "sets request metadata" do
         | 
| 141 | 
            +
                      make_request(env)
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                      expect(last_transaction.to_h).to include(
         | 
| 144 | 
            +
                        "metadata" => {
         | 
| 145 | 
            +
                          "method" => "GET",
         | 
| 146 | 
            +
                          "path" => "/some/path"
         | 
| 147 | 
            +
                        },
         | 
| 148 | 
            +
                        "sample_data" => hash_including(
         | 
| 149 | 
            +
                          "environment" => hash_including(
         | 
| 150 | 
            +
                            "REQUEST_METHOD" => "GET",
         | 
| 151 | 
            +
                            "PATH_INFO" => "/some/path"
         | 
| 152 | 
            +
                            # and more, but we don't need to test Rack mock defaults
         | 
| 153 | 
            +
                          )
         | 
| 154 | 
            +
                        )
         | 
| 155 | 
            +
                      )
         | 
| 156 | 
            +
                    end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                    it "sets request parameters" do
         | 
| 159 | 
            +
                      make_request(env)
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                      expect(last_transaction.to_h).to include(
         | 
| 162 | 
            +
                        "sample_data" => hash_including(
         | 
| 163 | 
            +
                          "params" => hash_including(
         | 
| 164 | 
            +
                            "page" => "2",
         | 
| 165 | 
            +
                            "query" => "lorem"
         | 
| 166 | 
            +
                          )
         | 
| 167 | 
            +
                        )
         | 
| 168 | 
            +
                      )
         | 
| 169 | 
            +
                    end
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  context "with queue start header" do
         | 
| 173 | 
            +
                    let(:queue_start_time) { fixed_time * 1_000 }
         | 
| 174 | 
            +
                    before do
         | 
| 175 | 
            +
                      env["HTTP_X_REQUEST_START"] = "t=#{queue_start_time.to_i}" # in milliseconds
         | 
| 176 | 
            +
                    end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                    it "sets the queue start" do
         | 
| 179 | 
            +
                      make_request(env)
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                      expect(last_transaction.ext.queue_start).to eq(queue_start_time)
         | 
| 182 | 
            +
                    end
         | 
| 183 | 
            +
                  end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                  class FilteredRequest
         | 
| 186 | 
            +
                    attr_reader :env
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                    def initialize(env)
         | 
| 189 | 
            +
                      @env = env
         | 
| 190 | 
            +
                    end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                    def path
         | 
| 193 | 
            +
                      "/static/path"
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                    def request_method
         | 
| 197 | 
            +
                      "GET"
         | 
| 198 | 
            +
                    end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                    def filtered_params
         | 
| 201 | 
            +
                      { "abc" => "123" }
         | 
| 202 | 
            +
                    end
         | 
| 203 | 
            +
                  end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  context "with overridden request class and params method" do
         | 
| 206 | 
            +
                    let(:options) do
         | 
| 207 | 
            +
                      { :request_class => FilteredRequest, :params_method => :filtered_params }
         | 
| 208 | 
            +
                    end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                    it "uses the overridden request class and params method to fetch params" do
         | 
| 211 | 
            +
                      make_request(env)
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                      expect(last_transaction.to_h).to include(
         | 
| 214 | 
            +
                        "sample_data" => hash_including(
         | 
| 215 | 
            +
                          "params" => { "abc" => "123" }
         | 
| 216 | 
            +
                        )
         | 
| 217 | 
            +
                      )
         | 
| 218 | 
            +
                    end
         | 
| 219 | 
            +
                  end
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                  context "with parent instrumentation" do
         | 
| 222 | 
            +
                    before do
         | 
| 223 | 
            +
                      env[Appsignal::Rack::APPSIGNAL_TRANSACTION] = http_request_transaction
         | 
| 224 | 
            +
                    end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                    it "uses the existing transaction" do
         | 
| 227 | 
            +
                      make_request(env)
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                      expect { make_request(env) }.to_not(change { created_transactions.count })
         | 
| 230 | 
            +
                    end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                    it "doesn't complete the existing transaction" do
         | 
| 233 | 
            +
                      make_request(env)
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                      expect(env[Appsignal::Rack::APPSIGNAL_TRANSACTION]).to_not be_completed
         | 
| 236 | 
            +
                    end
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                    context "with custom set action name" do
         | 
| 239 | 
            +
                      it "does not overwrite the action name" do
         | 
| 240 | 
            +
                        env[Appsignal::Rack::APPSIGNAL_TRANSACTION].set_action("My custom action")
         | 
| 241 | 
            +
                        env["appsignal.action"] = "POST /my-action"
         | 
| 242 | 
            +
                        make_request(env)
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                        expect(last_transaction.to_h).to include("action" => "My custom action")
         | 
| 245 | 
            +
                      end
         | 
| 246 | 
            +
                    end
         | 
| 247 | 
            +
                  end
         | 
| 248 | 
            +
                end
         | 
| 249 | 
            +
              end
         | 
| 250 | 
            +
            end
         | 
| @@ -11,6 +11,7 @@ describe Appsignal::Rack::EventHandler do | |
| 11 11 | 
             
              let(:response) { nil }
         | 
| 12 12 | 
             
              let(:log_stream) { StringIO.new }
         | 
| 13 13 | 
             
              let(:log) { log_contents(log_stream) }
         | 
| 14 | 
            +
              let(:event_handler_instance) { described_class.new }
         | 
| 14 15 | 
             
              before do
         | 
| 15 16 | 
             
                start_agent
         | 
| 16 17 | 
             
                Appsignal.internal_logger = test_logger(log_stream)
         | 
| @@ -18,7 +19,7 @@ describe Appsignal::Rack::EventHandler do | |
| 18 19 | 
             
              around { |example| keep_transactions { example.run } }
         | 
| 19 20 |  | 
| 20 21 | 
             
              def on_start
         | 
| 21 | 
            -
                 | 
| 22 | 
            +
                event_handler_instance.on_start(request, response)
         | 
| 22 23 | 
             
              end
         | 
| 23 24 |  | 
| 24 25 | 
             
              describe "#on_start" do
         | 
| @@ -34,6 +35,14 @@ describe Appsignal::Rack::EventHandler do | |
| 34 35 | 
             
                  expect(Appsignal::Transaction.current).to eq(last_transaction)
         | 
| 35 36 | 
             
                end
         | 
| 36 37 |  | 
| 38 | 
            +
                context "when the handler is nested in another EventHandler" do
         | 
| 39 | 
            +
                  it "does not create a new transaction in the nested EventHandler" do
         | 
| 40 | 
            +
                    on_start
         | 
| 41 | 
            +
                    expect { described_class.new.on_start(request, response) }
         | 
| 42 | 
            +
                      .to_not(change { created_transactions.length })
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 37 46 | 
             
                it "registers transaction on the request environment" do
         | 
| 38 47 | 
             
                  on_start
         | 
| 39 48 |  | 
| @@ -87,7 +96,7 @@ describe Appsignal::Rack::EventHandler do | |
| 87 96 |  | 
| 88 97 | 
             
              describe "#on_error" do
         | 
| 89 98 | 
             
                def on_error(error)
         | 
| 90 | 
            -
                   | 
| 99 | 
            +
                  event_handler_instance.on_error(request, response, error)
         | 
| 91 100 | 
             
                end
         | 
| 92 101 |  | 
| 93 102 | 
             
                it "reports the error" do
         | 
| @@ -103,6 +112,15 @@ describe Appsignal::Rack::EventHandler do | |
| 103 112 | 
             
                  )
         | 
| 104 113 | 
             
                end
         | 
| 105 114 |  | 
| 115 | 
            +
                context "when the handler is nested in another EventHandler" do
         | 
| 116 | 
            +
                  it "does not report the error on the transaction" do
         | 
| 117 | 
            +
                    on_start
         | 
| 118 | 
            +
                    described_class.new.on_error(request, response, ExampleStandardError.new("the error"))
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                    expect(last_transaction.to_h).to include("error" => nil)
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 106 124 | 
             
                it "logs an error in case of an internal error" do
         | 
| 107 125 | 
             
                  on_start
         | 
| 108 126 |  | 
| @@ -122,7 +140,7 @@ describe Appsignal::Rack::EventHandler do | |
| 122 140 | 
             
                let(:response) { Rack::Events::BufferedResponse.new(200, {}, ["body"]) }
         | 
| 123 141 |  | 
| 124 142 | 
             
                def on_finish
         | 
| 125 | 
            -
                   | 
| 143 | 
            +
                  event_handler_instance.on_finish(request, response)
         | 
| 126 144 | 
             
                end
         | 
| 127 145 |  | 
| 128 146 | 
             
                it "doesn't do anything without a transaction" do
         | 
| @@ -155,6 +173,22 @@ describe Appsignal::Rack::EventHandler do | |
| 155 173 | 
             
                    )
         | 
| 156 174 | 
             
                  )
         | 
| 157 175 | 
             
                  expect(last_transaction.ext.queue_start).to eq(queue_start_time)
         | 
| 176 | 
            +
                  expect(last_transaction).to be_completed
         | 
| 177 | 
            +
                end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                context "when the handler is nested in another EventHandler" do
         | 
| 180 | 
            +
                  it "does not complete the transaction" do
         | 
| 181 | 
            +
                    on_start
         | 
| 182 | 
            +
                    described_class.new.on_finish(request, response)
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                    expect(last_transaction.to_h).to include(
         | 
| 185 | 
            +
                      "action" => nil,
         | 
| 186 | 
            +
                      "metadata" => {},
         | 
| 187 | 
            +
                      "sample_data" => {},
         | 
| 188 | 
            +
                      "events" => []
         | 
| 189 | 
            +
                    )
         | 
| 190 | 
            +
                    expect(last_transaction).to_not be_completed
         | 
| 191 | 
            +
                  end
         | 
| 158 192 | 
             
                end
         | 
| 159 193 |  | 
| 160 194 | 
             
                it "doesn't set the action name if already set" do
         | 
| @@ -183,6 +217,25 @@ describe Appsignal::Rack::EventHandler do | |
| 183 217 | 
             
                  )
         | 
| 184 218 | 
             
                end
         | 
| 185 219 |  | 
| 220 | 
            +
                it "sets the response status as a tag" do
         | 
| 221 | 
            +
                  on_start
         | 
| 222 | 
            +
                  on_finish
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                  expect(last_transaction.to_h).to include(
         | 
| 225 | 
            +
                    "sample_data" => hash_including(
         | 
| 226 | 
            +
                      "tags" => { "response_status" => 200 }
         | 
| 227 | 
            +
                    )
         | 
| 228 | 
            +
                  )
         | 
| 229 | 
            +
                end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                it "increments the response status counter for response status" do
         | 
| 232 | 
            +
                  expect(Appsignal).to receive(:increment_counter)
         | 
| 233 | 
            +
                    .with(:response_status, 1, :status => 200, :namespace => :web)
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                  on_start
         | 
| 236 | 
            +
                  on_finish
         | 
| 237 | 
            +
                end
         | 
| 238 | 
            +
             | 
| 186 239 | 
             
                it "logs an error in case of an error" do
         | 
| 187 240 | 
             
                  expect(Appsignal::Transaction)
         | 
| 188 241 | 
             
                    .to receive(:complete_current!).and_raise(ExampleStandardError, "oh no")
         |