Packages
appsignal
2.3.1
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
Current section
Files
lib/mix/tasks/appsignal.diagnose.ex
defmodule Mix.Tasks.Appsignal.Diagnose do
use Mix.Task
alias Appsignal.Config
alias Appsignal.Diagnose
require Appsignal.Utils
@system Appsignal.Utils.compile_env(:appsignal, :appsignal_system, Appsignal.System)
@report Appsignal.Utils.compile_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)
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("https://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 library")
IO.puts(" Language: Elixir")
IO.puts(" Package version: #{format_value(library_report[:package_version])}")
IO.puts(" Agent version: #{format_value(library_report[:agent_version])}")
IO.puts(" Nif loaded: #{format_value(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)
else
if install_parsing_error do
do_print_download_report(report)
do_print_parsing_error("installation", report)
else
do_print_installation_report(report)
end
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: #{inspect(parsing_report["error"])}")
if Map.has_key?(parsing_report, "raw") do
IO.puts(" Raw report:\n#{inspect(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: #{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: #{build_report["musl_override"]}")
IO.puts(" Linux ARM override: #{build_report["linux_arm_override"]}")
IO.puts(" Library type: #{format_value(build_report["library_type"])}")
IO.puts(" Dependencies: #{format_value(build_report["dependencies"])}")
IO.puts(" Flags: #{format_value(build_report["flags"])}")
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: #{download_report["musl_override"]}")
IO.puts(" Linux ARM override: #{download_report["linux_arm_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: #{format_value(host_report[:architecture])}")
IO.puts(" Operating System: #{format_value(host_report[:os])}")
IO.puts(" Elixir version: #{format_value(host_report[:language_version])}")
IO.puts(" OTP version: #{format_value(host_report[:otp_version])}")
root_user = if host_report[:root], do: "true (not recommended)", else: "false"
IO.puts(" Root user: #{root_user}")
if host_report[:heroku] do
IO.puts(" Heroku: true")
end
IO.puts(" Running in container: #{format_value(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")
# Filter out the diagnose_endpoint config option. Users don't need to see
# the config option. It's a private config option.
filtered_options = Enum.reject(config[:options], fn {key, _} -> key == :diagnose_endpoint end)
Enum.each(filtered_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 =
Enum.map_join(sources, "\n", fn source ->
label = String.pad_trailing("#{source}:", max_source_label_length)
" #{label} #{format_value(option_sources[source][key])}"
end)
"\n Sources:\n#{sources_label}"
end
defp sources_for_option(key, sources) do
[:default, :system, :file, :env, :override]
|> 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) when is_nil(value), do: "nil"
defp format_value(value) when is_boolean(value), do: value
defp format_value(value) when is_atom(value) do
value
|> Atom.to_string()
|> format_value
end
defp format_value(value), do: inspect(value)
defp print_validation(validation_report) do
IO.puts("Validation")
IO.puts(" Validating Push API key: #{validation_report[:push_api_key]}")
end
defp print_paths(path_report) do
IO.puts("Paths")
labels = Diagnose.Paths.labels()
Enum.each(labels, fn {name, label} ->
print_path(Map.fetch!(path_report, name), label)
end)
end
defp print_path(path, label) do
IO.puts(" #{label}")
IO.puts(" Path: #{inspect(path[:path])}")
if path[:exists] do
IO.puts(" Writable?: #{format_value(path[:writable])}")
file_uid = path[:ownership][:uid]
process_uid = @system.uid()
IO.write(" Ownership?: #{format_value(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]}")
empty_line()
end
defp send_report_to_appsignal_if_agreed_upon(config, report, send_report) do
IO.puts("Diagnostics 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"
)
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(" Transmitting diagnostics report\n")
send_report_to_appsignal(config, report)
false ->
IO.puts(" Not sending diagnostics information 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("")
# 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