diff --git a/lib/honeybadger/backend/server.rb b/lib/honeybadger/backend/server.rb index b042bd4b..5100a235 100644 --- a/lib/honeybadger/backend/server.rb +++ b/lib/honeybadger/backend/server.rb @@ -11,11 +11,10 @@ module Backend class Server < Base ENDPOINTS = { notices: '/v1/notices'.freeze, - deploys: '/v1/deploys'.freeze + deploys: '/v1/deploys'.freeze, }.freeze - CHECK_IN_ENDPOINT = '/v1/check_in'.freeze - + EVENTS_ENDPOINT = '/v1/events'.freeze HTTP_ERRORS = Util::HTTP::ERRORS @@ -48,6 +47,18 @@ def check_in(id) Response.new(:error, nil, "HTTP Error: #{e.class}") end + # Send event + # @example + # backend.event([{event_type: "email_received", ts: "2023-03-04T12:12:00+1:00", subject: 'Re: Aquisition' }}) + # + # @param [Array] payload array of event hashes to send + # @return [Response] + def event(payload) + Response.new(@http.post_newline_delimited(EVENTS_ENDPOINT, payload)) + rescue *HTTP_ERRORS => e + Response.new(:error, nil, "HTTP Error: #{e.class}") + end + private def payload_headers(payload) diff --git a/lib/honeybadger/util/http.rb b/lib/honeybadger/util/http.rb index 844729e7..d2030415 100644 --- a/lib/honeybadger/util/http.rb +++ b/lib/honeybadger/util/http.rb @@ -49,6 +49,12 @@ def post(endpoint, payload, headers = nil) response end + def post_newline_delimited(endpoint, payload, headers = nil) + response = http_connection.post(endpoint, compress(payload.map(&:to_json).join("\n")), http_headers(headers)) + debug { sprintf("http method=POST path=%s code=%d", endpoint.dump, response.code) } + response + end + private attr_reader :config diff --git a/spec/unit/honeybadger/backend/server_spec.rb b/spec/unit/honeybadger/backend/server_spec.rb index f643fe47..b71c5805 100644 --- a/spec/unit/honeybadger/backend/server_spec.rb +++ b/spec/unit/honeybadger/backend/server_spec.rb @@ -11,6 +11,7 @@ it { should respond_to :notify } it { should respond_to :check_in } + it { should respond_to :event } describe "#check_in" do it "returns a response" do @@ -79,6 +80,53 @@ def notify_backend subject.notify(:notices, payload) end + end + + describe "#event" do + it "returns the response" do + stub_http + expect(send_event).to be_a Honeybadger::Backend::Response + end + + it "adds auth headers" do + http = stub_http + expect(http).to receive(:post).with(anything, anything, hash_including({ 'X-API-Key' => 'abc123'})) + send_event + end + + it "serialises json and compresses" do + http = stub_http + expect(http).to receive(:post) do |path, body, headers| + cleartext_body = Zlib::Inflate.inflate(body) + json = JSON.parse(cleartext_body) + expect(json["ts"]).to_not be_nil + expect(json["event_type"]).to eq("checkout") + expect(json["increment"]).to eq(0) + end + send_event + end + + it "serialises json newline delimited and compresses" do + http = stub_http + expect(http).to receive(:post) do |path, body, headers| + cleartext_body = Zlib::Inflate.inflate(body) + + the_jsons = cleartext_body.split("\n").map { |t| JSON.parse(t) } + expect(the_jsons.length).to eq(2) + expect(the_jsons[0]["ts"]).to_not be_nil + expect(the_jsons[0]["event_type"]).to eq("checkout") + expect(the_jsons[0]["sum"]).to eq("123.23") + expect(the_jsons[0]["increment"]).to eq(0) + expect(the_jsons[1]["increment"]).to eq(1) + end + send_event(2) + end + + def send_event(count=1) + payload = [] + count.times {|i| payload << {ts: DateTime.now.new_offset(0).rfc3339, event_type: "checkout", sum: "123.23", increment: i} } + subject.event(payload) + end end end diff --git a/spec/unit/honeybadger/util/http_spec.rb b/spec/unit/honeybadger/util/http_spec.rb index 9e958401..93da9c7d 100644 --- a/spec/unit/honeybadger/util/http_spec.rb +++ b/spec/unit/honeybadger/util/http_spec.rb @@ -10,6 +10,7 @@ subject { described_class.new(config) } it { should respond_to :post } + it { should respond_to :post_newline_delimited } it { should respond_to :get } it "sends a user agent with version number" do @@ -57,6 +58,11 @@ expect(http_post).to be_a Net::HTTPResponse end + it "returns the response for post_newline_delimited" do + stub_http + expect(http_post_newline_delimited).to be_a Net::HTTPResponse + end + it "returns the response for #get" do stub_http expect(http_get).to be_a Net::HTTPResponse @@ -240,10 +246,30 @@ end end + describe "#post_newline_delimited" do + it "should properly serialize NDJSON and compress" do + http = stub_http + expect(http).to receive(:post) do |path, body, headers| + expect(path).to eq("/v1/foo") + decompressed = Zlib::Inflate.inflate(body) + parts = decompressed.split("\n").map { |part| JSON.parse(part) } + expect(parts.length).to be(2) + + Net::HTTPSuccess.new('1.2', '200', 'OK') + end + http_post_newline_delimited + end + end + def http_post subject.post('/v1/foo', double('Notice', to_json: '{}')) end + def http_post_newline_delimited + ts = DateTime.now.new_offset(0).rfc3339 + subject.post_newline_delimited('/v1/foo', [{ts: ts, event_type: "test"}, {ts: ts, event_type: "test2"}]) + end + def http_get subject.get('/v1/foo') end