Errors and logging¶
The errors the client can get¶
These are the errors jsonrpcserver sends by itself. Your own errors, from
Error or JsonRpcError, are sent exactly as you made them.
| Code | Message | When | data |
|---|---|---|---|
| -32700 | Parse error | The request isn't valid JSON, or your deserializer raised. |
The deserializer's exception message. |
| -32600 | Invalid request | The JSON isn't a valid request, a batch is empty, or a batch is bigger than max_batch_size. |
"The request failed schema validation", or the batch size and the limit. |
| -32601 | Method not found | No method has that name. | The method name from the request. |
| -32602 | Invalid params | The params don't fit the method's signature. | Python's explanation, which names the parameter, such as "missing a required argument: 'name'". |
| -32603 | Internal error | The method raised an exception, returned something other than Success or Error, or returned a result the serializer couldn't handle. |
None. With debug=True, the exception message. |
| -32000 | Server error | Something failed inside jsonrpcserver, outside any method. For example, a custom validator let through a request with no method. |
None. With debug=True, the exception message. |
debug only changes the last two rows. The data in the other rows always
reaches the client. Keep that in mind if you write a custom deserializer,
because its exception messages are sent as they are.
Changed in 5.0.10
From 5.0.0 to 5.0.9, the -32603 and -32000 errors always carried the
exception message. It could hold passwords, paths or SQL. See
Security. 4.x had a debug option that hid it by default,
and 5.0.10 brings that back.
Four ways for a method to fail¶
import logging
from jsonrpcserver import Error, InvalidParams, JsonRpcError, Result, Success, dispatch
def by_return(amount: int) -> Result:
return Error(1, "Insufficient funds", {"balance": 10})
def by_raise(amount: int) -> Result:
raise JsonRpcError(1, "Insufficient funds", {"balance": 10})
def bad_value(amount: int) -> Result:
return InvalidParams("amount must be positive")
def by_accident(amount: int) -> Result:
return Success(amount / 0)
methods = {
"by_return": by_return,
"by_raise": by_raise,
"bad_value": bad_value,
"by_accident": by_accident,
}
>>> logging.disable(logging.CRITICAL) # Keep the traceback out of this page.
>>> dispatch('{"jsonrpc": "2.0", "method": "by_return", "params": [50], "id": 1}', methods)
'{"jsonrpc": "2.0", "error": {"code": 1, "message": "Insufficient funds", "data": {"balance": 10}}, "id": 1}'
>>> dispatch('{"jsonrpc": "2.0", "method": "by_raise", "params": [50], "id": 1}', methods)
'{"jsonrpc": "2.0", "error": {"code": 1, "message": "Insufficient funds", "data": {"balance": 10}}, "id": 1}'
>>> dispatch('{"jsonrpc": "2.0", "method": "bad_value", "params": [-5], "id": 1}', methods)
'{"jsonrpc": "2.0", "error": {"code": -32602, "message": "Invalid params", "data": "amount must be positive"}, "id": 1}'
>>> dispatch(
... '{"jsonrpc": "2.0", "method": "by_accident", "params": [50], "id": 1}', methods
... )
'{"jsonrpc": "2.0", "error": {"code": -32603, "message": "Internal error"}, "id": 1}'
>>> logging.disable(logging.NOTSET)
- Return
Errorfor errors the client should know about. It's the normal way. - Raise
JsonRpcErrorwhen the error is found deep inside other functions. The response is the same as returningError. - Return
InvalidParamswhen the arguments have the right shape but a bad value. - An uncaught exception is a bug as far as the client is concerned. It gets a bare Internal error, and the details go to the log.
Errors you make on purpose are sent as they are, whatever debug is, so
don't put secrets in them.
Debug mode¶
debug=True adds the exception message to the -32603 and -32000 errors, so
you can see what went wrong without reading the log:
>>> logging.disable(logging.CRITICAL)
>>> dispatch(
... '{"jsonrpc": "2.0", "method": "by_accident", "params": [50], "id": 1}',
... methods,
... debug=True,
... )
'{"jsonrpc": "2.0", "error": {"code": -32603, "message": "Internal error", "data": "division by zero"}, "id": 1}'
>>> logging.disable(logging.NOTSET)
Use it in development only. It's the same option on every dispatch function, sync and async. It's new in 5.0.10.
Spec errors¶
The spec says an error code must be an integer, and the message should be a
string. Error and JsonRpcError raise TypeError for anything else:
>>> Error("E1", "Insufficient funds")
Traceback (most recent call last):
...
TypeError: JSON-RPC error codes must be integers, not 'E1'
Inside a method, that TypeError is an exception like any other, so the
client gets an Internal error and the log has the traceback. The spec also
reserves method names that start with rpc., and @method raises
ValueError for them.
Changed in 6.0
5.0.10 warned with a UserWarning and sent the error as given.
Logging¶
Everything is logged on the jsonrpcserver logger or one of its children:
| Logger | What it logs |
|---|---|
jsonrpcserver.dispatcher |
For dispatch: an exception a method didn't catch (ERROR, with the traceback). A failure inside jsonrpcserver (ERROR, with the traceback). |
jsonrpcserver.async_dispatcher |
The same, for async_dispatch. |
jsonrpcserver.main |
A result the serializer couldn't handle, such as a datetime (ERROR, with the traceback). Both sync and async. |
jsonrpcserver.server |
serve() only: where it's listening, and one line per HTTP request (INFO). |
Invalid requests, unknown methods and params that don't fit aren't logged. The client gets the error, and it's not a problem with your server.
jsonrpcserver doesn't configure logging itself. If nothing configures it, Python still prints warnings and errors to stderr, but without the time or the logger's name. To see them properly, configure logging when your server starts:
logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s"
)
To change only jsonrpcserver's messages, set the level or add a handler on
the parent logger: logging.getLogger("jsonrpcserver").
New in 5.0.10
The jsonrpcserver.main messages. Before 5.0.10, a serializer failure
raised out of dispatch.