← Learning center

Understanding Elixir error messages and stack traces

Understanding Elixir error messages and stack traces

It's important to understand what your application's error type is telling you when debugging an issue.

In this article, we'll cover common Elixir error types and what your application tells you when encountering one.

How to read an error message

Error messages are most often output in the following format:

elixir
** (ErrorCode) description file_location:line_number: (error_location)

For example, considering the following example:

elixir
** (ArgumentError) argument error publisher.exs:5: (file)

We can deduce the following:

  1. Error location: line 27 of the articles_controller.rb file in the publish function call in the publish_article function
  2. Error type: ArgumentError
  3. Error Message: wrong number of arguments (given 3, expected 2)

Which can be understood as: "On line 27, in the publish_article function in the articles_controller.rb file, three arguments were passed to the publish function instead of two"

How to read a stack trace

Whenever an error occurs in your application, Elixir will raise an exception and log a stack trace, which you can then use to find the location of an error within your application's code.

In Elixir, if you try and divide a number by zero, you will raise a ArithmeticError. To demonstrate a stack trace, let's raise one:

elixir
# maths_is_hard.exs defmodule MathsIsHard do def divide(x, y) do IO.puts("#{x} divided by #{y} is #{x / y}") end end MathsIsHard.divide(8, 0)

In the above code snippet, we've defined a function called divide(a, b), which will divide any two integers we provided. Let's execute the code in our terminal to raise ArithmeticError and print a stack trace:

elixir
$ elixir maths_is_hard.exs ** (ArithmeticError) bad argument in arithmetic expression maths_is_hard.exs:3: MathsIsHard.divide/2 (elixir 1.13.4) lib/code.ex:1183: Code.require_file/2

So, what is our stack trace telling us? Let's look at it line per line:

  1. The first line tells us: an ArithmeticError has been raised due to a bad argument.
  2. The second line tells us: the divide function was called on line 2 of the maths_is_hard.exs file.
  3. The third line tells us: Tell us that the error occurred in the elixir standard library (elixir 1.13.4) during runtime. You may see a slightly different error depending on what package your application is using, for example, if an error occurs while loading a Phoenix function.

We can use this stack trace to deduce where the error was raised in our code and where the function was called. In the case of the example above, we know the issue was caused by 0 being passed as an argument on maths_is_hard.exs:3. We could then make sure not to pass 0 as an argument or modify our function to safely return a message if 0 is passed as an integer:

elixir
defmodule MathsIsHard do def divide(x, y) do if y > 0 do IO.puts("#{x} divided by #{y} is #{x / y}") else IO.puts("You can't divide by 0") end end end MathsIsHard.divide(8, 0)

Which would output the following when run:

shell
$ elixir maths_is_hard.exs You can't divide by 0

Good to know: We're using shorter stack traces here for brevity, but your application's stack trace will often be much longer. However, the same logic to reading stack traces applies, regardless of length.

Common error types

ArgumentError

ArgumentError is raised when you call a function with the incorrect type of argument.

For example, say you want to convert a string into an integer; Elixir would expect a "textual representation of an integer" like 1, 300 or541 , and not a word representation of an integer like "one", "three-hundred", "five-hundred-and-one" etc:

elixir
iex(1)> String.to_integer("one") ** (ArgumentError) errors were found at the given arguments: * 1st argument: not a textual representation of an integer :erlang.binary_to_integer("one")

Resolution:

To resolve this error, check what arguments the function is expecting and ensure you pass those arguments when calling the function in your code:

elixir
iex(2)> String.to_integer("12") 12

Note: If you are curious about what arguments a function expects in Elixir, you can use the helper function in your iex console, for example:

elixir
iex(3)> h(String.to_integer())

will return the following output, with argument information and error expectations:

elixir
def to_integer(string) @spec to_integer(t()) :: integer() Returns an integer whose text representation is string. string must be the string representation of an integer. Otherwise, an ArgumentError will be raised. If you want to parse a string that may contain an ill-formatted integer, use Integer.parse/1. Inlined by the compiler. ## Examples iex> String.to_integer("123") 123 Passing a string that does not represent an integer leads to an error: String.to_integer("invalid data") ** (ArgumentError) argument error

FunctionClauseError

In Elixir, a single function can have multiple clauses defined via pattern matching and guards.

A FunctionClauseError is raised when a function is called with clauses that do not match the function's expectations.

We speak a lot of languages in AppSignal, so we've created some Elixir code to greet everyone in their native tongue:

elixir
defmodule Greeter do def hello(:dutch, name) do IO.puts("Hallo #{name}!") end def hello(:english, name) do IO.puts("Hello #{name}!") end def hello(:spanish, name) do IO.puts("¡Hola #{name}!") end end

When we call the Greeter.hello function, Elixir will scan the file for a function with a clause that matches the arguments we've provided. If there is no matching clause, then we'll get a FunctionClauseError, for example, let's try and greet someone in Serbian:

elixir
iex(2)> Greeter.hello(:serbian, "Milica") ** (FunctionClauseError) no function clause matching in Greeter.hello/2 The following arguments were given to Greeter.hello/2: # 1 :serbian # 2 "Milica" iex:2: Greeter.hello/2

Resolution

To resolve this error, we'll need to add a function clause that matches, so in the above example, a function clause that accepts four arguments:

elixir
def hello(:serbian, name) do IO.puts("zdravo #{name}!") end

KeyError

A KeyError occurs when trying to access a key that does not exist, for example, when trying to access the non-defined author key in the below article_attributes map:

elixir
article_attributes = %{title: 'Elixir is awesome', header: 'elixir.png'}

The following error would be returned:

elixir
iex(1)> Map.fetch!(article_attributes, :author) ** (KeyError) key :author not found in: %{header: 'elixir.png', title: 'Elixir is awesome'} (stdlib 3.16.1) :maps.get(:author, %{header: 'elixir.png', title: 'Elixir is awesome'}

Resolution

To resolve this error, you need to ensure you're fetching keys that exist in a map or use a function that does not raise an error when a key is not found. You can always list the available keys using the Map.keys() function:

elixir
iex(1)> Map.keys(article_attributes) [:header, :title] iex(2)> Map.fetch!(article_attributes, :title) 'Elixir is awesome'

To see if a specific key exists, you can use the has_key?(map, key) function:

elixir
article_attributes = %{title: 'Elixir is awesome', header: 'elixir.png'} iex(1)> Map.has_key?(article_attributes, :author) false The `get(map, key)` function allows us to get keys from a map without raising an error if the key doesn't exist: ```elixir iex(1)> Map.get(article_attributes, :author) nil iex(2)> Map.get(article_attributes, :author, "Jane Doe") "Jane Doe"

UndefinedFunctionError

UndefinedFunctionError is raised when you call a function that is not defined. In the example below, we call the publish_article function on the Article module; however, no publish_article function is defined:

elixir
# articles_controller.exs def publish_article(article) do article.publish_article(article) end

Which, when executed, will result in the following error:

elixir
** (UndefinedFunctionError) function Article.publish_article/0 is undefined or private Article.publish_article()

Resolution

To resolve the error, a function exported by the module must be invoked.

We can check the file where our Article module is defined, and we can use the module_info function to see what functions the Article module exports:

elixir
iex(1)> Article.module_info [ module: Article, exports: [ __info__: 1, publish: 1, ...

Let's use the publish() function:

elixir
iex(2)> Article.publish(article) {:ok, %PhoenixApp.Article{ __meta__: #Ecto.Schema.Metadata<:loaded, "articles">, id: 1, inserted_at: ~N[2023-06-28 10:01:37], published: true, title: "The Elixir of Life", updated_at: ~N[2023-06-28 10:01:37] }}

Never miss an exception with AppSignal

With AppSignal Error Reporting, AppSignal can record and notify you when errors occur, helping you quickly resolve problems in your application's codebase.

Graph showing Elixir error in AppSignal

AppSignal's Error Reporting is just one of our many developer-driven features that help you get the most out of monitoring your application. Developers also enjoy using our monitoring because we offer:

  • An intuitive interface that is easy to navigate.
  • Simple and predictable pricing.
  • Developer-to-developer support.

If you're ready to monitor your application's errors, start a free trial with AppSignal today!

Start your free trial

Don’t let the bad bugs bite. Try AppSignal for free.

AppSignal offers a 30-day free trial, no credit card is required. All features are available in all plans. Start monitoring your application in just a few clicks!