defmodule Slipstream.Honeycomb.Connection do
@moduledoc """
A GenServer that collects telemetry events from Slipstream connections and
emits them to Honeycomb
"""
@sender Application.get_env(
:slipstream_honeycomb,
:honeycomb_sender,
Opencensus.Honeycomb.Sender
)
alias Opencensus.Honeycomb.Event
@event_names [
~w[slipstream connection connect stop]a,
~w[slipstream connection handle stop]a
]
use GenServer
@doc false
def start_link(_args) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
@doc false
@impl GenServer
def init(state) do
{:ok, state, {:continue, :telemetry_attach}}
end
@doc false
@impl GenServer
def handle_continue(:telemetry_attach, state) do
:telemetry.attach_many(
"slipstream-honeycomb-connection-exporter",
@event_names,
&handle_event/4,
state
)
{:noreply, state}
end
@doc false
def handle_event(event, measurements, metadata, _state) do
GenServer.cast(__MODULE__, {event, measurements, metadata})
end
@doc false
@impl GenServer
def handle_cast({event, measurements, metadata}, state) do
{event, measurements, metadata}
|> map_to_event()
|> send_event()
{:noreply, state}
end
defp map_to_event(
{~w[slipstream connection connect stop]a, %{duration: duration},
metadata}
) do
struct(
Event,
%{
time: metadata.start_time,
data: %{
state: inspect(metadata.state),
traceId: metadata.trace_id,
id: metadata.connection_id,
durationMs: convert_time(duration)
},
samplerate: 1.0
}
)
end
defp map_to_event(
{~w[slipstream connection handle stop]a, %{duration: duration},
metadata}
) do
struct(
Event,
%{
time: metadata.start_time,
data: %{
start_state: inspect(metadata.start_state),
end_state: inspect(metadata.end_state),
traceId: metadata.trace_id,
parentId: metadata.connection_id,
id: metadata.span_id,
durationMs: convert_time(duration),
raw_message: inspect(metadata.raw_message),
message: inspect(metadata.message),
events: inspect(metadata.events),
built_events: inspect(metadata.built_events),
return: metadata.return
},
samplerate: 1.0
}
)
end
defp send_event(event) do
@sender.send_batch([event])
end
defp convert_time(time) do
# nanoseconds but with decimals!
# if we were to convert directly to msec, it'd be an integer :(
System.convert_time_unit(time, :native, :microsecond) / 1_000
end
end