elixir

Capabilities of Elixir's Logger

Julio Sampaio

Julio Sampaio on

Last updated:

Capabilities of Elixir's Logger

This post was updated on 9 August 2023 to reference the latest version of the Elixir logger.

Logs are an important part of your application and logging shouldn't be one of the last things you think of. You should configure your log system, formatter, and style as soon as you start the development of your app. Also, do your best to document the process and share how it works with the rest of your team.

In this article, we're going to demonstrate how logs work in Elixir. We'll jump into Elixir's Logger module, which brings a lot of power to logging features. It also smoothly integrates with Erlang’s :logger to convert code to Elixir syntax.

You’ll learn how to customize your formatter, set up and change the log defaults of a Phoenix web API, as well as understand Logger levels a bit better.

Logs are useful but having a complete overview of your app is even better. Check out our 5-in-1 application monitoring for Elixir. We're free for OSS.

Our Project Environment Setup

To follow along, you should have a working Elixir environment set up. You can find instructions for that on Elixir’s Installation Page.

After installation, the following command will be available:

shell
iex

When you run that command, an Elixir Interactive Shell will be opened. This is a REPL command-line environment in which you can test Elixir commands.

This is the perfect stage for some initial tests with Elixir’s IO module. As the name suggests, this module handles operations over IO, like data input and output.

To check that out, run the following commands within the iex shell:

shell
IO.puts("Hello world from Elixir") IO.puts(:stdio, "Logging from the stdio...") IO.puts(:stderr, "This is an error...") IO.inspect(self())

This is the simplest form of logging in Elixir. The puts function takes two arguments, the first being the target destination of the log message (as an atom) and the second, the message itself.

When called with only the message argument, IO.puts will print to :stdout by default.

The third line exemplifies how you could change the destination to something else, like the :stderr. Although this setting changes the internal structure of the log, it doesn’t change anything in the output because there’s no color highlight being made.

Finally, there’s an inspect function on the last line. This function inspects the more complex structures of your Elixir application and allows you to log, for example, information about the process id in which that code is running.

You can see the logs of this execution below:

Execution results

Phoenix Project

For the purposes of the article, you'll also need to have a Phoenix application created from scratch.

So make sure to follow the official instructions here.

Since our focus is on logging, we only need the homepage and a single GET endpoint.

What About the Elixir Logger?

Among other things, Elixir’s Logger module provides:

  • All the major levels of logging common to most languages
  • Pre-formatting of log messages before they reach their destination
  • Integration with Erlang’s Logger for even more power

Logger Levels

Logger currently embraces the following log levels:

  • :debug: must be used for debug-only purposes. Be careful not to set this mode for production environments.
  • :info: the most used one. It should be triggered whenever you need to print information of any nature.
  • :warn and :error: even though :info can be used for everything, it’s better to finetune your warning and error messages. This way, automation tools will understand your categorized logs better.

They are stacked in the exact order of precedence which Elixir considers when having to choose between one or another.

Usage Example

Its usage is pretty simple. See the following example with a string interpolation:

elixir
Logger.info("Hey, it's me: #{user_name}")

You can also use it within the iex shell. Just run the following:

elixir
require Logger user_name = "Julio" Logger.info("Hey, it's me: #{user_name}")

Here's the output:

shell
11:29:55.379 [info] Hey, it's me: Julio

Formatter

Note that, by default, Logger comes with a preset format. In the above log, you can see a timestamp of when the event happened, the level of the log, and its message.

When you work with real-world applications, like a web API, you need to define which levels are accepted for each environment.

Get back to the Phoenix application you created previously and open the config folder. You may see a bunch of Elixir files with names that match development, testing, and production environments. Open the config.exs file, which is the boss of your app configuration and locate the config :logger line.

Take a brief look at these lines. This is Elixir’s Logger configuration. Whatever you change here, is going to be applied to all your app’s logs.

Start by changing the default log level of the entire app. If you specify that it should be error (the highest in the stack), then the others won't be logged anymore. So change the first line of the config to this:

elixir
config :logger, :console, level: :error

To test, you’ll need to add some logs to your controller function. So, open the file simple_controller.ex that’s located in the web/controllers folder and switch its code to this:

elixir
require Logger defmodule SimpleElixirApi.PageController do use SimpleElixirApi.Web, :controller def index(conn, _params) do IO.puts("I'm a log") Logger.debug("I'm a debug log") Logger.info("I'm an info log") Logger.warn("I'm a warn log") Logger.error("I'm an error log") Logger.metadata(request_id: "123") render(conn, "index.html") end end

Restart your server and hit the http://localhost:4000/ address again. You should see the following in your console:

Error logs are shown in the console

Change the level to the others and check out the results. Remember that you may configure each of the environment files separately to avoid log setting confusion.

Message Formatter

Alternatively, you can format your log messages by providing a regex. Change your Logger config (at configs.ex) to the following:

elixir
config :logger, :console, level: :info, format: "$time $message $metadata[$level] \n"

Note that the level was changed to allow more log lines for comparison.

Here, the format is receiving only the log’s variables to be injected into the output. However, you can customize with any of Elixir’s regex that may please you.

This should be the output:

Custom formatted messages

In the end, it hasn’t changed that much. However, this was on purpose. For more advanced logs, you’ll need to write your own formatter.

Creating Your Formatter

Elixir’s config allows you to create and set your own formatter. With it, you have the option of creating whatever routines you want before, during, and after each log reaches its destination.

To make this happen, you’ll need to create a new logger module. So, within the lib folder, create another one called logger and create a file named my_logger.ex inside it with the following code:

elixir
defmodule Logger.MyLogger do def format(level, message, timestamp, metadata) do "time=#{timestamp} level=[#{level}] metadata=#{inspect(metadata)} message=#{message}\n" rescue _ -> "Ooops, there was an error here!\n" end end

You can name the function whatever you want but it’s common practice to give it the format name. It basically receives all common log attributes and formats them into a predefined pattern.

The message obeys the usual pattern for maps (key=value), very common among log processing tools.

In case anything goes wrong, an error message will be thrown at the rescue block.

For it to run, the Elixir config file has to be aware of it, so change the logger config to the following:

elixir
config :logger, :console, level: :info, format: {Logger.MyLogger, :format}

Restart the server and check out how your logs look now.

Logging Metadata

Another useful feature that comes built-in with Elixir’s Logger is metadata logging.

If you're using AppSignal to monitor your app, instead of adding metadata to your logs, you can add sample data to your AppSignal samples by using tagging. This way you can supply extra context on errors and performance issues.

There are times when you need to group a bunch of information in categorized logs. Since you’re under Phoenix's wing, your web APIs may need to automatically show some data related to HTTP requests and responses, like the request’s id.

Logger metadata works as a list of keywords (similar to maps and dictionaries) that’s exclusive to the running process (which means that, in the HTTP fandom, one request won’t get affected by others).

Every time a request arrives in the house, Logger will catch it, check your log configs and ask what metadata keywords are allowed to be logged.

You can define them one by one or tell Logger to consider them all:

elixir
config :logger, :console, level: :info, format: {Logger.MyLogger, :format}, metadata: [:request_id]

To set them all, just change the metadata config to:

elixir
metadata: :all

Do you remember the code line you added to the controller a few moments ago:

elixir
Logger.metadata(request_id: "123")

Now you can check out the request’s id appearing in your logs:

shell
12:08:13.040 metadata=[request_id=123] [info] My request log

Logging to Files

Perhaps one of the easiest features within Logger is changing the log’s destination to a file rather than the default console.

First, you need to set up a new backend for the logger_file_backend package:

elixir
config :logger, backends: [{LoggerFileBackend, :debug_log}]

Then, you specify the destination file that Logger will send the logs to:

elixir
config :logger, :debug_log, path: 'myLog.log', level: :debug

When you tail -f your myLog.log file, you should see the logs.

Conclusion

It’s unbelievable how such a simple subject as logging can lead to a lot of unseen extra information. Right now, Elixir's Logger is tightly integrated with Erlang’s, so that developers can now implement custom loggers. Good luck with exploring Elixir loggging!

P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, subscribe to our Elixir Alchemy newsletter and never miss a single post!

Julio Sampaio

Julio Sampaio

Our guest author Julio is a results-driven professional with a progressive, seven-year career in information technology. Julio has been responsible for all aspects of software development such as backend, frontend, and user relationship.

All articles by Julio Sampaio

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps