Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions lib/jsonrpc/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'logger'

module JSONRPC
# Configuration class for JSON-RPC procedure management and validation.
#
Expand Down Expand Up @@ -49,6 +51,20 @@ class Configuration
#
Procedure = Data.define(:allow_positional_arguments, :contract, :parameter_name)

# Logger instance used to emit log messages
#
# @api public
#
# @example Using the default logger
# config.logger # => #<Logger:...>
#
# @example Setting a custom logger
# config.logger = Logger.new($stderr)
#
# @return [Logger] the logger instance
#
attr_accessor :logger

# Whether to log detailed internal error information in the terminal
#
# @api public
Expand Down Expand Up @@ -129,14 +145,16 @@ def json_adapter=(adapter)
# @example With custom options
# config = JSONRPC::Configuration.new(
# log_request_validation_errors: true,
# render_internal_errors: true
# render_internal_errors: true,
# logger: Logger.new($stderr)
# )
#
# @param log_internal_errors [Boolean] whether to log detailed internal error information in the terminal
# @param log_request_validation_errors [Boolean] whether to log validation errors during JSON-RPC request processing
# @param rescue_internal_errors [Boolean] whether internal errors should be rescued and converted to JSON-RPC errors
# @param render_internal_errors [Boolean] whether to render detailed internal error information in responses
# @param validate_procedure_signatures [Boolean] whether procedure signatures are validated
# @param logger [Logger] the logger instance used to emit log messages
#
# @return [Configuration] a new configuration instance
#
Expand All @@ -145,14 +163,16 @@ def initialize(
log_request_validation_errors: false,
rescue_internal_errors: true,
render_internal_errors: false,
validate_procedure_signatures: true
validate_procedure_signatures: true,
logger: Logger.new($stdout)
)
@procedures = {}
@log_internal_errors = log_internal_errors
@log_request_validation_errors = log_request_validation_errors
@rescue_internal_errors = rescue_internal_errors
@render_internal_errors = render_internal_errors
@validate_procedure_signatures = validate_procedure_signatures
@logger = logger
end

# Returns the singleton instance of the Configuration class
Expand Down
12 changes: 7 additions & 5 deletions lib/jsonrpc/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ class Middleware
# @option options [String] :path ('/') The path to handle JSON-RPC requests on
# @option options [Boolean] :rescue_internal_errors (nil) Override config rescue_internal_errors
# @option options [Boolean] :log_internal_errors (true) Override config log_internal_errors
# @option options [Logger] :logger Override the logger used for internal error reporting
#
def initialize(app, options = {})
@app = app
@parser = Parser.new
@validator = Validator.new
@path = options.fetch(:path, DEFAULT_PATH)
@config = JSONRPC.configuration
@logger = options.fetch(:logger, @config.logger)
@parser = Parser.new
@validator = Validator.new(logger: @logger)
@log_internal_errors = options.fetch(:log_internal_errors, @config.log_internal_errors)
@rescue_internal_errors = options.fetch(:rescue_internal_errors, @config.rescue_internal_errors)
end
Expand Down Expand Up @@ -377,7 +379,7 @@ def read_request_body
body_content
end

# Logs internal errors to stdout with full backtrace
# Logs internal errors using the configured logger
#
# @api private
#
Expand All @@ -389,8 +391,8 @@ def read_request_body
# @return [void]
#
def log_internal_error(error)
puts "Internal error: #{error.message}"
puts error.backtrace.join("\n")
@logger.error("Internal error: #{error.message}")
@logger.error(error.backtrace.join("\n"))
end
end
end
22 changes: 20 additions & 2 deletions lib/jsonrpc/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ module JSONRPC
# error = validator.validate(request)
#
class Validator
# Initializes a new Validator
#
# @api public
#
# @example Default initialization
# validator = JSONRPC::Validator.new
#
# @example With a custom logger
# validator = JSONRPC::Validator.new(logger: Logger.new($stderr))
#
# @param logger [Logger] Logger instance for logging validation errors
#
# @return [Validator] a new validator instance
#
def initialize(logger: JSONRPC.configuration.logger)
@logger = logger
end

# Validates a single request, notification or a batch
#
# @api public
Expand Down Expand Up @@ -100,8 +118,8 @@ def validate_request_params(request_or_notification)
nil
rescue StandardError => e
if JSONRPC.configuration.log_request_validation_errors
puts "Validation error: #{e.message}"
puts e.backtrace.join("\n")
@logger.error("Validation error: #{e.message}")
@logger.error(e.backtrace.join("\n"))
end

InternalError.new(request_id: extract_request_id(request_or_notification))
Expand Down
34 changes: 21 additions & 13 deletions spec/jsonrpc/middleware_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -389,12 +389,20 @@
end

context 'when processing a JSON-RPC request that causes an unexpected exception' do
it 'returns HTTP 200 OK with a JSON-RPC internal error' do
# TODO: Remove this in the next iteration
# rubocop:disable RSpec/AnyInstance
allow_any_instance_of(described_class).to receive(:log_internal_error)
# rubocop:enable RSpec/AnyInstance
let(:logger) { instance_double(Logger) }

let(:app) do
test_logger = logger
middleware = described_class
Rack::Builder.new do
use middleware, path: '/jsonrpc', logger: test_logger
run TestApp.new
end
end

before { allow(logger).to receive(:error) }

it 'returns HTTP 200 OK with a JSON-RPC internal error' do
post_jsonrpc_request(
jsonrpc: '2.0',
method: 'explode',
Expand All @@ -413,14 +421,14 @@
)
end

it 'logs the internal error to stdout' do
expect do
post_jsonrpc_request(
jsonrpc: '2.0',
method: 'explode',
id: 'req-internal-error'
)
end.to output(/Internal error: Internal JSON-RPC error\./).to_stdout
it 'logs the internal error using the configured logger' do
post_jsonrpc_request(
jsonrpc: '2.0',
method: 'explode',
id: 'req-internal-error'
)

expect(logger).to have_received(:error).with(/Internal error: Internal JSON-RPC error\./)
end
end

Expand Down