Current section
Files
Jump to
Current section
Files
lib/open_telemetry/honeycomb/event.ex
defmodule OpenTelemetry.Honeycomb.Event do
@moduledoc """
Event structure.
Honeycomb events bind a timestamp to data as described in `t:t/0` below. The `data` corresponds
to OpenTelemetry span attributes, with limitations dictated by the intersection of their data
data models. For information on how we clean and flatten span attributes before sending them,
see `OpenTelemetry.Honeycomb.Attributes`.
Honeycomb event attributes for trace handling can collide with other attributes. For information
on the defaults and how to change them, see `OpenTelemetry.Honeycomb.Config.AttributeMap`.
"""
alias OpenTelemetry.Honeycomb.Attributes
alias OpenTelemetry.Honeycomb.Config.AttributeMap
alias OpenTelemetry.Records.Span
require Record
@enforce_keys [:time, :data]
defstruct [:time, :data, samplerate: 1]
@typedoc """
Span attributes after flattening.
"""
@type event_data :: %{optional(String.t()) => OpenTelemetry.attribute_value()}
@typedoc """
Honeycomb event suitable for POSTing to their batch API.
* `time`: ms since epoch; [MUST] be in ISO 8601 format, e.g. `"2019-05-17T09:55:12.622658Z"`
* `data`: `t:event_data/0` after flattening.
* `samplerate`: the sample rate, as a positive integer; `1_000` describes a `1:1000` ratio.
[MUST]: https://tools.ietf.org/html/rfc2119#section-1
"""
@type t :: %__MODULE__{
time: String.t(),
samplerate: pos_integer(),
data: event_data()
}
@doc """
The current UTC time in ISO 8601 format, e.g. `"2019-05-17T09:55:12.622658Z"`
Useful when creating events manually.
"""
@spec now() :: String.t()
def now do
DateTime.utc_now() |> DateTime.to_iso8601()
end
@doc """
Convert one OpenTelemetry span to an event suitable for POSTing to the
[Honeycomb Events API][HCevents].
[HCevents]: https://docs.honeycomb.io/api/events/
"""
@spec from_ot_span(
:opentelemetry.span(),
resource_attributes :: OpenTelemetry.attributes(),
attribute_map :: AttributeMap.t()
) :: [t()]
def from_ot_span(ot_span, resource_attributes, attribute_map) do
span = Span.from(ot_span)
data =
span
|> Map.get(:attributes)
|> Attributes.clean()
|> Attributes.merge(resource_attributes)
|> Attributes.merge(extracted_attributes(span, attribute_map))
|> Enum.into(%{}, &Attributes.trim_long_strings/1)
time =
span.start_time
|> :opentelemetry.convert_timestamp(:microsecond)
|> DateTime.from_unix!(:microsecond)
|> DateTime.to_iso8601()
[%__MODULE__{time: time, data: data}]
end
# span attribute extractors
@spec extracted_attributes(Span.t(), AttributeMap.t()) :: OpenTelemetry.attributes()
defp extracted_attributes(%Span{} = span, attribute_map) do
attribute_mapper = get_attribute_mapper(attribute_map)
[
duration_ms: ms(span.end_time) - ms(span.start_time),
name: span.name,
parent_span_id: hexify_span_id(span.parent_span_id),
span_id: hexify_span_id(span.span_id),
trace_id: hexify_trace_id(span.trace_id)
]
|> Enum.map(attribute_mapper)
|> Enum.filter(&has_binary_key?/1)
|> Attributes.sort()
end
@spec get_attribute_mapper(attribute_map :: AttributeMap.t()) ::
({atom(), OpenTelemetry.attribute_value()} ->
{OpenTelemetry.attribute_key(), OpenTelemetry.attribute_value()})
defp get_attribute_mapper(map) do
fn {k, v} -> {Map.get(map, k), v} end
end
defp has_binary_key?({k, _}) when is_binary(k), do: true
defp has_binary_key?(_), do: false
defp hexify_trace_id(:undefined), do: nil
defp hexify_trace_id(n), do: :io_lib.format("~32.16.0b", [n]) |> to_string()
defp hexify_span_id(:undefined), do: nil
defp hexify_span_id(n), do: :io_lib.format("~16.16.0b", [n]) |> to_string()
defp ms(t), do: :opentelemetry.convert_timestamp(t, :microsecond) / 1000
end