defmodule Tracer.Formatter.Base do @doc """ Format trace message to a string. """ def format_trace({:trace_ts, pid, :call, mfa, timestamp}, opts) do "#{inspect(pid)} [#{format_time(timestamp)}] call #{call_mfa(mfa, opts)}" end def format_trace({:trace_ts, pid, :call, mfa, dump, timestamp}, opts) do traces = String.split(dump, "\n") |> Enum.filter(&Regex.match?(~r/Return addr 0x|CP: 0x/, &1)) |> fold_over |> Enum.reverse() "#{inspect(pid)} [#{format_time(timestamp)}] call #{call_mfa(mfa, opts)}#{traces}" end def format_trace({:trace_ts, pid, :return_from, mfa, return, time}, opts) do "#{inspect(pid)} [#{time_used(time)}] returned " <> "#{return_mfa(mfa)}#{inspect(return, opts)}" end def format_trace({:trace_ts, pid, :exception_from, mfa, {class, value}, time}, _opts) do "#{inspect(pid)} [#{time_used(time)}] exception " <> "#{return_mfa(mfa)}#{inspect(class)}:#{inspect(value)}" end def format_trace(msg, opts) do "unknown message: #{inspect(msg, opts)}" end defp time_used(time) when time < 1000, do: time defp time_used(time), do: "#{div(time, 1000)}ms" defp call_mfa({module, function, arguments}, opts) do "#{inspect(module)}.#{function}(" <> Enum.map_join(arguments, ", ", &inspect(&1, opts)) <> ")" end defp return_mfa({module, function, argument}) do "#{inspect(module)}.#{function}/#{argument} -> " end defp fold_over(list, acc \\ []) defp fold_over([_last], acc), do: acc defp fold_over([one | tail], acc) do fold_over(tail, [extract_function(one) | acc]) end defp extract_function(line) do case Regex.run(~r"^.+\((.+):(.+)/(\d+).+\)$", line, capture: :all_but_first) do [m, f, a_length] -> "\n #{format_module(m)}.#{format_function(f)}/#{a_length}" nil -> "" end end defp format_module(binatom) do case unatom(binatom) do "Elixir." <> binatom -> binatom binatom -> ":#{binatom}" end end defp format_function(binatom) do unatom(binatom) end defp unatom(binatom) do body_size = byte_size(binatom) - 2 case binatom do <<"'", body::binary-size(body_size), "'">> -> body _ -> binatom end end defp to_time({_, _, micro} = now) do {_, {hours, minutes, seconds}} = :calendar.now_to_universal_time(now) {hours, minutes, seconds, div(micro, 1000)} end def format_time({_, _, _} = now), do: now |> to_time() |> format_time() def format_time({hh, mi, ss, ms}) do [pad2(hh), ?:, pad2(mi), ?:, pad2(ss), ?., pad3(ms)] end defp pad3(int) when int < 10, do: [?0, ?0, Integer.to_string(int)] defp pad3(int) when int < 100, do: [?0, Integer.to_string(int)] defp pad3(int), do: Integer.to_string(int) defp pad2(int) when int < 10, do: [?0, Integer.to_string(int)] defp pad2(int), do: Integer.to_string(int) end