diff --git a/README.md b/README.md index 2469f4c..67c1d9d 100644 --- a/README.md +++ b/README.md @@ -86,13 +86,14 @@ Cable.configure do |settings| settings.backend_class = Cable::RedisBackend settings.backend_ping_interval = 15.seconds settings.restart_error_allowance = 20 - settings.on_error = ->(error : Exception, message : String) do + settings.on_error = ->(error : Exception, message : String, connection : Cable::Connection?) do # or whichever error reportings you're using Bugsnag.report(error) do |event| event.app.app_type = "lucky" event.meta_data = { "error_class" => JSON::Any.new(error.class.name), "message" => JSON::Any.new(message), + "token" => JSON::Any.new(connection.try(&.token).to_s), } end end @@ -283,7 +284,7 @@ You can setup a hook to report errors to any 3rd party service you choose. ```crystal # config/cable.cr Cable.configure do |settings| - settings.on_error = ->(exception : Exception, message : String) do + settings.on_error = ->(exception : Exception, message : String, connection : Cable::Connection?) do # new 3rd part service handler ExceptionService.notify(exception, message: message) # default logic @@ -295,13 +296,13 @@ end ```crystal Habitat.create do - setting on_error : Proc(Exception, String, Nil) = ->(exception : Exception, message : String) do + setting on_error : Proc(Exception, String, Cable::Connection?, Nil) = ->(exception : Exception, message : String, connection : Cable::Connection?) do Cable::Logger.error(exception: exception) { message } end end ``` -> NOTE: The message field will contain details regarding which class/method raised the error +> NOTE: The message field will contain details regarding which class/method raised the error. The connection parameter provides access to the `Cable::Connection` instance (when available), including the `token`, `connection_identifier`, and any fields defined via `identified_by` or `owned_by`. ## Client-Side diff --git a/spec/cable/handler_spec.cr b/spec/cable/handler_spec.cr index 30ea58c..953e6d3 100644 --- a/spec/cable/handler_spec.cr +++ b/spec/cable/handler_spec.cr @@ -132,8 +132,10 @@ describe Cable::Handler do end FakeExceptionService.size.should eq(1) - FakeExceptionService.exceptions.first.keys.first.should eq("Cable::Handler#socket.on_message") - FakeExceptionService.exceptions.first.values.first.class.should eq(JSON::SerializableError) + exception = FakeExceptionService.exceptions.first + exception.message.should contain("Cable::Handler#socket.on_message") + exception.exception.class.should eq(JSON::SerializableError) + exception.connection.as(Cable::Connection).token.should eq("1") end it "rejected" do diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 582f947..ca4d3ec 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -16,8 +16,8 @@ Cable.configure do |settings| settings.backend_class = Cable::RedisBackend settings.backend_ping_interval = 2.seconds settings.restart_error_allowance = 2 - settings.on_error = ->(exception : Exception, message : String) do - FakeExceptionService.notify(exception, message: message) + settings.on_error = ->(exception : Exception, message : String, connection : Cable::Connection?) do + FakeExceptionService.notify(exception, message: message, connection: connection) end end diff --git a/spec/support/fake_exception_service.cr b/spec/support/fake_exception_service.cr index bd631e6..921a5e1 100644 --- a/spec/support/fake_exception_service.cr +++ b/spec/support/fake_exception_service.cr @@ -1,8 +1,9 @@ class FakeExceptionService - @@exceptions : Array(Hash(String, Exception)) = [] of Hash(String, Exception) + record Report, exception : Exception, message : String, connection : Cable::Connection? + @@exceptions : Array(Report) = [] of Report def self.clear - @@exceptions = [] of Hash(String, Exception) + @@exceptions = [] of Report end def self.size @@ -13,7 +14,7 @@ class FakeExceptionService @@exceptions end - def self.notify(exception, message) - @@exceptions << {message => exception} + def self.notify(exception, message, connection = nil) + @@exceptions << Report.new(exception: exception, message: message, connection: connection) end end diff --git a/src/cable.cr b/src/cable.cr index e47bc6d..00892d1 100644 --- a/src/cable.cr +++ b/src/cable.cr @@ -37,7 +37,8 @@ module Cable setting backend_class : Cable::BackendCore.class = Cable::BackendRegistry, example: "Cable::RedisBackend" setting backend_ping_interval : Time::Span = 15.seconds setting restart_error_allowance : Int32 = 20 - setting on_error : Proc(Exception, String, Nil) = ->(exception : Exception, message : String) do + # ameba:disable Lint/UnusedArgument + setting on_error : Proc(Exception, String, Cable::Connection?, Nil) = ->(exception : Exception, message : String, connection : Cable::Connection?) do Cable::Logger.error(exception: exception) { message } end end diff --git a/src/cable/connection.cr b/src/cable/connection.cr index 4fc08b3..acff7e0 100644 --- a/src/cable/connection.cr +++ b/src/cable/connection.cr @@ -77,7 +77,7 @@ module Cable channels_to_close.each do |identifier, channel| channel.close rescue e : IO::Error - Cable.settings.on_error.call(e, "IO::Error: #{e.message} -> #{self.class.name}#close") + Cable.settings.on_error.call(e, "IO::Error: #{e.message} -> #{self.class.name}#close", self) end unsubscribe_from_internal_channel end @@ -185,7 +185,7 @@ module Cable Cable::Logger.info { "#{channel.class}#receive(#{payload.data})" } channel.receive(payload.data) rescue e : TypeCastError - Cable.settings.on_error.call(e, "Exception: #{e.message} -> #{self.class.name}#message(payload) { #{payload.inspect} }") + Cable.settings.on_error.call(e, "Exception: #{e.message} -> #{self.class.name}#message(payload) { #{payload.inspect} }", self) end end end diff --git a/src/cable/handler.cr b/src/cable/handler.cr index 360357d..8b57d2a 100644 --- a/src/cable/handler.cr +++ b/src/cable/handler.cr @@ -51,7 +51,7 @@ module Cable ws_pinger.stop socket.close(HTTP::WebSocket::CloseCode::InvalidFramePayloadData, "Invalid message") Cable.server.remove_connection(connection_id) - Cable.settings.on_error.call(e, "Cable::Handler#socket.on_message") + Cable.settings.on_error.call(e, "Cable::Handler#socket.on_message -> #{message}", connection) rescue e : Cable::Connection::UnauthorizedConnectionException # handle unauthorized connections # no need to log them @@ -69,7 +69,7 @@ module Cable # handle restart Cable.server.count_error! Cable.restart if Cable.server.restart? - Cable.settings.on_error.call(e, "Cable::Handler#socket.on_message") + Cable.settings.on_error.call(e, "Cable::Handler#socket.on_message -> #{message}", connection) end end @@ -84,7 +84,7 @@ module Cable if conn_id = connection_id Cable.server.remove_connection(conn_id) end - Cable.settings.on_error.call(e, "Cable::Handler#call -> HTTP::WebSocketHandler") + Cable.settings.on_error.call(e, "Cable::Handler#call -> HTTP::WebSocketHandler", connection) raise e end diff --git a/src/cable/server.cr b/src/cable/server.cr index f1fcd62..2e35744 100644 --- a/src/cable/server.cr +++ b/src/cable/server.cr @@ -53,7 +53,7 @@ module Cable subscribe process_subscribed_messages rescue e - Cable.settings.on_error.call(e, "Cable::Server.initialize") + Cable.settings.on_error.call(e, "Cable::Server.initialize", nil) raise e end end @@ -151,7 +151,7 @@ module Cable end end rescue e : IO::Error - Cable.settings.on_error.call(e, "IO::Error Exception: #{e.message}: #{parsed_message} -> Cable::Server#send_to_channels(channel, message)") + Cable.settings.on_error.call(e, "IO::Error Exception: #{e.message}: #{parsed_message} -> Cable::Server#send_to_channels(channel, message)", nil) end end