Skip to content

Commit 3e64576

Browse files
oakbanithomaszurkan-optimizely
authored andcommitted
feat: Log and Handle Error for Event Dispatcher response (#221)
* WIP * finalize
1 parent c720b02 commit 3e64576

File tree

5 files changed

+138
-22
lines changed

5 files changed

+138
-22
lines changed

lib/optimizely.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def initialize(
7070
)
7171
@logger = logger || NoOpLogger.new
7272
@error_handler = error_handler || NoOpErrorHandler.new
73-
@event_dispatcher = event_dispatcher || EventDispatcher.new
73+
@event_dispatcher = event_dispatcher || EventDispatcher.new(logger: @logger, error_handler: @error_handler)
7474
@user_profile_service = user_profile_service
7575

7676
begin
@@ -701,7 +701,7 @@ def validate_instantiation_options
701701

702702
return if Helpers::Validator.event_dispatcher_valid?(@event_dispatcher)
703703

704-
@event_dispatcher = EventDispatcher.new
704+
@event_dispatcher = EventDispatcher.new(logger: @logger, error_handler: @error_handler)
705705
raise InvalidInputError, 'event_dispatcher'
706706
end
707707

lib/optimizely/event/batch_event_processor.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ class BatchEventProcessor < EventProcessor
3737

3838
def initialize(
3939
event_queue: SizedQueue.new(DEFAULT_QUEUE_CAPACITY),
40-
event_dispatcher: Optimizely::EventDispatcher.new,
40+
event_dispatcher: nil,
4141
batch_size: DEFAULT_BATCH_SIZE,
4242
flush_interval: DEFAULT_BATCH_INTERVAL,
4343
logger: NoOpLogger.new,
4444
notification_center: nil
4545
)
4646
@event_queue = event_queue
4747
@logger = logger
48-
@event_dispatcher = event_dispatcher
48+
@event_dispatcher = event_dispatcher || EventDispatcher.new(logger: @logger)
4949
@batch_size = if (batch_size.is_a? Integer) && positive_number?(batch_size)
5050
batch_size
5151
else

lib/optimizely/event_dispatcher.rb

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2016-2017, Optimizely and contributors
4+
# Copyright 2016-2017, 2019, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
1515
# See the License for the specific language governing permissions and
1616
# limitations under the License.
1717
#
18+
require_relative 'exceptions'
19+
1820
require 'httparty'
1921

2022
module Optimizely
@@ -28,26 +30,46 @@ class EventDispatcher
2830
# @api constants
2931
REQUEST_TIMEOUT = 10
3032

33+
def initialize(logger: nil, error_handler: nil)
34+
@logger = logger || NoOpLogger.new
35+
@error_handler = error_handler || NoOpErrorHandler.new
36+
end
37+
3138
# Dispatch the event being represented by the Event object.
3239
#
3340
# @param event - Event object
3441
def dispatch_event(event)
3542
if event.http_verb == :get
36-
begin
37-
HTTParty.get(event.url, headers: event.headers, query: event.params, timeout: REQUEST_TIMEOUT)
38-
rescue Timeout::Error => e
39-
return e
40-
end
43+
response = HTTParty.get(event.url, headers: event.headers, query: event.params, timeout: REQUEST_TIMEOUT)
44+
4145
elsif event.http_verb == :post
42-
begin
43-
HTTParty.post(event.url,
44-
body: event.params.to_json,
45-
headers: event.headers,
46-
timeout: REQUEST_TIMEOUT)
47-
rescue Timeout::Error => e
48-
return e
49-
end
46+
response = HTTParty.post(event.url,
47+
body: event.params.to_json,
48+
headers: event.headers,
49+
timeout: REQUEST_TIMEOUT)
5050
end
51+
52+
error_msg = "Event failed to dispatch with response code: #{response.code}"
53+
54+
case response.code
55+
when 400...500
56+
@logger.log(Logger::ERROR, error_msg)
57+
@error_handler.handle_error(HTTPCallError.new("HTTP Client Error: #{response.code}"))
58+
59+
when 500...600
60+
@logger.log(Logger::ERROR, error_msg)
61+
@error_handler.handle_error(HTTPCallError.new("HTTP Server Error: #{response.code}"))
62+
end
63+
rescue Timeout::Error => e
64+
@logger.log(Logger::ERROR, "Request Timed out. Error: #{e}")
65+
@error_handler.handle_error(e)
66+
67+
# Returning Timeout error to retain existing behavior.
68+
e
69+
rescue StandardError => e
70+
@logger.log(Logger::ERROR, "Event failed to dispatch. Error: #{e}")
71+
@error_handler.handle_error(e)
72+
nil
5173
end
5274
end
5375
end

lib/optimizely/exceptions.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
module Optimizely
1919
class Error < StandardError; end
2020

21+
class HTTPCallError < Error
22+
# Raised when a 4xx or 5xx response code is recieved.
23+
def initialize(msg = 'HTTP call resulted in a response with an error code.')
24+
super
25+
end
26+
end
27+
2128
class InvalidAudienceError < Error
2229
# Raised when an invalid audience is provided
2330

spec/event_dispatcher_spec.rb

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
#
4-
# Copyright 2016-2017, Optimizely and contributors
4+
# Copyright 2016-2017, 2019, Optimizely and contributors
55
#
66
# Licensed under the Apache License, Version 2.0 (the "License");
77
# you may not use this file except in compliance with the License.
@@ -16,11 +16,13 @@
1616
# limitations under the License.
1717
#
1818
require 'spec_helper'
19-
require 'webmock'
20-
require 'optimizely/event_builder'
2119
require 'optimizely/event_dispatcher'
20+
require 'optimizely/exceptions'
2221

2322
describe Optimizely::EventDispatcher do
23+
let(:error_handler) { spy(Optimizely::NoOpErrorHandler.new) }
24+
let(:spy_logger) { spy('logger') }
25+
2426
before(:context) do
2527
@url = 'https://www.optimizely.com'
2628
@params = {
@@ -34,6 +36,9 @@
3436

3537
before(:example) do
3638
@event_dispatcher = Optimizely::EventDispatcher.new
39+
@customized_event_dispatcher = Optimizely::EventDispatcher.new(
40+
logger: spy_logger, error_handler: error_handler
41+
)
3742
end
3843

3944
it 'should properly dispatch V2 (POST) events' do
@@ -64,7 +69,7 @@
6469
expect(a_request(:get, get_url)).to have_been_made.once
6570
end
6671

67-
it 'should properly dispatch V2 (GET) events' do
72+
it 'should properly dispatch V2 (GET) events with timeout exception' do
6873
get_url = @url + '?a=111001&g=111028&n=test_event&u=test_user'
6974
stub_request(:get, get_url)
7075
event = Optimizely::Event.new(:get, get_url, @params, @post_headers)
@@ -74,4 +79,86 @@
7479

7580
expect(result).to eq(timeout_error)
7681
end
82+
83+
it 'should log and handle Timeout error' do
84+
get_url = @url + '?a=111001&g=111028&n=test_event&u=test_user'
85+
stub_request(:post, get_url)
86+
event = Optimizely::Event.new(:post, get_url, @params, @post_headers)
87+
timeout_error = Timeout::Error.new
88+
allow(HTTParty).to receive(:post).with(any_args).and_raise(timeout_error)
89+
result = @customized_event_dispatcher.dispatch_event(event)
90+
91+
expect(result).to eq(timeout_error)
92+
expect(spy_logger).to have_received(:log).with(
93+
Logger::ERROR, 'Request Timed out. Error: Timeout::Error'
94+
).once
95+
96+
expect(error_handler).to have_received(:handle_error).once.with(Timeout::Error)
97+
end
98+
99+
it 'should log and handle any standard error' do
100+
get_url = @url + '?a=111001&g=111028&n=test_event&u=test_user'
101+
stub_request(:post, get_url)
102+
event = Optimizely::Event.new(:post, get_url, @params, @post_headers)
103+
error = ArgumentError
104+
allow(HTTParty).to receive(:post).with(any_args).and_raise(error)
105+
result = @customized_event_dispatcher.dispatch_event(event)
106+
107+
expect(result).to eq(nil)
108+
expect(spy_logger).to have_received(:log).with(
109+
Logger::ERROR, 'Event failed to dispatch. Error: ArgumentError'
110+
).once
111+
112+
expect(error_handler).to have_received(:handle_error).once.with(ArgumentError)
113+
end
114+
115+
it 'should log and handle any response with status code 4xx' do
116+
stub_request(:post, @url).to_return(status: 499)
117+
event = Optimizely::Event.new(:post, @url, @params, @post_headers)
118+
119+
@customized_event_dispatcher.dispatch_event(event)
120+
121+
expect(spy_logger).to have_received(:log).with(
122+
Logger::ERROR, 'Event failed to dispatch with response code: 499'
123+
).once
124+
125+
error = Optimizely::HTTPCallError.new('HTTP Client Error: 499')
126+
expect(error_handler).to have_received(:handle_error).once.with(error)
127+
end
128+
129+
it 'should log and handle any response with status code 5xx' do
130+
stub_request(:post, @url).to_return(status: 500)
131+
event = Optimizely::Event.new(:post, @url, @params, @post_headers)
132+
133+
@customized_event_dispatcher.dispatch_event(event)
134+
135+
expect(spy_logger).to have_received(:log).with(
136+
Logger::ERROR, 'Event failed to dispatch with response code: 500'
137+
).once
138+
139+
error = Optimizely::HTTPCallError.new('HTTP Server Error: 500')
140+
expect(error_handler).to have_received(:handle_error).once.with(error)
141+
end
142+
143+
it 'should do nothing on response with status code 3xx' do
144+
stub_request(:post, @url).to_return(status: 399)
145+
event = Optimizely::Event.new(:post, @url, @params, @post_headers)
146+
147+
response = @customized_event_dispatcher.dispatch_event(event)
148+
149+
expect(response).to be_nil
150+
expect(spy_logger).not_to have_received(:log)
151+
expect(error_handler).not_to have_received(:handle_error)
152+
end
153+
154+
it 'should do nothing on response with status code 600' do
155+
stub_request(:post, @url).to_return(status: 600)
156+
event = Optimizely::Event.new(:post, @url, @params, @post_headers)
157+
158+
response = @customized_event_dispatcher.dispatch_event(event)
159+
160+
expect(response).to be_nil
161+
expect(spy_logger).not_to have_received(:log)
162+
expect(error_handler).not_to have_received(:handle_error)
163+
end
77164
end

0 commit comments

Comments
 (0)