Packages

appsignal

2.1.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
appsignal mix_helpers.exs
Raw

mix_helpers.exs

{_, _} = Code.eval_file("agent.exs")
defmodule Mix.Appsignal.Helper do
@moduledoc """
Helper functions for downloading and compiling the AppSignal agent library.
"""
@os Application.get_env(:appsignal, :os, :os)
@system Application.get_env(:appsignal, :mix_system, System)
@max_retries 5
@proxy_env_vars [
"APPSIGNAL_HTTP_PROXY",
"https_proxy",
"HTTPS_PROXY",
"http_proxy",
"HTTP_PROXY"
]
require Logger
def install do
report = initial_report()
case verify_system_architecture(report) do
{:ok, {arch, report}} ->
case find_package_source(arch, report) do
{:ok, {arch_config, %{build: %{source: "remote"}} = report}} ->
download_and_compile(arch_config, report)
Logger.debug("""
AppSignal for Elixir #{Mix.Project.config()[:version]} succesfully installed!
If you're upgrading from version 1.x, please review our upgrade guide:
https://docs.appsignal.com/elixir/installation/upgrading-from-1.x-to-2.x.html
""")
{:ok, report} ->
# Installation using already downloaded package of the extension
compile(report)
{:error, {reason, report}} ->
abort_installation(reason, report)
end
{:error, {reason, report}} ->
abort_installation(reason, report)
end
end
@doc """
Checks to see if a proxy is defined in any of the accepted OS enviroment
variables (as per `@proxy_env_vars`).
Returns `nil` if no proxy is defined, or a `{variable_name, proxy_url}` tuple
matching the first found variable.
_NOTE: If the first variable found is defined but empty (""), proxying is
disabled (eg. `nil` is returned)._
"""
def check_proxy(environment \\ System.get_env()) do
Enum.reduce_while(@proxy_env_vars, nil, fn name, acc ->
if value = Map.get(environment, name) do
{:halt, proxy_or_nil(name, value)}
else
{:cont, acc}
end
end)
end
defp proxy_or_nil(_, ""), do: nil
defp proxy_or_nil(name, value), do: {name, value}
defp find_package_source(arch, report) do
architecture_key = arch_key(arch)
arch_config = Appsignal.Agent.triples()[architecture_key]
System.put_env("LIB_DIR", priv_dir())
cond do
has_local_release_files?() ->
Mix.shell().info("AppSignal: Using local agent release.")
File.mkdir_p!(priv_dir())
clean_up_extension_files()
Enum.each(
["appsignal.h", "appsignal-agent", "appsignal.version", "libappsignal.a"],
fn file ->
File.cp(project_ext_path(file), priv_path(file))
end
)
{:ok, merge_report(report, %{build: %{source: "local"}})}
has_files?() and has_correct_agent_version?() ->
{:ok, merge_report(report, %{build: %{source: "cached_in_priv_dir"}})}
is_nil(arch_config) ->
{:error,
{"No architecture build found for '#{architecture_key}'.",
merge_report(report, %{build: %{source: "remote"}})}}
true ->
{:ok, {arch_config, merge_report(report, %{build: %{source: "remote"}})}}
end
end
defp download_and_compile(arch_config, report) do
report = merge_report(report, %{download: %{checksum: "unverified"}})
case download_package(arch_config, report) do
{:ok, {filename, report}} ->
case verify_download_package(filename, arch_config[:checksum], report) do
{:ok, {filename, report}} ->
case extract_package(filename) do
:ok ->
compile(report)
{:error, reason} ->
abort_installation(reason, report)
end
{:error, {reason, report}} ->
abort_installation(reason, report)
end
{:error, {reason, report}} ->
abort_installation(reason, report)
end
end
defp download_package(arch_config, report) do
version = Appsignal.Agent.version()
File.mkdir_p!(priv_dir())
clean_up_extension_files()
url = arch_config[:download_url]
report = merge_report(report, %{download: %{download_url: url}})
filename = Path.join(tmp_dir(), "appsignal-agent-#{version}.tar.gz")
case File.exists?(filename) do
true ->
{:ok, {filename, merge_report(report, %{build: %{source: "cached_in_tmp_dir"}})}}
false ->
Mix.shell().info("Downloading agent release from #{url}")
:application.ensure_all_started(:hackney)
case do_download_file!(url, filename, @max_retries) do
:ok ->
{:ok, {filename, report}}
error ->
{:error, {error, report}}
end
end
end
defp verify_download_package(filename, expected_checksum, report) do
data = File.read!(filename)
calculated_checksum = :crypto.hash(:sha256, data) |> Base.encode16(case: :lower)
if calculated_checksum == expected_checksum do
{:ok, {filename, merge_report(report, %{download: %{checksum: "verified"}})}}
else
sub_report = %{download: %{checksum: "invalid"}}
reason = """
Checksum verification of #{filename} failed!
Calculated: #{calculated_checksum}
Expected: #{expected_checksum}
"""
{:error, {reason, merge_report(report, sub_report)}}
end
end
defp do_download_file!(url, filename, retries \\ 0)
defp do_download_file!(url, filename, 0) do
case :hackney.request(:get, url, [], "", download_options()) do
{:ok, 200, _, reference} ->
case :hackney.body(reference) do
{:ok, body} -> File.write(filename, body)
{:error, reason} -> {:error, reason}
end
response ->
{:error, response}
end
end
defp do_download_file!(url, filename, retries) do
case do_download_file!(url, filename) do
:ok -> :ok
_ -> do_download_file!(url, filename, retries - 1)
end
end
defp download_options do
options = [
ssl_options:
[
verify: :verify_peer,
cacertfile: priv_path("cacert.pem"),
depth: 4,
ciphers: ciphers(),
honor_cipher_order: :undefined
] ++ customize_hostname_check_or_verify_fun()
]
case check_proxy() do
nil ->
options
{var, url} ->
Mix.shell().info("- using proxy from #{var} (#{url})")
options ++ [proxy: url]
end
end
defp extract_package(filename) do
case System.cmd("tar", ["zxf", filename, "--no-same-owner"],
stderr_to_stdout: true,
cd: priv_dir()
) do
{_, 0} ->
:ok
{result, _exitcode} ->
IO.binwrite(result)
{:error, "Extracting of #{filename} failed!"}
end
end
defp compile(report) do
report = merge_report(report, %{build: %{agent_version: agent_version()}})
{output, exit_code} = run_make()
if exit_code == 0 do
Mix.shell().info("AppSignal extension installation successful")
write_report(merge_report(report, %{result: %{status: :success}}))
:ok
else
message = """
Build error was encountered while running `make` (exit code: #{exit_code}):
#{output}
"""
abort_installation(message, report)
end
end
defp run_make do
try do
System.cmd(make(), make_args(to_string(Mix.env())), stderr_to_stdout: true)
rescue
reason ->
{serialize_report_value(reason), 1}
end
end
defp make_args("test" <> _), do: ["-e", "CFLAGS_ADD=-DTEST"]
defp make_args(_), do: []
defp verify_system_architecture(report) do
input_arch = :erlang.system_info(:system_architecture)
case map_arch(input_arch, agent_platform()) do
{:ok, {arch, target} = architecture} ->
sub_report = %{build: %{architecture: arch, target: target}}
{:ok, {architecture, merge_report(report, sub_report)}}
{:error, {:unsupported, {arch, target}}} ->
sub_report = %{build: %{architecture: arch, target: target}}
reason =
"Unsupported target platform #{arch} - #{target}, AppSignal " <>
"integration disabled!\nPlease check " <>
"http://docs.appsignal.com/support/operating-systems.html"
{:error, {reason, merge_report(report, sub_report)}}
{:error, {:unknown, {arch, target}}} ->
sub_report = %{build: %{architecture: arch, target: target}}
reason =
"Unknown target platform #{arch} - #{target}, AppSignal " <>
"integration disabled!\nPlease check " <>
"http://docs.appsignal.com/support/operating-systems.html"
{:error, {reason, merge_report(report, sub_report)}}
end
end
if Mix.env() != :test_no_nif do
defp map_arch('i386-' ++ _, platform), do: build_for("i686", platform)
defp map_arch('i686-' ++ _, platform), do: build_for("i686", platform)
defp map_arch('x86-' ++ _, platform), do: build_for("i686", platform)
defp map_arch('amd64-' ++ _, platform), do: build_for("x86_64", platform)
defp map_arch('x86_64-' ++ _, platform), do: build_for("x86_64", platform)
end
defp map_arch(arch, platform), do: {:error, {:unknown, {arch, platform}}}
defp build_for(bit, platform) do
arch = {bit, platform}
case Map.has_key?(Appsignal.Agent.triples(), arch_key(arch)) do
true -> {:ok, arch}
false -> {:error, {:unsupported, arch}}
end
end
defp arch_key({arch, target}) do
"#{arch}-#{target}"
end
defp tmp_dir do
default_tmp_dir = "/tmp"
case {File.dir?(default_tmp_dir), File.stat(default_tmp_dir)} do
{true, {:ok, %{access: :write}}} -> default_tmp_dir
{true, {:ok, %{access: :read_write}}} -> default_tmp_dir
_ -> System.tmp_dir!()
end
end
defp priv_path(filename) do
Path.join(priv_dir(), filename)
end
defp project_ext_path(filename) do
Path.join([__DIR__, "c_src", filename])
end
defp has_file(filename) do
filename |> priv_path |> File.exists?()
end
defp has_local_ext_file(filename) do
filename |> project_ext_path |> File.exists?()
end
defp has_files? do
has_file("appsignal-agent") and has_file("appsignal.h") and has_file("appsignal_extension.so")
end
defp has_local_release_files? do
has_local_ext_file("appsignal-agent") and has_local_ext_file("appsignal.h") and
has_local_ext_file("libappsignal.a")
end
defp has_correct_agent_version? do
agent_version() == Appsignal.Agent.version()
end
defp agent_version do
path = priv_path("appsignal.version")
{:ok, agent_version} = File.read(path)
String.trim(agent_version)
end
defp clean_up_extension_files do
priv_dir()
|> Path.join("*appsignal*")
|> Path.wildcard()
|> Enum.each(&File.rm_rf!/1)
end
defp library_dependencies do
case ldd_version_output() do
{:ok, output} ->
case extract_ldd_version(output) do
nil ->
%{}
ldd_version ->
%{libc: ldd_version}
end
_ ->
%{}
end
end
def agent_platform do
case force_musl_build?() do
true ->
"linux-musl"
false ->
case @os.type() do
{:unix, :linux} ->
agent_platform_by_ldd_version()
{_, os} ->
to_string(os)
end
end
end
defp agent_platform_by_ldd_version do
case ldd_version_output() do
{:ok, output} ->
case String.contains?(output, "musl") do
true ->
"linux-musl"
false ->
case extract_ldd_version(output) do
nil ->
"linux"
ldd_version ->
case Version.compare("#{ldd_version}.0", "2.15.0") do
:lt -> "linux-musl"
_ -> "linux"
end
end
end
_ ->
"linux"
end
end
# Fetches the libc version number from the `ldd` command
# If `ldd` is not found it returns `nil`
defp ldd_version_output do
case @system.cmd("ldd", ["--version"], stderr_to_stdout: true) do
{output, _} ->
{:ok, output}
end
rescue
exception ->
{:error, exception}
end
defp extract_ldd_version(ldd_output) when is_binary(ldd_output) do
case Regex.run(~r/\d+\.\d+/, ldd_output) do
[version | _tail] -> version
_ -> nil
end
end
defp extract_ldd_version(_), do: nil
defp initial_report do
{_, os} = :os.type()
%{
result: %{
status: :incomplete
},
language: %{
name: "elixir",
version: System.version(),
otp_version: System.otp_release()
},
build: %{
time: DateTime.to_iso8601(DateTime.utc_now()),
package_path: priv_dir(),
architecture: nil,
target: os,
musl_override: force_musl_build?(),
library_type: "static"
},
host: %{
root_user: root?(),
dependencies: library_dependencies()
}
}
end
defp write_report(report) do
write_download_report(report)
write_report_file("install", report)
end
defp write_download_report(%{download: %{}} = report) do
%{
build: %{
time: time,
architecture: architecture,
target: target,
musl_override: musl_override,
library_type: library_type
}
} = report
%{
download: %{
download_url: download_url,
checksum: checksum
}
} = report
download_report = %{
download: %{
time: time,
architecture: architecture,
target: target,
musl_override: musl_override,
library_type: library_type,
download_url: download_url,
checksum: checksum || "unverified"
}
}
write_report_file("download", download_report)
end
defp write_download_report(_) do
# Write nothing if no download details are recorded in the report
end
cond do
Code.ensure_loaded?(Jason) ->
defp json_encoder, do: {:ok, Jason}
Code.ensure_loaded?(Poison) ->
defp json_encoder, do: {:ok, Poison}
true ->
defp json_encoder do
{:error,
"""
No JSON encoder found. Please add jason to your list of dependencies in mix.exs:
def deps do
[
{:appsignal, "~> 1.0"},
{:jason, "~> 1.1"}
]
end
"""}
end
end
defp encode_report_file(report) do
case json_encoder() do
{:ok, encoder} -> encoder.encode(report)
{:error, reason} -> {:error, reason}
end
end
defp write_report_file(file, report) do
case encode_report_file(report) do
{:ok, body} ->
File.mkdir_p!(priv_dir())
filename = "#{file}.report"
case File.open(priv_path(filename), [:write]) do
{:ok, file} ->
result = IO.binwrite(file, body)
File.close(file)
result
{:error, reason} ->
Mix.Shell.IO.error("""
Error: Could not write AppSignal installation report file (#{filename}).
#{serialize_report_value(reason)}
""")
{:error, reason}
end
{:error, reason} ->
Mix.Shell.IO.error("""
Error: Could not encode AppSignal installation report.
#{serialize_report_value(reason)}
""")
{:error, reason}
end
end
defp merge_report(report, %{download: _download_report} = sub_report) do
key = :download
{download_report, sub_report} = Map.pop(sub_report, key)
report = Map.put(report, key, Map.merge(report[key] || %{}, download_report))
merge_report(report, sub_report)
end
defp merge_report(report, %{build: _build_report} = sub_report) do
key = :build
{build_report, sub_report} = Map.pop(sub_report, key)
report = Map.put(report, key, Map.merge(report[key], build_report))
merge_report(report, sub_report)
end
defp merge_report(report, %{result: _installation_report} = sub_report) do
key = :result
{installation_report, sub_report} = Map.pop(sub_report, key)
report = Map.put(report, key, installation_report)
merge_report(report, sub_report)
end
defp merge_report(report, %{}), do: report
defp abort_installation(reason, report) do
report =
merge_report(report, %{result: %{status: :failed, message: serialize_report_value(reason)}})
write_report(report)
Mix.Shell.IO.error("AppSignal installation failed: #{serialize_report_value(reason)}")
end
defp priv_dir do
case :code.priv_dir(:appsignal) do
{:error, :bad_name} ->
# This happens on initial compilation
Mix.Tasks.Compile.Erlang.manifests()
|> List.first()
|> Path.dirname()
|> String.trim_trailing(".mix")
|> Path.join("priv")
path ->
path
|> List.to_string()
end
end
defp force_musl_build? do
!is_nil(System.get_env("APPSIGNAL_BUILD_FOR_MUSL"))
end
defp make do
if System.find_executable("gmake"), do: "gmake", else: "make"
end
if System.otp_release() >= "20.3" do
defp ciphers, do: :ssl.cipher_suites(:default, :"tlsv1.2")
else
defp ciphers, do: :ssl.cipher_suites()
end
if System.otp_release() >= "21" do
defp customize_hostname_check_or_verify_fun do
[
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
end
else
defp customize_hostname_check_or_verify_fun do
[
verify_fun:
{fn
_, :valid, state -> {:valid, state}
_, :valid_peer, state -> {:valid, state}
_, {:extension, _}, state -> {:unknown, state}
_, reason, _ -> {:fail, reason}
end, self()}
]
end
end
def root? do
uid() == 0
end
def uid do
try do
case @system.cmd("id", ["-u"]) do
{id, 0} ->
case Integer.parse(List.first(String.split(id, "\n"))) do
{int, _} -> int
:error -> nil
end
{_, _} ->
nil
end
catch
:error, _ ->
nil
end
end
defp serialize_report_value(value) when is_binary(value), do: value
defp serialize_report_value(value), do: inspect(value)
end