Skip to content

Integrate Slack into the Elixir's logging system

Posted on:2024-02-23 | 4 min read

With the release of Elixir 1.15, it included a new integration with Erlang/OTP: “Logger Handlers”. The Logger Handlers provide the ability to plug yourself into Erlang’s logging system, the same as the previous elixir logger backends, but in a standard way defined by Erlang.

Before. Sending a Slack message manually (prone to errors).

message = "New customer signup"

After. Slack is integrated into our logging system!"New customer signup", slack: true)

Let’s build a simple log handler that sends a notification to both STDOUT and Slack.

Table of contents

Open Table of contents

Slack Log handler module

To define a log handler, we must implement the following callback:

log(LogEvent, Config) -> void()

You can see the Erlang callback @spec here. The handler specification has additional callback functions to define if you need said functionality.

Now, onto the handler:

defmodule MyApp.Slack.LogHandler do
  @moduledoc """
  Read from logs and send a message to Slack when the log metadata contains `[slack: true]`.

  ## :logger handlers callbacks

  @spec log(:logger.log_event(), :logger.handler_config()) :: :ok
  def log(%{level: log_level, meta: %{slack: true}, msg: {:string, log_msg}}, _handler_config) do
    %{level: configured_log_level} = :logger.get_primary_config()

    if Logger.compare_levels(log_level, configured_log_level) == :lt do
      Task.start({MyApp.Slack.HTTPClient, :send_message, [log_msg]})

  def log(_log_event, _handler_config) do

As you can read, we first pattern match on the LogEvent: meta: %{slack: true} to check if this log is configured to be sent to Slack. Then, we compare the configured_log_level to know if the log level is greater than the configured application log level, see log levels here. After those checks, we fire a Task to send a message using the MyApp.Slack.Client (defined here).


Configuring the log handler

In config/config.exs:

# Configures Logger Handlers
config :my_app, :logger, [
  {:handler, :slack_handler, MyApp.Slack.LogHandler, %{}}

Attaching the log handler to the logging system

In your Application.start/2 callback, found in lib/my_app/application.ex:

@impl true
def start(_type, _args) do

  children = [

Logging messages to STDOUT and to Slack!"useful log", slack: true)

Done, the logging system will log to both STDOUT and send the same log to the Slack channel you configured.

Slack HTTP client module

You need to set up a Slack application with the required scopes to call this endpoint.

  1. Set up Slack application with the required scopes for the bot (chat:write).
  2. Install the application you created in your workspace.
  3. Set the Bot User OAuth Token (found in the left menu OAuth & Permissions) as the SLACK_APP_TOKEN env var.
  4. Set the @notifications_channel in the Slack.HTTPCLient module (right-click the desired channel, and scroll to the bottom).
  5. Call"Test", slack: true) to test the integration.
defmodule MyApp.Slack.HTTPClient do
  @moduledoc """
  Slack HTTP Client.

  @notifications_channel_id "XXXXXXXXX"

  @spec send_message(String.t()) :: Req.Response.t() | :ignore
  def send_message(message) do
    case slack_app_token() do
      {:ok, token} ->!(req(),
          url: "chat.postMessage",
          auth: {:bearer, token},
          json: %{
            channel: @notifications_channel_id,
            text: message

      :error ->

  @spec req() :: Req.Request.t()
  defp req do
      base_url: "",
      headers: %{
        content_type: "application/json"

  @spec slack_app_token() :: {:ok, String.t()} | :error
  defp slack_app_token do

That’s it! You are now free to implement any logger handler and use the power of Erlang’s logging system. If you want to learn more about log handlers, I recommend reading Sentry’s Elixir library. Here is the link to their logger handler.

Happy logging!