Although there are similar questions at SO (example), for some reason the error is formatted in another way in my case (and also I understand the general meaning of FunctionClauseError
), so let me ask "once more".
Here's the question I'm getting:
{:error, reason} -> Logger.error(fn -> ["failed to fetch internal transactions for >blocks: ", inspect(reason)] end, error_count: unique_numbers_count )
gives (I've added line breaks)
2022-04-13T18:44:19.749 application=indexer fetcher=internal_transaction count=10 error_count=10 [error] failed to fetch internal transactions for blocks: %FunctionClauseError{args: nil, arity: 1, clauses: nil, function: :finalize, kind: nil, module: EthereumJSONRPC.Geth.Tracer}
so obviously reason
is
%FunctionClauseError{args: nil, arity: 1, clauses: nil, function: :finalize, kind: nil, module: EthereumJSONRPC.Geth.Tracer}
I wonder what do these bits mean and can they help with debugging?
In module EthereumJSONRPC.Geth.Tracer
function finalize
is defined as
defp finalize(%{stack: [top], calls: [calls]}) do
and an example of the argument passed to it (causing the error) is:
%{
calls: [[], [], [], []],
depth: 4,
stack: [
%{
"callType" => "staticcall",
"from" => nil,
"gas" => 432013,
"gasUsed" => 21661,
"input" => "0xefc4cfa.....",
"output" => "0x",
"outputLength" => 64,
"outputOffset" => 4346,
"to" => "0x0000000000000000000000000000000000000001",
"traceAddress" => [0, 0, 0],
"type" => "call",
"value" => "0x0"
},
%{
"callType" => "staticcall",
"from" => nil,
"gas" => 438139,
"gasUsed" => 1726,
"input" => "0xefc4c.......",
"output" => "0x",
"outputLength" => 64,
"outputOffset" => 4026,
"to" => "0x0000000000000000000000000000000000000001",
"traceAddress" => [0, 0],
"type" => "call",
"value" => "0x0"
},
%{
"callType" => "staticcall",
"from" => nil
"gas" => 445060,
"gasUsed" => 2521,
"input" => "0xefc4......",
"output" => "0x",
"outputLength" => 64,
"outputOffset" => 3706,
"to" => "0x0000000000000000000000000000000000000001",
"traceAddress" => [0],
"type" => "call",
"value" => "0x0"
},
%{
"callType" => "call",
"from" => "0x9a66644084108a1bc23a9ccd50d6d63e53098db6",
"gas" => 460960,
"gasUsed" => 11500,
"input" => "0xba2c.........",
"output" => "0x",
"to" => "0x841ce48f9446c8e281d3f1444cb859b4a6d0738c",
"traceAddress" => [],
"type" => "call",
"value" => "0x0"
}
],
trace_address: [0, 0, 0, 0]
}
which looks ok for me (it has both stack
and calls
props). Can I extract something more from the args: nil, arity: 1, clauses: nil, function: :finalize, kind: nil
bit for debugging? If you point the cause, it will be helpful too, but I also want to understand how to debug this. Can extra props (depth
, trace_address
) be the source of the problem? Or should I look for the error cause inside the finalize
function body? (as far as I understand, no)
PS A note for others seeking help with debugging: the bit before the error text
application=indexer fetcher=internal_transaction count=10 error_count=10
may contain useful info: if you look for Logger.metadata
, you may find something like
Logger.metadata(fetcher: :internal_transaction)
which can give a hint about the source of the error.
CodePudding user response:
By default, if you don't use try / rescue
, a FunctionClauseError
will be logged with all the information (values, patterns, stacktrace) needed to debug it:
> String.downcase(1)
** (FunctionClauseError) no function clause matching in String.downcase/2
The following arguments were given to String.downcase/2:
# 1
1
# 2
:default
...
But if you rescure the error and return it as a tuple, you will lose most of this information:
try do
String.downcase(1)
rescue
err -> {:error, err}
end
The result will only contain a very barebones struct:
{:error,
%FunctionClauseError{
args: nil,
arity: 2,
clauses: nil,
function: :downcase,
kind: nil,
module: String
}}
You can log directly in the rescue clause, where __STACKTRACE__
is available, using the approach explained in this official guide:
try do
String.downcase(1)
rescue
err ->
# format the exception in a readable way
Exception.format(:error, err, __STACKTRACE__)
|> Logger.error()
# return value
:error
end
That being said, this is not so common in Elixir to rely on try/rescue
as explained in the guide above. Most of the time you just let unexpected things fail (the famous "Let it crash" philosophy), and use :error
tuples for cases you actually expect and want to handle.
CodePudding user response:
Sidenote: the answer provided by @sabiwara has a ton of helpful info and should be marked as correct.
Here is the cause of the error you got:
defp finalize(%{stack: [top], calls: [calls]})
This defines a function, accepting a map with keys, including but not limited to :stack
and :calls
, and their values being lists containing exactly one element.
You are passing longer lists there, hence the error. This is what you likely wanted to do:
defp finalize(%{stack: top, calls: calls})
when is_list(top) and is_list(calls)