Packages

appsignal

2.1.2
2.17.4 2.17.3 2.17.2 2.17.1 2.17.0 2.16.0 2.15.11 2.15.10 2.15.9 2.15.8 2.15.7 2.15.6 2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 2.15.0 2.14.1 2.14.0 2.13.4 2.13.3 2.13.2 2.13.1 2.13.0 2.12.3 2.12.2 2.12.1 2.12.0 2.11.0 2.10.2 2.10.1 2.10.0 2.9.2 2.9.1 2.9.0 2.8.3 2.8.2 2.8.1 2.8.0 2.7.13 2.7.12 2.7.11 2.7.10 2.7.9 2.7.8 2.7.7 2.7.6 2.7.5 2.7.4 2.7.3 2.7.2 2.7.1 2.7.0 2.7.0-beta.1 2.6.1 2.6.0 2.5.3 2.5.2 2.5.1 2.5.0 2.4.3 2.4.2 2.4.1 2.4.0 2.3.1 2.3.0 2.2.19 2.2.18 2.2.17 2.2.16 2.2.15 2.2.14 2.2.13 2.2.12 retired 2.2.11 2.2.10 2.2.9 2.2.8 2.2.7 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 2.2.1 2.2.0 2.1.15 2.1.14 2.1.13 2.1.12 2.1.11 2.1.10 2.1.9 2.1.8 2.1.7 2.1.7-beta.1 2.1.6 2.1.5 2.1.4 2.1.3 2.1.2 2.1.1 2.1.0 2.0.8 2.0.7 2.0.6 2.0.6-beta.1 2.0.5 retired 2.0.4 retired 2.0.3 retired 2.0.2 retired 2.0.1 retired 2.0.0 retired 2.0.0-beta.11 2.0.0-beta.10 2.0.0-beta.9 2.0.0-beta.8 2.0.0-beta.7 2.0.0-beta.6 2.0.0-beta.5 2.0.0-beta.4 2.0.0-beta.3 2.0.0-beta.2 2.0.0-beta.1 2.0.0-alpha.7 2.0.0-alpha.6 2.0.0-alpha.5 2.0.0-alpha.4 2.0.0-alpha.3 2.0.0-alpha.2 2.0.0-alpha.1 1.13.5 1.13.4 1.13.3 1.13.2 1.13.1 1.13.0 1.13.0-beta.2 1.13.0-beta.1 1.12.1 1.12.0 1.12.0-beta.3 1.12.0-beta.2 1.12.0-beta.1 1.11.8 1.11.7 1.11.6 1.11.5 1.11.4 1.11.3 1.11.2 1.11.1 1.11.0 1.11.0-beta.1 1.10.13 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.4 1.9.3 1.9.2 1.9.1 1.9.1-beta.1 1.9.0 1.9.0-beta.1 1.9.0-alpha.1 1.8.2 1.8.1 1.8.1-beta.2 1.8.1-beta.1 1.8.0 1.7.2 1.7.1 1.7.0 1.7.0-beta.2 1.7.0-beta.1 1.7.0-alpha.4 1.7.0-alpha.3 1.7.0-alpha.2 1.7.0-alpha.1 1.6.7 1.6.6 1.6.6-beta.2 1.6.6-beta.1 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.6.0-beta.1 1.6.0-alpha.1 1.5.1 1.5.0 1.5.0-beta.2 1.5.0-beta.1 1.4.11-beta.1 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.4.0-alpha.1 1.3.6 1.3.6-beta.1 1.3.5 1.3.4 1.3.4-beta.1 1.3.3 1.3.2 1.3.1 1.3.0 1.3.0-beta.3 1.3.0-beta.2 1.3.0-beta.1 1.2.3 1.2.2 1.2.1 1.2.0 1.1.1 1.1.0 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 1.0.0-rc.1 0.13.0 0.12.3 0.12.2 0.12.1 0.12.0 0.11.6 0.11.5 0.11.4 0.11.3 0.11.2 0.11.1 0.11.0 0.10.0 0.9.2 0.9.1 0.9.0 0.8.0 0.7.1 0.7.0 0.6.2 0.6.1 0.6.0 0.5.0 0.4.0 0.3.0 0.2.0 0.1.0 0.0.9 0.0.8 0.0.7 0.0.6 0.0.5 0.0.4 0.0.3 0.0.2 0.0.1

Collects error and performance data from your Elixir applications and sends it to AppSignal

Current section

Files

Jump to
appsignal lib mix tasks appsignal.diagnose.ex
Raw

lib/mix/tasks/appsignal.diagnose.ex

defmodule Mix.Tasks.Appsignal.Diagnose do
use Mix.Task
alias Appsignal.Config
alias Appsignal.Diagnose
@system Application.get_env(:appsignal, :appsignal_system, Appsignal.System)
@report Application.get_env(:appsignal, :appsignal_diagnose_report, Appsignal.Diagnose.Report)
@shortdoc "Starts and tests AppSignal while validating the configuration"
def run(args) do
send_report =
cond do
is_nil(args) -> nil
Enum.member?(args, "--send-report") -> :send_report
Enum.member?(args, "--no-send-report") -> :no_send_report
true -> nil
end
Application.load(:appsignal)
report = %{process: %{uid: @system.uid()}}
configure_appsignal()
config_report = Diagnose.Config.config()
config = config_report[:options]
report = Map.put(report, :config, config_report)
header()
empty_line()
library_report = Diagnose.Library.info()
report = Map.put(report, :library, library_report)
print_library_info(library_report)
empty_line()
installation_report = fetch_installation_report()
report = Map.put(report, :installation, installation_report)
print_installation_report(installation_report)
empty_line()
host_report = Diagnose.Host.info()
report = Map.put(report, :host, host_report)
print_host_information(host_report)
empty_line()
report =
case Diagnose.Agent.report() do
{:ok, agent_report} ->
print_agent_diagnostics(agent_report)
Map.put(report, :agent, agent_report)
{:error, :nif_not_loaded} ->
IO.puts("Agent diagnostics")
IO.puts(" Error: Nif not loaded, aborting.\n")
report
{:error, raw_report} ->
IO.puts("Agent diagnostics")
IO.puts(" Error: Could not parse the agent report:")
IO.puts(" Output: #{raw_report}\n")
Map.put(report, :agent, %{output: raw_report})
end
print_configuration(config_report)
empty_line()
validation_report = Diagnose.Validation.validate(config)
report = Map.put(report, :validation, validation_report)
print_validation(validation_report)
empty_line()
path_report = Diagnose.Paths.info()
report = Map.put(report, :paths, path_report)
print_paths(path_report)
empty_line()
send_report_to_appsignal_if_agreed_upon(config, report, send_report)
end
defp header do
IO.puts("AppSignal diagnose")
IO.puts(String.duplicate("=", 80))
IO.puts("Use this information to debug your configuration.")
IO.puts("More information is available on the documentation site.")
IO.puts("http://docs.appsignal.com/")
IO.puts("Send this output to support@appsignal.com if you need help.")
IO.puts(String.duplicate("=", 80))
end
defp print_library_info(library_report) do
IO.puts("AppSignal agent")
IO.puts(" Language: Elixir")
IO.puts(" Package version: #{library_report[:package_version]}")
IO.puts(" Agent version: #{library_report[:agent_version]}")
IO.puts(" Nif loaded: #{yes_or_no(library_report[:extension_loaded])}")
end
defp fetch_installation_report do
download_report =
case do_fetch_installation_report("download") do
{:ok, report} ->
report
{:error, %{"parsing_error" => parsing_report}} ->
%{"download_parsing_error" => parsing_report}
end
install_report =
case do_fetch_installation_report("install") do
{:ok, report} ->
report
{:error, %{"parsing_error" => parsing_report}} ->
%{"installation_parsing_error" => parsing_report}
end
Map.merge(install_report, download_report)
end
defp do_fetch_installation_report(file) do
case File.read(Path.join([:code.priv_dir(:appsignal), "#{file}.report"])) do
{:ok, raw_report} ->
case Appsignal.Json.decode(raw_report) do
{:ok, report} ->
{:ok, report}
{:error, reason} ->
{:error, %{"parsing_error" => %{"error" => reason, "raw" => raw_report}}}
end
{:error, reason} ->
{:error, %{"parsing_error" => %{"error" => reason}}}
end
end
defp print_installation_report(report) do
IO.puts("Extension installation report")
download_parsing_error = Map.has_key?(report, "download_parsing_error")
install_parsing_error = Map.has_key?(report, "installation_parsing_error")
if download_parsing_error && install_parsing_error do
do_print_parsing_error("download", report)
do_print_parsing_error("installation", report)
end
if install_parsing_error do
do_print_download_report(report)
do_print_parsing_error("installation", report)
else
do_print_installation_report(report)
end
end
defp do_print_parsing_error(key, report) do
parsing_report = report["#{key}_parsing_error"]
IO.puts(" Error found while parsing the #{key} report.")
IO.puts(" Error: #{format_value(parsing_report["error"])}")
if Map.has_key?(parsing_report, "raw") do
IO.puts(" Raw report:\n#{format_value(parsing_report["raw"])}")
end
end
defp do_print_installation_report(installation_report) do
result_report = installation_report["result"]
IO.puts(" Installation result")
IO.puts(" Status: #{format_value(result_report["status"])}")
if Map.has_key?(result_report, "message") do
IO.puts(" Message: #{format_value(result_report["message"])}")
end
if Map.has_key?(result_report, "error") do
IO.puts(" Error: #{format_value(result_report["error"])}")
end
language_report = installation_report["language"]
IO.puts(" Language details")
IO.puts(" Elixir version: #{format_value(language_report["version"])}")
IO.puts(" OTP version: #{format_value(language_report["otp_version"])}")
do_print_download_report(installation_report)
build_report = installation_report["build"]
IO.puts(" Build details")
IO.puts(" Install time: #{format_value(build_report["time"])}")
IO.puts(" Source: #{format_value(build_report["source"])}")
IO.puts(" Agent version: #{format_value(build_report["agent_version"])}")
IO.puts(" Architecture: #{format_value(build_report["architecture"])}")
IO.puts(" Target: #{format_value(build_report["target"])}")
IO.puts(" Musl override: #{format_value(build_report["musl_override"])}")
IO.puts(" Library type: #{format_value(build_report["library_type"])}")
host_report = installation_report["host"]
IO.puts(" Host details")
IO.puts(" Root user: #{format_value(host_report["root_user"])}")
IO.puts(" Dependencies: #{format_value(host_report["dependencies"])}")
end
defp do_print_download_report(%{"download_parsing_error" => %{}} = installation_report) do
do_print_parsing_error("download", installation_report)
end
defp do_print_download_report(installation_report) do
download_report = installation_report["download"]
IO.puts(" Download details")
IO.puts(" Download time: #{format_value(download_report["time"])}")
IO.puts(" Download URL: #{format_value(download_report["download_url"])}")
IO.puts(" Architecture: #{format_value(download_report["architecture"])}")
IO.puts(" Target: #{format_value(download_report["target"])}")
IO.puts(" Musl override: #{format_value(download_report["musl_override"])}")
IO.puts(" Library type: #{format_value(download_report["library_type"])}")
IO.puts(" Checksum: #{format_value(download_report["checksum"])}")
end
defp print_host_information(host_report) do
IO.puts("Host information")
IO.puts(" Architecture: #{host_report[:architecture]}")
IO.puts(" Elixir version: #{host_report[:language_version]}")
IO.puts(" OTP version: #{host_report[:otp_version]}")
IO.puts(" Operating System: #{host_report[:os]}")
root_user = if host_report[:root], do: "yes (not recommended)", else: "no"
IO.puts(" root user: #{root_user}")
if host_report[:heroku] do
IO.puts(" Heroku: yes")
end
IO.puts(" Container: #{yes_or_no(host_report[:running_in_container])}")
end
defp configure_appsignal do
Config.initialize()
Config.write_to_environment()
end
defp print_agent_diagnostics(report) do
Diagnose.Agent.print(report)
end
defp print_configuration(config) do
IO.puts("Configuration")
Enum.each(config[:options], fn {key, _} = option ->
config_label = configuration_option_label(option)
option_sources = config[:sources]
sources = sources_for_option(key, option_sources)
sources_label = configuration_option_source_label(key, sources, option_sources)
IO.puts("#{config_label}#{sources_label}")
end)
IO.puts(
"\nRead more about how the diagnose config output is rendered\n" <>
"https://docs.appsignal.com/elixir/command-line/diagnose.html"
)
end
defp configuration_option_label({key, value}) do
" #{key}: #{format_value(value)}"
end
defp configuration_option_source_label(_, [], _), do: ""
defp configuration_option_source_label(_, [:default], _), do: ""
defp configuration_option_source_label(_, sources, _) when length(sources) == 1 do
" (Loaded from #{Enum.join(sources, ", ")})"
end
defp configuration_option_source_label(key, sources, option_sources) do
max_source_label_length =
sources
|> Enum.map(fn source ->
source
|> to_string
|> String.length()
end)
|> Enum.max()
# + 1 to account for the : symbol
max_source_label_length = max_source_label_length + 1
sources_label =
sources
|> Enum.map(fn source ->
label = String.pad_trailing("#{source}:", max_source_label_length)
" #{label} #{format_value(option_sources[source][key])}"
end)
|> Enum.join("\n")
"\n Sources:\n#{sources_label}"
end
defp sources_for_option(key, sources) do
[:default, :system, :file, :env]
|> Enum.map(fn source ->
if Map.has_key?(sources[source], key) do
source
end
end)
|> Enum.reject(fn value -> value == nil end)
end
defp format_value(value) do
inspect(value)
end
defp print_validation(validation_report) do
IO.puts("Validation")
IO.puts(" Push API key: #{validation_report[:push_api_key]}")
end
defp print_paths(path_report) do
IO.puts("Paths")
labels = Diagnose.Paths.labels()
Enum.each(path_report, fn {name, path} ->
print_path(path, Map.fetch!(labels, name))
end)
end
defp print_path(path, label) do
IO.puts(" #{label}")
IO.puts(" Path: #{inspect(path[:path])}")
if path[:exists] do
IO.puts(" Writable?: #{yes_or_no(path[:writable])}")
file_uid = path[:ownership][:uid]
process_uid = @system.uid()
IO.write(" Ownership?: #{yes_or_no(file_uid == process_uid)}")
IO.puts(" (file: #{file_uid}, process: #{process_uid})")
else
IO.puts(" Exists?: no")
end
if path[:content] do
IO.puts(" Contents (last 10 lines):")
Enum.each(Enum.take(path[:content], -10), &IO.puts/1)
end
if path[:error], do: IO.puts(" Error: #{path[:error]}")
end
defp send_report_to_appsignal_if_agreed_upon(config, report, send_report) do
IO.puts("\nDiagnostics report")
IO.puts(" Do you want to send this diagnostics report to AppSignal?")
IO.puts(
" If you share this report you will be given a link to \n" <>
" AppSignal.com to validate the report.\n" <>
" You can also contact us at support@appsignal.com\n with your support token.\n\n"
)
answer =
case send_report do
:send_report ->
IO.puts(" Confirmed sending report using --send-report option.")
true
:no_send_report ->
IO.puts(" Not sending report. (Specified with the --no-send-report option.)")
false
_ ->
yes_or_no?(" Send diagnostics report to AppSignal? (Y/n): ")
end
case answer do
true ->
IO.puts("\n Transmitting diagnostics report")
send_report_to_appsignal(config, report)
false ->
IO.puts(" Not sending diagnostics report to AppSignal.")
end
end
def send_report_to_appsignal(config, report) do
case @report.send(config, report) do
{:ok, support_token} ->
IO.puts(" Your support token: #{support_token}")
IO.puts(" View this report: https://appsignal.com/diagnose/#{support_token}")
{:error, %{status_code: 200, body: body}} ->
IO.puts(" Error: Couldn't decode server response.")
IO.puts(" Response body: #{body}")
{:error, %{status_code: status_code, body: body}} ->
IO.puts(" Error: Something went wrong while submitting the report to AppSignal.")
IO.puts(" Response code: #{status_code}")
IO.puts(" Response body: #{body}")
{:error, %{reason: reason}} ->
IO.puts(" Error: Something went wrong while submitting the report to AppSignal.")
IO.puts(reason)
end
end
defp empty_line, do: IO.puts("")
defp yes_or_no(true), do: "yes"
defp yes_or_no(false), do: "no"
# Ask for a yes or no input from the user
defp yes_or_no?(prompt) do
case IO.gets(prompt) do
input when is_binary(input) ->
case String.downcase(String.trim(input)) do
input when input in ["y", "yes", ""] -> true
input when input in ["n", "no"] -> false
_ -> yes_or_no?(prompt)
end
:eof ->
yes_or_no?(prompt)
{:error, reason} ->
IO.puts(" Error while reading input: #{reason}")
yes_or_no?(prompt)
end
end
end