Skip to content

Instantly share code, notes, and snippets.

@bforchhammer
Last active November 1, 2023 08:04
Show Gist options
  • Save bforchhammer/79d205246d60a2527876bba8d25ce065 to your computer and use it in GitHub Desktop.
Save bforchhammer/79d205246d60a2527876bba8d25ce065 to your computer and use it in GitHub Desktop.
Opentelemetry propagator for datadog tracing headers
defmodule DatadogPropagator do
@moduledoc """
Opentelemetry context propagator for spandex / datadog traces.
This propagator adds support for datadog tracing headers (eg. x-datadog-trace-id), which is the
format supported by the Spandex library to enable distributed tracing.
Notes:
- The x-datadog-origin header is currently not supported
- The value of the x-datadog-sampling-priority header is currently simply mapped to "1" for
sampled traces and 0 otherwise
More details on propagation: https://opentelemetry.io/docs/instrumentation/erlang/propagation
"""
@behaviour :otel_propagator_text_map
require Record
Record.defrecord(:span_ctx, Record.extract(:span_ctx, from_lib: "opentelemetry_api/include/opentelemetry.hrl"))
@trace_id "x-datadog-trace-id"
@parent_id "x-datadog-parent-id"
@sampling_priority "x-datadog-sampling-priority"
def fields(_), do: [@trace_id, @parent_id, @sampling_priority]
def inject(context, carrier, carrier_set_fn, _options) do
case :otel_tracer.current_span_ctx(context) do
span_ctx(trace_flags: trace_flags) = span_ctx ->
carrier = carrier_set_fn.(@trace_id, dd_trace_id(span_ctx), carrier)
carrier = carrier_set_fn.(@parent_id, dd_span_id(span_ctx), carrier)
carrier = carrier_set_fn.(@sampling_priority, dd_sampling_priority(trace_flags), carrier)
carrier
_ ->
carrier
end
end
def extract(context, carrier, carrier_keys_fn, carrier_get_fn, _options) do
keys = carrier_keys_fn.(carrier)
if @trace_id in keys and @parent_id in keys do
trace_id =
carrier_get_fn.(@trace_id, carrier)
|> :erlang.binary_to_integer()
span_id =
carrier_get_fn.(@parent_id, carrier)
|> :erlang.binary_to_integer()
sampled? =
if @sampling_priority in keys do
carrier_get_fn.(@sampling_priority, carrier)
|> parse_sampling_priority()
else
# Same handling as otel_propagator_b3multi: if the optional sampled flag is missing,
# we just set it to 0.
0
end
span_ctx = :otel_tracer.from_remote_span(trace_id, span_id, sampled?)
:otel_tracer.set_current_span(context, span_ctx)
else
context
end
end
# See :opentelemetry_logger_metadata_datadog.trace_id_to_dd()
defp dd_trace_id(span_ctx) do
hex_trace_id = :otel_span.hex_trace_id(span_ctx)
:binary.part(hex_trace_id, {byte_size(hex_trace_id), -16})
|> :erlang.binary_to_list()
|> :erlang.list_to_integer(16)
|> :erlang.integer_to_binary()
end
defp dd_span_id(span_ctx) do
:otel_span.span_id(span_ctx)
end
defp dd_sampling_priority(trace_flags) do
# See IS_SAMPLED macro in "opentelemetry_api/include/opentelemetry.hrl"
if Bitwise.band(trace_flags, 1) != 0, do: "1", else: "0"
end
# From https://github.com/open-telemetry/opentelemetry-python-contrib/blob/b53b9a012f76c4fc883c3c245fddc29142706d0d/exporter/opentelemetry-exporter-datadog/src/opentelemetry/exporter/datadog/propagator.py#L59
# AUTO_KEEP = 1
# USER_KEEP = 2
@dd_sampled_values [1, 2]
defp parse_sampling_priority(sampling_priority) when is_binary(sampling_priority) do
priority = sampling_priority |> :erlang.binary_to_integer()
if priority in @dd_sampled_values, do: 1, else: 0
end
end
config :opentelemetry,
text_map_propagators: [:baggage, :trace_context, SpandexPropagator]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment