diff --git a/lib/logger_json/formatters/datadog_logger.ex b/lib/logger_json/formatters/datadog_logger.ex index fec2f90..3b6c8c6 100644 --- a/lib/logger_json/formatters/datadog_logger.ex +++ b/lib/logger_json/formatters/datadog_logger.ex @@ -67,7 +67,7 @@ defmodule LoggerJSON.Formatters.DatadogLogger do LoggerJSON.take_metadata(md, md_keys, @processed_metadata_keys) |> convert_tracing_keys(md) |> JasonSafeFormatter.format() - |> FormatterUtils.maybe_put(:error, FormatterUtils.format_process_crash(md)) + |> FormatterUtils.maybe_put(:error, format_process_crash(md)) end # To connect logs and traces, span_id and trace_id keys are respectively dd.span_id and dd.trace_id @@ -113,6 +113,38 @@ defmodule LoggerJSON.Formatters.DatadogLogger do _ -> "" end + # This follows the DataDog standard attributes for error tracking, allowing + # errors to automatically be aggregated by error tracking. + defp format_process_crash(metadata) do + case Keyword.get(metadata, :crash_reason) do + {reason, stacktrace} -> + initial_call = Keyword.get(metadata, :initial_call) + + json_map( + initial_call: format_initial_call(initial_call), + stack: Exception.format_stacktrace(stacktrace), + message: format_exception_message(reason), + kind: format_exception_kind(reason) + ) + + nil -> + nil + end + end + + defp format_initial_call(nil), do: nil + + defp format_initial_call({module, function, arity}), + do: FormatterUtils.format_function(module, function, arity) + + defp format_exception_message(%{message: message}), do: message + defp format_exception_message(other), do: JasonSafeFormatter.format(other) + + defp format_exception_kind(:throw), do: "throw" + defp format_exception_kind(:exit), do: "exit" + defp format_exception_kind(%exception{}), do: inspect(exception) + defp format_exception_kind(other), do: inspect(other) + defp method_name(metadata) do function = Keyword.get(metadata, :function) module = Keyword.get(metadata, :module) diff --git a/test/unit/logger_json/formatters/datadog_logger_test.exs b/test/unit/logger_json/formatters/datadog_logger_test.exs index a2ef055..1bf8b9d 100644 --- a/test/unit/logger_json/formatters/datadog_logger_test.exs +++ b/test/unit/logger_json/formatters/datadog_logger_test.exs @@ -413,14 +413,25 @@ defmodule LoggerJSONDatadogTest do test "logs crash reason when present" do Logger.configure_backend(LoggerJSON, metadata: [:crash_reason]) - Logger.metadata(crash_reason: {%RuntimeError{message: "oops"}, []}) + + Logger.metadata( + crash_reason: + {%RuntimeError{message: "oops"}, + [ + {Exception, :exception, [[message: "stacktrace test"]], []}, + {LoggerJSONDatadogTest, :"test logs crash reason when present", 1, + [file: 'test/unit/logger_json/formatters/datadog_logger_test.exs', line: 414]} + ]} + ) log = capture_log(fn -> Logger.debug("hello") end) |> Jason.decode!() assert is_nil(log["error"]["initial_call"]) - assert log["error"]["reason"] == "** (RuntimeError) oops" + assert log["error"]["kind"] == "RuntimeError" + assert log["error"]["message"] == "oops" + assert log["error"]["stack"] =~ "stacktrace test" end test "logs erlang style crash reasons" do @@ -432,7 +443,7 @@ defmodule LoggerJSONDatadogTest do |> Jason.decode!() assert is_nil(log["error"]["initial_call"]) - assert log["error"]["reason"] == "{:socket_closed_unexpectedly, []}" + assert log["error"]["message"] == "socket_closed_unexpectedly" end test "logs initial call when present" do