Logging from within Remote Session

Hey everyone,
can someone help me to get on the right track - how to find the issue.

I’ve following problem:
I’m logging via a custom backend to my Matrix server.
This is working, but not if I try to log (manually) within a remote session.

  • release/bin/app rpc 'require Logger; Logger.info('message');'
  • or within release/bin/app remote

It just logs to the console, but not to my custom Backend.
Automatic logs from within a schedule are working as expected.
The Application config also seems to by right.

Please anybody, how could I debug this behavior…

Could you show us how you configured Logger to send information to your custom backend?

prod.exs

config :logger,
  backends: [
    :console,
    {LoggerExtension.Backend.Matrix, :matrix}
  ]

config :logger, :matrix, level: :info

releases.exs

config :logger, :matrix, server_url: "https://matrix.example.com"
config :logger, :matrix, room: System.fetch_env!("MATRIX_ROOM")
config :logger, :matrix, access_token: System.fetch_env!("MATRIX_ACCESS_TOKEN")

matrix.ex

defmodule LoggerExtension.Backend.Matrix do
  @moduledoc """
  Deliver log messages to Matrix server.
  """

  @behaviour :gen_event

  @derive {Inspect, except: [:access_token]}
  defstruct level: nil, server_url: nil, room: nil, access_token: nil

  # :gen_event API

  def init({__MODULE__, _options}) do
    config = Application.get_env(:logger, :matrix)
    device = Keyword.get(config, :device, :user)

    if Process.whereis(device) do
      {:ok, configure(config, %__MODULE__{})}
    else
      {:error, :ignore}
    end
  end

  def handle_call({:configure, options}, state) do
    {:ok, :ok, configure(options, state)}
  end

  def handle_event({_level, group_leader, _event}, state) when node(group_leader) != node() do
    {:ok, state}
  end

  def handle_event({level, _group_leader, {Logger, message, _timestamp, _metadata}}, state) do
    %{level: log_level} = state

    cond do
      not meet_level?(level, log_level) ->
        {:ok, state}

      true ->
        {:ok, send_message(message, state)}
    end
  end

  def handle_event(_, state) do
    {:ok, state}
  end

  def handle_info(_, state) do
    {:ok, state}
  end

  def code_change(_old_version, state, _extra) do
    {:ok, state}
  end

  def terminate(_reason, _state) do
    :ok
  end

  # Internal API

  defp meet_level?(_level, nil), do: true

  defp meet_level?(level, min) do
    Logger.compare_levels(level, min) != :lt
  end

  defp configure(config, state) do
    level = Keyword.get(config, :level)
    server_url = Keyword.get(config, :server_url)
    room = Keyword.get(config, :room)
    access_token = Keyword.get(config, :access_token)

    %{
      state
      | level: level,
        server_url: server_url,
        room: room,
        access_token: access_token
    }
  end

  # Sends the message payload to the server
  def send_message(message, state) do
    %{server_url: server_url, room: room, access_token: access_token} = state

    api_url =
      "#{server_url}/_matrix/client/r0/rooms/#{room}/send/m.room.message?access_token=#{
        access_token
      }"

    payload = "{\"msgtype\":\"m.text\", \"body\":\"#{message}\"}"

    response =
      :httpc.request(
        :post,
        {to_charlist(api_url), [], 'application/json', payload},
        [],
        []
      )

    with {:error, reason} <- response,
         do:
           IO.warn(
             "LoggerExtension.Backend.Matrix.send_message/3 failed! Error (httpc): '#{
               inspect(reason)
             }'."
           )

    state
  end
end

I’ve tried a lot today, but currently I’m thinking this must by somehow be a bug within Elixir / Erlang releases!?

Ok - I found out this must have something to do with the difference of daemon and start vs. remote, rpc and eval.
So about the Erlang release start vs start_clean.
Or the difference that remote, rpc and eval are started in interactive mode vs. embedded.

But I still have not found a solution to solve this problem.

Your code no-ops when group leader isn’t the current node, which I believe should be the case when you’re remoting into a deploy (you’re on a slave node, I think)

3 Likes

@ityonemo
You’re my hero! Thank you very much.
I’ve “copied” the code for my Matrix Logger Backend from the Console Backend.
Where it makes sense to no-ops when the group leader isn’t the current node - but now for my case.