Home > Enterprise >  How do I read/debug FunctionClauseError?
How do I read/debug FunctionClauseError?

Time:04-16

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)
  • Related