When I run this code:
$client->evaluate('
box.session.settings.error_marshaling_enabled = false
box.error{code = 42, reason = "Foobar", type = "MyError"}
');
regardless of the value of error_marshaling_enabled I always get a response with a new (extended) error format:
[
49 => 'Foobar',
82 => [
0 => [
0 => [
0 => 'CustomError',
2 => 3,
1 => 'eval',
3 => 'Foobar',
4 => 0,
5 => 42,
6 => [
'custom_type' => 'MyError',
],
],
],
],
],
Why is that?
Short answer.
error_marshaling_enabled option affects only how error objects are encoded in response body (48, IPROTO_DATA). It does not affect how they are returned as exceptions, in the response header (82, IPROTO_ERROR).
Long answer.
In Tarantool an error object can be returned in 2 ways: as an exception and as an object. For example, this is how to throw an error as exception:
function throw_error()
box.error({code = 1000, reason = "Error message"})
-- Or
error('Some error string')
end
This is how to return it as an object:
function return_error()
return box.error.new({code = 1000, reason = "Error message"})
end
If the function was called remotely, using IPROTO protocol via a connector like netbox, or PHP connector, or any other one, the error return way affects how it is encoded into MessagePack response packet. When the function throws, and the error reaches the top stack frame without being caught, it is encoded as IPROTO_ERROR (82) and IPROTO_ERROR_24 (49).
When the error object is returned as a regular value, not as an exception, it is encoded also as a regular value, inside IPROTO_DATA (48). Just like a string, a number, a tuple, etc.
With encoding as IPROTO_ERROR/IPROTO_ERROR_24 there is no much of a configuration space. Format of these values can't be changed. IPROTO_ERROR is always returned as a MessagePack map, with a stack of errors in it. IPROTO_ERROR_24 is always an error message. The IPROTO_ERROR_24 field is kept for compatibility with connectors to Tarantool versions < 2.4.1.
With encoding as a part of IPROTO_DATA you can choose serialization way using error_marshaling_enabled option. When it is true, errors are encoded as MessagePack extension type MP_EXT, and contain the whole error stack, encoded exactly like IPROTO_ERROR value. When the option is false (default behaviour in 2.4.1), the error is encoded as a string, MP_STR, which is the error's message. If there is a stack of errors, only the newest error is encoded.
error_marshaling_enabled option exists for backward compatibility, in case your application on Tarantool wants to be compatible with old connectors, which don't support MP_EXT encoded errors.
In Tarantool < 2.4.1 errors were encoded into result MessagePack as a string with error message, and error stacks didn't exist at all. So when the new format and the error stacks feature were introduced, making the new format default would be a too radical change breaking the old connectors.
Consider these examples of how error marshaling affects results. I use Tarantool 2.4.1 console here, and built-in netbox connector. The code below can be copy pasted into the console.
First instance:
box.cfg{listen = 3313}
box.schema.user.grant('guest', 'super')
function throw_error()
box.error({code = 1000, reason = "Error message"})
end
function return_error()
return box.error.new({code = 1000, reason = "Error message"})
end
Second instance:
netbox = require('net.box')
c = netbox.connect(3313)
Now I try to call the function on the second instance:
tarantool> c:call('throw_error')
---
- error: Error message
...
The c:call('throw_error') threw an exception. If I catch it using pcall() Lua function, I will see the error object.
tarantool> ok, err = pcall(c.call, c, 'throw_error')
tarantool> err:unpack()
---
- code: 1000
base_type: ClientError
type: ClientError
message: Error message
trace:
- file: '[string "function throw_error()..."]'
line: 2
...
As you can see, I didn't set error_marshaling_enabled, but got the full error. Now I will call the other function, without exceptions. But the error object won't be full.
tarantool> err = c:call('return_error')
tarantool> err
---
- Error message
...
tarantool> err:unpack()
---
- error: '[string "return err:unpack()"]:1: attempt to call method ''unpack'' (a nil
value)'
...
The error was returned as a mere string, error message. Not as an error object. Now I will turn on the marshaling:
tarantool> c:eval('box.session.settings.error_marshaling_enabled = true')
---
...
tarantool> err = c:call('return_error')
---
...
tarantool> err:unpack()
---
- code: 1000
base_type: ClientError
type: ClientError
message: Error message
trace:
- file: '[C]'
line: 4294967295
...
Now the same function returned the error in the new format, more featured.
On the summary: error_marshaling_enabled affects only returned errors. Not thrown errors.
Related
I'm debugging some tests for the .NET SurrealDB library. I can open connections to the database just fine but when I send a create RPC message to the db (docker container) it returns an error that reads "There was a problem with the database: The table does not exist"
TRACE tungstenite::protocol Received message {"id":"02B70C1AFE5D","async":true,"method":"create","params":["users",{"username":"john","password":"test123"}]}
...
16 13:46:45] DEBUG surrealdb::dbs Executing: CREATE $what CONTENT $data RETURN AFTER
surreal_1 | [2022-09-16 13:46:45] TRACE surrealdb::dbs Iterating: CREATE $what CONTENT $data RETURN AFTER
code: -32000, message: "There was a problem with the database: The table does not exist"
Any idea why that would happen? The table, of course, doesn't exist since I'm trying to create it. Would there be another reason in the Surreal code that such an error would be returned?
The error message was a red herring. The actual issue was that the client had an error that didn't allow it to sign in correctly so it wasn't authorized to make changes to the database.
Offending code:
// The table doesn't exist
Err(Error::TbNotFound) => match opt.auth.check(Level::Db) {
// We can create the table automatically
true => {
run.add_and_cache_ns(opt.ns(), opt.strict).await?;
run.add_and_cache_db(opt.ns(), opt.db(), opt.strict).await?;
run.add_and_cache_tb(opt.ns(), opt.db(), &rid.tb, opt.strict).await
}
// We can't create the table so error
false => Err(Error::TbNotFound), // Wrong Error Message
},
This has since been fixed and should now return a query permission error if the client is unauthorized.
I'm having these json parsing errors from time to time:
2022-01-07T12:15:19,872][WARN ][logstash.filters.json ] Error parsing json
{:source=>"message", :raw=>" { the invalid json }", :exception=>#<LogStash::Json::ParserError: Unrecognized character escape 'x' (code 120)
Is there a way to get the :exception field in the logstash config file?
I opened the exact same thread on the elastic forum and got a working solution there. Thanks to #Badger on the forum, I ended up using the following raw ruby filter:
ruby {
code => '
#source = "message"
source = event.get(#source)
return unless source
begin
parsed = LogStash::Json.load(source)
rescue => e
event.set("jsonException", e.to_s)
return
end
#target = "jsonData"
if #target
event.set(#target, parsed)
end
'
}
which extracts the info I needed:
"jsonException" => "Unexpected character (',' (code 44)): was expecting a colon to separate field name and value\n at [Source: (byte[])\"{ \"baz\", \"oh!\" }\r\"; line: 1, column: 9]",
Or as the author of the solution suggested, get rid of the #target part and use the normal json filter for the rest of the data.
When a GraphQL Error occurs, I cannot easily know where it occured. I have to spend unnecessary time trying to track it down. How do I get a Traceback printed in the console of my text editor?
I answered my own question by accessing the GraphQL error(s) with result.errors, iterating through the list, and using python's print_tb function to print the Traceback.
Does anyone have a different or better way of doing it?
Example usage of the print_graphql_errors function:
from django.conf.settings import DEBUG
result = schema.execute(
mutation_str, context_value=request, variable_values=variable_values
)
if result.errors is None:
return self.handle_success(result)
if DEBUG:
print_graphql_errors(result.errors)
return self.handle_failure(result)
The print_graphql_errors function:
from traceback import print_tb
from django.conf.settings import DEBUG
def print_graphql_errors(errors, raise_error=False):
if not DEBUG:
raise Exception(
'DevError: This function should not be called in production'
)
assert errors, 'DevError: The "errors" parameter cannot be None'
print_after = []
current_error = None
print('######################################################################')
print('Manually Generated Traceback (with the print_graphql_errors function):')
print(f'There are {len(errors)} errors:')
for i, error in enumerate(errors):
print(f'{i + 1}) ', error)
print('######################################################################')
for error in errors:
current_error = error
# FYI: This object has these attributes: (example attribute values)
# tb_frame <frame at 0x000002DDB844D548, file 'site-packages\\graphql\\execution\\executor.py', line 455, code resolve_or_error>
# tb_lasti 16
# tb_lineno 447
# tb_next <traceback object at 0x000002DDBAFBA388>
# print('error.locations:', error.locations)
# print('error.positions:', error.positions)
try:
print_tb(error.stack)
except AttributeError as e:
print(e.__traceback__)
print(f'Warning: An error occured while trying to print the traceback: {e}. It has the following attributes instead: {dir(error)}')
print_after.append(error)
if len(print_after):
print('###################################################################')
print(f'Number of errors without the "stack" attribute: {len(print_after)}')
print('###################################################################')
if raise_error:
for error in print_after:
raise error
raise current_error
I have an AWS Lambda which is designed for direct invocation from multiple applications within my team's service landscape. I'm writing a wrapper class which will perform this invocation, and associated validation and error detection / handling in the Lambda's response.
By design, the Lambda terminates with an unhandled exception if runtime validation of the invocation parameters fails. But I didn't expect to find that in spite of an unhandled function error, the status_code of the invocation response is 200:
[1] pry(main)> lambda_client = Aws::Lambda::Client.new(region: 'us-east-1')
=> #<Aws::Lambda::Client>
[2] pry(main)> invocation_response = lambda_client.invoke(function_name: 'jwt-tokens-dev-AccessTokenCreator')
=> #<struct Aws::Lambda::Types::InvocationResponse
status_code=200,
function_error="Unhandled",
log_result=nil,
payload=#<StringIO:0x007f91c1749028>,
executed_version="$LATEST">
[3] pry(main)> invocation_response.payload.string
=> "{\"errorMessage\": \"Required parameters are missing: role, sub, sub_type\", \"errorType\": \"ParamMissingError\", \"stackTrace\": [[\"/var/task/handler.py\", 15, \"access_token_creator\", \"return access_token_creator_handler(event, context)\"], [\"/var/task/access_token_creator.py\", 32, \"handler\", \"params = _validate_event_params(event)\"], [\"/var/task/access_token_creator.py\", 97, \"_validate_event_params\", \"raise ParamMissingError('Required parameters are missing: %s' % ', '.join(missing_params))\"]]}"
[4] pry(main)> ActiveSupport::JSON.decode(invocation_response.payload.string)
=> {"errorMessage"=>"Required parameters are missing: role, sub, sub_type",
"errorType"=>"ParamMissingError",
"stackTrace"=>
[["/var/task/handler.py", 15, "access_token_creator", "return access_token_creator_handler(event, context)"],
["/var/task/access_token_creator.py", 32, "handler", "params = _validate_event_params(event)"],
["/var/task/access_token_creator.py",
97,
"_validate_event_params",
"raise ParamMissingError('Required parameters are missing: %s' % ', '.join(missing_params))"]]}
Is it expected that the status code of the response of a direct Lambda invocation would be 200, despite an unhandled function error? I want to implement proper error response detection in my wrapper class, and searching for the key "errorType" in the top-level of the JSON response doesn't really seem so robust.
The solution -- too obvious, but not enough to distract from the "200" status_code -- is to check if function_error is nil.
The value is nil in the event of successful invocation:
[8] pry(main)> invocation_response = lambda_client.invoke(function_name: 'jwt-tokens-dev-AccessTokenCreator', payload: ActiveSupport::JSON::encode(sub:3285397, sub_type:'user', role:'admin'))
=> #<struct Aws::Lambda::Types::InvocationResponse
status_code=200,
function_error=nil,
log_result=nil,
payload=#<StringIO:0x007f91c2a2eeb8>,
executed_version="$LATEST">
Otherwise it has the value 'Unhandled' or 'Handled':
function_error ⇒ String
Indicates whether an error occurred while executing the Lambda function. If an error occurred this field will
have one of two values; Handled or Unhandled. Handled errors are
errors that are reported by the function while the Unhandled errors
are those detected and reported by AWS Lambda. Unhandled errors
include out of memory errors and function timeouts. For information
about how to report an Handled error, see Programming Model.
https://docs.aws.amazon.com/sdkforruby/api/Aws/Lambda/Types/InvocationResponse.html#function_error-instance_method
I'm debugging a problem where occasional writes to a mongo collection seem to be failing. As I was going over my error checking code, I found that the update method in the Collection class, seems to be returning a Fixnum instead of a hash.
Here is a code fragment (with debug statements)
begin
puts "collection type: #{#db_collection.class}"
status = #db_collection.update(selector, document)
puts "[Warn] Database update returned NULL status" unless status
puts "[Error] Mongo update returned an error: #{status.class}" unless (status == true)
puts "[Error] Mongo update returned an error: #{status}" unless (status == true)
rescue => e
puts "[Warn] Unable to update mongoDB (#{e})"
end
When I run this code, I get the following output:
collection type: Mongo::Collection
[Error] Mongo update returned an error: Fixnum
[Error] Mongo update returned an error: 236
I was expecting the update function to return a true for successful operations and a Hash for fails as per the documentation:
Returns:
(Hash, true) — Returns a Hash containing the last error object if acknowledging writes. > Otherwise, returns true.
I'm using version 1.8.0 of the ruby driver.
I'm not sure how to properly check that the writes have occurred correctly. I know that the driver should throw an exception if the write fails but I'm not seeing that happen. I don't know how to properly check the status variable since it's not returning a type I expect.
Thanks in advance for any help here.