Skip to content

Commit

Permalink
refactor: Redesign main modules to enable ad-hoc usage
Browse files Browse the repository at this point in the history
  • Loading branch information
martosaur committed Jan 25, 2025
1 parent d0a282a commit 7a46d45
Show file tree
Hide file tree
Showing 31 changed files with 1,476 additions and 1,033 deletions.
2 changes: 1 addition & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ config :disco_log,
occurrences_channel_id: "",
info_channel_id: "",
error_channel_id: "",
discord: DiscoLog.DiscordMock,
discord_client_module: DiscoLog.Discord.API.Mock,
websocket_adapter: DiscoLog.WebsocketClient.Mock,
enable_presence: true

Expand Down
59 changes: 58 additions & 1 deletion lib/disco_log.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,68 @@ defmodule DiscoLog do
@moduledoc """
Elixir-based built-in error tracking solution.
"""
alias DiscoLog.Dedupe
alias DiscoLog.Error
alias DiscoLog.Storage
alias DiscoLog.Discord.API
alias DiscoLog.Discord.Prepare

def report(exception, stacktrace, given_context \\ %{}, config \\ nil) do
config = config || DiscoLog.Config.read!()
context = Map.merge(DiscoLog.Context.get(), given_context)

error = DiscoLog.Error.new(exception, stacktrace, context, config)
DiscoLog.Client.send_error(error, config)
send_error(error, config)
end

def send_error(%Error{} = error, config) do
with :ok <- maybe_dedupe(error, config) do
config.supervisor_name
|> Storage.get_thread_id(error.fingerprint)
|> case do
nil ->
available_tags = Storage.get_tags(config.supervisor_name) || %{}

applied_tags =
error.context
|> Map.keys()
|> Enum.filter(&(&1 in Map.keys(available_tags)))
|> Enum.map(&Map.fetch!(available_tags, &1))

message = Prepare.prepare_occurrence(error, applied_tags)

with {:ok, %{status: 201, body: %{"id" => thread_id}}} <-
API.post_thread(config.discord_client, config.occurrences_channel_id, message) do
Storage.add_thread_id(config.supervisor_name, error.fingerprint, thread_id)
end

thread_id ->
message = Prepare.prepare_occurrence_message(error)

API.post_message(config.discord_client, thread_id, message)
end
end
end

def log_info(message, metadata, config) do
message = Prepare.prepare_message(message, metadata)

API.post_message(config.discord_client, config.info_channel_id, message)
end

def log_error(message, metadata, config) do
message = Prepare.prepare_message(message, metadata)

API.post_message(config.discord_client, config.error_channel_id, message)
end

defp maybe_dedupe(%Error{} = error, config) do
case Dedupe.insert(config.supervisor_name, error) do
:new ->
:ok

:existing ->
:excluded
end
end
end
104 changes: 0 additions & 104 deletions lib/disco_log/client.ex

This file was deleted.

23 changes: 8 additions & 15 deletions lib/disco_log/config.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
defmodule DiscoLog.Config do
alias DiscoLog.Discord

@configuration_schema [
otp_app: [
type: :atom,
Expand Down Expand Up @@ -50,7 +48,7 @@ defmodule DiscoLog.Config do
enable_discord_log: [
type: :boolean,
default: false,
doc: "Logs requests to Discord API?"
doc: "Log requests to Discord API?"
],
enable_presence: [
type: :boolean,
Expand Down Expand Up @@ -82,15 +80,9 @@ defmodule DiscoLog.Config do
default: [:cowboy, :bandit],
doc: "Logs with domains from this list will be ignored"
],
before_send: [
type: {:or, [nil, :mod_arg, {:fun, 1}]},
default: nil,
doc:
"This callback will be called with error or {message, metadata} tuple as argument before it is sent"
],
discord: [
discord_client_module: [
type: :atom,
default: DiscoLog.Discord,
default: DiscoLog.Discord.API.Client,
doc: "Discord client to use"
],
supervisor_name: [
Expand Down Expand Up @@ -129,7 +121,7 @@ defmodule DiscoLog.Config do
@compiled_schema NimbleOptions.new!(@configuration_schema)

@moduledoc """
Configuration related module for DiscoLog.
DiscoLog configuration
## Configuration Schema
Expand Down Expand Up @@ -166,9 +158,9 @@ defmodule DiscoLog.Config do
"""
@spec validate(options :: keyword() | map()) ::
{:ok, config()} | {:error, NimbleOptions.ValidationError.t()}
def validate(%{discord_config: _} = config) do
def validate(%{discord_client: _} = config) do
config
|> Map.delete(:discord_config)
|> Map.delete(:discord_client)
|> validate()
end

Expand All @@ -179,7 +171,8 @@ defmodule DiscoLog.Config do
validated
|> Map.new()
|> then(fn config ->
Map.put(config, :discord_config, Discord.Config.new(config))
client = config.discord_client_module.client(config.token)
Map.put(config, :discord_client, %{client | log?: config.enable_discord_log})
end)

{:ok, config}
Expand Down
144 changes: 63 additions & 81 deletions lib/disco_log/discord.ex
Original file line number Diff line number Diff line change
@@ -1,84 +1,66 @@
defmodule DiscoLog.DiscordBehaviour do
@moduledoc false
@type config :: %DiscoLog.Discord.Config{}

@callback list_channels(config()) :: {:ok, list(any)} | {:error, String.t()}

@callback fetch_or_create_channel(
config(),
channels :: list(any),
channel_config :: map(),
parent_id :: nil | String.t()
) :: {:ok, map()} | {:error, String.t()}

@callback maybe_delete_channel(config(), channels :: list(any), channel_config :: map()) ::
{:ok, map()} | {:error, String.t()}

@callback create_occurrence_thread(config(), error :: String.t()) ::
{:ok, map()} | {:error, String.t()}

@callback create_occurrence_message(config(), thread_id :: String.t(), error :: String.t()) ::
{:ok, map()} | {:error, String.t()}

@callback list_occurrence_threads(config(), occurrence_channel_id :: String.t()) :: list(any)

@callback delete_channel_messages(config(), channel_id :: String.t()) ::
list(any)

@callback create_message(
config(),
channel_id :: String.t(),
message :: String.t(),
metadata :: map()
) ::
{:ok, map()} | {:error, String.t()}

@callback delete_threads(config(), channel_id :: String.t()) :: list(any)

@callback get_gateway(config()) :: {:ok, String.t()} | {:error, String.t()}

@callback list_tags(config(), occurence_channel_id :: String.t()) :: map()
end

defmodule DiscoLog.Discord do
@moduledoc """
Abstraction over Discord api
"""
@behaviour DiscoLog.DiscordBehaviour

alias DiscoLog.Discord

@impl true
def list_channels(config), do: Discord.Client.list_channels(config)

@impl true
defdelegate fetch_or_create_channel(config, channels, channel_config, parent_id \\ nil),
to: Discord.Context

@impl true
defdelegate maybe_delete_channel(config, channels, channel_config), to: Discord.Context

@impl true
defdelegate create_occurrence_thread(config, error), to: Discord.Context

@impl true
defdelegate create_occurrence_message(config, thread_id, error), to: Discord.Context

@impl true
defdelegate list_occurrence_threads(config, occurrence_channel_id), to: Discord.Context

@impl true
defdelegate delete_channel_messages(config, channel_id), to: Discord.Context

@impl true
defdelegate create_message(config, channel_id, message, metadata), to: Discord.Context

@impl true
defdelegate delete_threads(config, channel_id), to: Discord.Context

@impl true
defdelegate get_gateway(config), to: Discord.Context
@moduledoc false

@impl true
defdelegate list_tags(config, occurence_channel_id), to: Discord.Context
alias DiscoLog.Discord.API
alias DiscoLog.Discord.Prepare

def list_occurrence_threads(discord_client, guild_id, occurrences_channel_id) do
case API.list_active_threads(discord_client, guild_id) do
{:ok, %{status: 200, body: %{"threads" => threads}}} ->
active_threads =
threads
|> Enum.filter(&(&1["parent_id"] == occurrences_channel_id))
|> Enum.map(&{Prepare.fingerprint_from_thread_name(&1["name"]), &1["id"]})
|> Map.new()

{:ok, active_threads}

{:ok, response} ->
{:error, response}

other ->
other
end
end

def list_occurrence_tags(discord_client, occurrences_channel_id) do
case API.get_channel(discord_client, occurrences_channel_id) do
{:ok, %{status: 200, body: %{"available_tags" => available_tags}}} ->
tags = for %{"id" => id, "name" => name} <- available_tags, into: %{}, do: {name, id}
{:ok, tags}

{:ok, response} ->
{:error, response}

error ->
error
end
end

def get_gateway(discord_client) do
case API.get_gateway(discord_client) do
{:ok, %{status: 200, body: %{"url" => raw_uri}}} -> URI.new(raw_uri)
{:ok, response} -> {:error, response}
error -> error
end
end

def delete_threads(discord_client, guild_id, channel_id) do
{:ok, %{status: 200, body: %{"threads" => threads}}} =
API.list_active_threads(discord_client, guild_id)

threads
|> Enum.filter(&(&1["parent_id"] == channel_id))
|> Enum.map(fn %{"id" => thread_id} ->
{:ok, %{status: 200}} = API.delete_thread(discord_client, thread_id)
end)
end

def delete_channel_messages(discord_client, channel_id) do
{:ok, %{status: 200, body: messages}} = API.get_channel_messages(discord_client, channel_id)

for %{"id" => message_id} <- messages do
API.delete_message(discord_client, channel_id, message_id)
end
end
end
Loading

0 comments on commit 7a46d45

Please sign in to comment.