Skip to content

Commit d13b725

Browse files
authored
feat: allow converting otel trace ids to datadog values (#91)
* feat: allow converting otel trace ids to datadog values * mix format * update function for better performance * add support for charlist values
1 parent 8c1ebe2 commit d13b725

File tree

2 files changed

+95
-4
lines changed

2 files changed

+95
-4
lines changed

lib/logger_json/formatters/datadog_logger.ex

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ defmodule LoggerJSON.Formatters.DatadogLogger do
2929
@behaviour LoggerJSON.Formatter
3030

3131
@default_opts %{hostname: :system}
32-
@processed_metadata_keys ~w[pid file line function module application span_id trace_id]a
32+
@processed_metadata_keys ~w[pid file line function module application span_id trace_id otel_span_id otel_trace_id]a
3333

3434
@impl true
3535
def init(formatter_opts \\ %{}) do
@@ -72,16 +72,46 @@ defmodule LoggerJSON.Formatters.DatadogLogger do
7272
# To connect logs and traces, span_id and trace_id keys are respectively dd.span_id and dd.trace_id
7373
# https://docs.datadoghq.com/tracing/faq/why-cant-i-see-my-correlated-logs-in-the-trace-id-panel/?tab=jsonlogs
7474
defp convert_tracing_keys(output, md) do
75-
Enum.reduce([:trace_id, :span_id], output, fn key, acc ->
75+
fields = %{
76+
span_id: ["dd.span_id", & &1],
77+
trace_id: ["dd.trace_id", & &1],
78+
otel_span_id: ["dd.span_id", &convert_otel_field/1],
79+
otel_trace_id: ["dd.trace_id", &convert_otel_field/1]
80+
}
81+
82+
Enum.reduce(fields, output, fn {key, [new_key, transformer]}, acc ->
7683
if Keyword.has_key?(md, key) do
77-
dd_key = "dd." <> Atom.to_string(key)
78-
Map.merge(acc, %{dd_key => Keyword.get(md, key)})
84+
new_value = apply(transformer, [Keyword.get(md, key)])
85+
Map.merge(acc, %{new_key => new_value})
7986
else
8087
acc
8188
end
8289
end)
8390
end
8491

92+
# This converts native OpenTelemetry fields to the native Datadog format.
93+
# This function is taken from the Datadog examples for converting. Mostly the Golang version
94+
# https://docs.datadoghq.com/tracing/other_telemetry/connect_logs_and_traces/opentelemetry/?tab=go
95+
# Tests were stolen from https://github.com/open-telemetry/opentelemetry-specification/issues/525
96+
# and https://go.dev/play/p/pUBHcLdXJNy
97+
defp convert_otel_field(<<value::binary-size(16)>>) do
98+
{value, _} = Integer.parse(value, 16)
99+
Integer.to_string(value, 10)
100+
rescue
101+
_ -> ""
102+
end
103+
104+
defp convert_otel_field(value) when byte_size(value) < 16, do: ""
105+
106+
defp convert_otel_field(value) do
107+
value = to_string(value)
108+
len = byte_size(value) - 16
109+
<<_front::binary-size(len), value::binary>> = value
110+
convert_otel_field(value)
111+
rescue
112+
_ -> ""
113+
end
114+
85115
defp method_name(metadata) do
86116
function = Keyword.get(metadata, :function)
87117
module = Keyword.get(metadata, :module)

test/unit/logger_json_datadog_test.exs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,67 @@ defmodule LoggerJSONDatadogTest do
223223
assert Map.has_key?(log, "trace_id") == false
224224
assert Map.has_key?(log, "span_id") == false
225225
end
226+
227+
test "convert otel_trace_id/otel_span_id binary to expected datadog keys" do
228+
Logger.configure_backend(LoggerJSON, metadata: :all)
229+
230+
Logger.metadata(
231+
otel_trace_id:
232+
<<98, 56, 49, 48, 100, 98, 97, 50, 57, 56, 48, 51, 101, 101, 54, 49, 101, 55, 99, 55, 49, 102, 102, 48, 99,
233+
50, 99, 57, 53, 97, 57, 100>>
234+
)
235+
236+
log =
237+
fn -> Logger.debug("hello") end
238+
|> capture_log()
239+
|> Jason.decode!()
240+
241+
assert %{"dd.trace_id" => "16701352862047361693"} = log
242+
assert Map.has_key?(log, "trace_id") == false
243+
assert Map.has_key?(log, "span_id") == false
244+
end
245+
246+
test "convert otel_trace_id/otel_span_id charlist to expected datadog keys" do
247+
Logger.configure_backend(LoggerJSON, metadata: :all)
248+
Logger.metadata(otel_trace_id: 'b810dba29803ee61e7c71ff0c2c95a9d')
249+
250+
log =
251+
fn -> Logger.debug("hello") end
252+
|> capture_log()
253+
|> Jason.decode!()
254+
255+
assert %{"dd.trace_id" => "16701352862047361693"} = log
256+
assert Map.has_key?(log, "trace_id") == false
257+
assert Map.has_key?(log, "span_id") == false
258+
end
259+
260+
test "convert otel_trace_id/otel_span_id string to expected datadog keys" do
261+
Logger.configure_backend(LoggerJSON, metadata: :all)
262+
Logger.metadata(otel_trace_id: "e7c71ff0c2c95a9d")
263+
264+
log =
265+
fn -> Logger.debug("hello") end
266+
|> capture_log()
267+
|> Jason.decode!()
268+
269+
assert %{"dd.trace_id" => "16701352862047361693"} = log
270+
assert Map.has_key?(log, "trace_id") == false
271+
assert Map.has_key?(log, "span_id") == false
272+
end
273+
274+
test "does not error on incorrect otel_trace_id/otel_span_id metadata" do
275+
Logger.configure_backend(LoggerJSON, metadata: :all)
276+
Logger.metadata(otel_trace_id: {:noop})
277+
278+
log =
279+
fn -> Logger.debug("hello") end
280+
|> capture_log()
281+
|> Jason.decode!()
282+
283+
assert %{"dd.trace_id" => ""} = log
284+
assert Map.has_key?(log, "trace_id") == false
285+
assert Map.has_key?(log, "span_id") == false
286+
end
226287
end
227288

228289
describe "on_init/1 callback" do

0 commit comments

Comments
 (0)