defmodule ErrorTracker.ErrorNotifier.Email do @moduledoc false import Swoosh.Email require Logger def send(occurrence, header) do conf = config() from_email = Keyword.fetch!(conf, :from_email) to_email = Keyword.fetch!(conf, :to_email) mailer = Keyword.fetch!(conf, :mailer) first_line = first_stack_line(occurrence) file = if first_line, do: first_line.file, else: "unknown" line = if first_line, do: first_line.line, else: "?" error_name = String.slice(occurrence.reason, 0, 80) email = new() |> to(to_email) |> from({"MusicLibrary", from_email}) |> subject("[MusicLibrary] Error: #{error_name} - #{file}:#{line}") |> html_body(build_html(occurrence, header)) case mailer.deliver(email) do {:ok, _} -> Logger.info("Error notification email sent") {:ok, :sent} {:error, reason} -> Logger.error("Failed to send error notification: #{inspect(reason)}") {:error, reason} end end # -- Private -- defp build_html(occurrence, header) do first_line = first_stack_line(occurrence) location = if first_line do "#{first_line.module}.#{first_line.function} (#{first_line.file}:#{first_line.line})" else "Unknown location" end view = occurrence.context["live_view.view"] || "N/A" path = occurrence.context["request.path"] || "N/A" error_url = error_url(occurrence.error_id) escaped_header = html_escape(header) escaped_error_id = html_escape(occurrence.error_id) escaped_reason = occurrence.reason |> String.slice(0..199) |> html_escape() escaped_location = html_escape(location) escaped_view = html_escape(view) escaped_path = html_escape(path) escaped_url = html_escape(error_url) stack_trace_html = format_stack_trace(occurrence) """
ErrorTracker has detected an error:
Error ID: #{escaped_error_id}
Reason: #{escaped_reason}
Location: #{escaped_location}
#{stack_trace_html}View: #{escaped_view}
Request Path: #{escaped_path}
Time: #{DateTime.utc_now()}
Stack Trace:
#{formatted}
"""
_ ->
""
end
end
defp error_url(error_id) do
conf = config()
base_url = Keyword.get(conf, :base_url, "")
error_path = Keyword.get(conf, :error_tracker_path, "/dev/errors")
error_path = error_path |> String.trim_trailing("/")
base_url = base_url |> String.trim_trailing("/")
"#{base_url}#{error_path}/#{error_id}"
end
defp first_stack_line(occurrence) do
case occurrence.stacktrace do
%{lines: [first | _]} -> first
_ -> nil
end
end
defp html_escape(value) do
value |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
end
defp config do
Application.get_env(:music_library, ErrorTracker.ErrorNotifier, [])
end
end