Current section
Files
Jump to
Current section
Files
lib/statix/conn.ex
defmodule Statix.Conn do
@moduledoc false
# sock field holds different types depending on state and transport:
# - UDP: port (after open/1) or atom for process name
# - UDS: {:socket_path, path} before open/1, socket reference after
defstruct [:sock, :address, :port, :prefix, :transport, :socket_path]
alias Statix.Packet
require Logger
def new(host, port, prefix) when is_binary(host) do
new(String.to_charlist(host), port, prefix)
end
def new(host, port, prefix) when is_list(host) or is_tuple(host) do
case :inet.getaddr(host, :inet) do
{:ok, address} ->
%__MODULE__{address: address, port: port, prefix: prefix, transport: :udp}
{:error, reason} ->
raise(
"cannot get the IP address for the provided host " <>
"due to reason: #{:inet.format_error(reason)}"
)
end
end
def new(socket_path, prefix) when is_binary(socket_path) do
%__MODULE__{
prefix: prefix,
transport: :uds,
sock: {:socket_path, socket_path},
socket_path: socket_path
}
end
def open(%__MODULE__{transport: :udp} = conn) do
{:ok, sock} = :gen_udp.open(0, active: false)
%__MODULE__{conn | sock: sock}
end
def open(%__MODULE__{transport: :uds, sock: {:socket_path, path}} = conn) do
unless Code.ensure_loaded?(:socket) do
raise "Unix domain socket support requires OTP 22+"
end
case safe_open(conn) do
{:ok, opened} ->
opened
{:error, reason} ->
raise "Failed to connect to Unix domain socket at #{path}: #{inspect(reason)}"
end
end
def safe_open(%__MODULE__{transport: :uds, sock: {:socket_path, path}} = conn) do
with {:ok, sock} <- :socket.open(:local, :dgram, :default) do
path_addr = %{family: :local, path: String.to_charlist(path)}
case :socket.connect(sock, path_addr) do
:ok ->
{:ok, %__MODULE__{conn | sock: sock}}
{:error, reason} ->
:socket.close(sock)
{:error, reason}
end
end
end
def transmit_event(%__MODULE__{sock: sock} = conn, title, text, options)
when is_list(options) do
result =
Packet.build_event(title, text, options)
|> transmit(conn)
with {:error, error} <- result do
Logger.error(fn ->
if(is_atom(sock), do: "", else: "Statix ") <>
"#{inspect(sock)} event \"#{title}\" lost, error=#{inspect(error)}"
end)
end
result
end
def transmit_metric(%__MODULE__{sock: sock, prefix: prefix} = conn, type, key, val, options)
when is_binary(val) and is_list(options) do
result =
prefix
|> Packet.build_metric(type, key, val, options)
|> transmit(conn)
with {:error, error} <- result do
Logger.error(fn ->
if(is_atom(sock), do: "", else: "Statix ") <>
"#{inspect(sock)} #{type} metric \"#{key}\" lost value #{val}" <>
" error=#{inspect(error)}"
end)
end
result
end
defp transmit(packet, %__MODULE__{
transport: :udp,
address: address,
port: port,
sock: sock_name
}) do
sock = Process.whereis(sock_name)
if sock do
:gen_udp.send(sock, address, port, packet)
else
{:error, :port_closed}
end
end
defp transmit(packet, %__MODULE__{transport: :uds, sock: sock}) do
# UDS DGRAM sockets send atomically
:socket.send(sock, packet)
end
defp transmit(_packet, %__MODULE__{transport: transport}) do
raise ArgumentError, "unsupported transport type: #{inspect(transport)}"
end
end