Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 37 additions & 9 deletions lib/open_telemetry_decorator/attributes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,51 @@ defmodule OpenTelemetryDecorator.Attributes do

def get(all_attributes, requested_attributes) do
Enum.reduce(requested_attributes, [], fn requested_attribute, taken_attributes ->
case get_attribute(all_attributes, requested_attribute) do
{name, value} -> Keyword.put(taken_attributes, name, value)
_ -> taken_attributes
end
otlp_attributes = get_attributes(all_attributes, requested_attribute)

otlp_attributes ++ taken_attributes
end)
end

defp get_attribute(attributes, [attribute_name | nested_keys]) do
defp get_attributes(attributes, [attribute_name | nested_keys]) do
requested_obj = attributes |> Keyword.get(attribute_name) |> as_map()

if value = recursive_get_in(requested_obj, nested_keys) do
{derived_name([attribute_name | nested_keys]), to_otlp_value(value)}
otlp_value = to_otlp_value(value)

map_otlp_attributes([attribute_name | nested_keys], otlp_value)
else
[]
end
end

defp get_attribute(attributes, attribute_name) do
defp get_attributes(attributes, attribute_name) do
if value = Keyword.get(attributes, attribute_name) do
{derived_name(attribute_name), to_otlp_value(value)}
otlp_value = to_otlp_value(value)

map_otlp_attributes([attribute_name], otlp_value)
else
[]
end
end

defp map_otlp_attributes(name_nesting, otlp_value) do
case otlp_value do
attrs when is_list(attrs) ->
Enum.map(attrs, fn {key, value} ->
name = derived_name(name_nesting ++ [key])
{name, value}
end)

otlp_value ->
[{derived_name(name_nesting), otlp_value}]
end
end

defp recursive_get_in(obj, []), do: obj

defp recursive_get_in(obj, [key]), do: get_in(obj, [key])

defp recursive_get_in(obj, [key | nested_keys]) do
nested_obj =
case get_in(obj, [key]) do
Expand Down Expand Up @@ -104,5 +126,11 @@ defmodule OpenTelemetryDecorator.Attributes do
when is_binary(value) or is_integer(value) or is_boolean(value) or is_float(value)

defp to_otlp_value(value) when is_otlp_value(value), do: value
defp to_otlp_value(value), do: inspect(value)

defp to_otlp_value(value) do
case OpenTelemetryDecorator.Traceable.impl_for(value) do
nil -> inspect(value)
impl -> impl.otlp_value(value)
end
end
end
8 changes: 8 additions & 0 deletions lib/open_telemetry_decorator/traceable.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defprotocol OpenTelemetryDecorator.Traceable do
@type t :: OpenTelemetryDecorator.Traceable.t()

@type otlp_value :: number() | String.t() | boolean() | OpenTelemetry.attributes_map()

@spec otlp_value(t()) :: otlp_value()
def otlp_value(value)
end
39 changes: 39 additions & 0 deletions test/open_telemetry_decorator/attributes_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,45 @@ defmodule OpenTelemetryDecorator.AttributesTest do
assert attrs == [{:result_a, "b"}, {:obj_id, 1}]
end

test "handles traceable scalar otlp values" do
attributes = [date: ~D[2020-01-01], obj: %{checkout: ~D[2021-01-01]}]
required_attributes = [:date, [:obj, :checkout]]

attrs = Attributes.get(attributes, required_attributes)

assert attrs == [obj_checkout: "2021-01-01", date: "2020-01-01"]
end

test "handles traceable list of otlp key and values" do
attributes = [
uri: URI.parse("https://example.com"),
obj: %{home_page: URI.parse("https://example.com/home")}
]

required_attributes = [:uri, [:obj, :home_page]]

attrs = Attributes.get(attributes, required_attributes)

assert attrs == [
obj_home_page_authority: "example.com",
obj_home_page_fragment: nil,
obj_home_page_host: "example.com",
obj_home_page_path: "/home",
obj_home_page_port: 443,
obj_home_page_query: nil,
obj_home_page_scheme: "https",
obj_home_page_userinfo: nil,
uri_authority: "example.com",
uri_fragment: nil,
uri_host: "example.com",
uri_path: nil,
uri_port: 443,
uri_query: nil,
uri_scheme: "https",
uri_userinfo: nil
]
end

test "when target value is valid OTLP type, use it" do
assert [{:val, 42.42}] == Attributes.get([val: 42.42], [:val])
assert [{:val, true}] == Attributes.get([val: true], [:val])
Expand Down
18 changes: 18 additions & 0 deletions test/support/traceable_protocols_examples.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defimpl OpenTelemetryDecorator.Traceable, for: Date do
def otlp_value(date), do: Date.to_iso8601(date)
end

defimpl OpenTelemetryDecorator.Traceable, for: URI do
def otlp_value(uri) do
[
{"authority", uri.authority},
{"fragment", uri.fragment},
{"host", uri.host},
{"path", uri.path},
{"port", uri.port},
{"query", uri.query},
{"scheme", uri.scheme},
{"userinfo", uri.userinfo}
]
end
end