diff --git a/.sampo/changesets/dashing-witch-aino.md b/.sampo/changesets/dashing-witch-aino.md new file mode 100644 index 0000000..65f5607 --- /dev/null +++ b/.sampo/changesets/dashing-witch-aino.md @@ -0,0 +1,5 @@ +--- +hex/posthog: patch +--- + +Add source location stacktrace frames for plain Logger messages. diff --git a/lib/posthog/handler.ex b/lib/posthog/handler.ex index ddd660e..d6b07d0 100644 --- a/lib/posthog/handler.ex +++ b/lib/posthog/handler.ex @@ -77,7 +77,13 @@ defmodule PostHog.Handler do initial_exception = exception(log_event, config) reporter_exception = - log_event |> Map.update!(:meta, &Map.delete(&1, :crash_reason)) |> exception(config) + log_event + |> Map.update!(:meta, fn metadata -> + metadata + |> Map.delete(:crash_reason) + |> Map.put(:posthog_reporter, true) + end) + |> exception(config) [initial_exception, reporter_exception] end @@ -97,7 +103,13 @@ defmodule PostHog.Handler do initial_exception = exception(log_event, config) reporter_exception = - log_event |> Map.update!(:meta, &Map.delete(&1, :crash_reason)) |> exception(config) + log_event + |> Map.update!(:meta, fn metadata -> + metadata + |> Map.delete(:crash_reason) + |> Map.put(:posthog_reporter, true) + end) + |> exception(config) [reporter_exception, initial_exception] end @@ -135,6 +147,16 @@ defmodule PostHog.Handler do defp do_type(%{meta: %{crash_reason: {reason, _}}}), do: Exception.format_banner(:exit, reason) + defp do_type(%{msg: {:string, chardata}, meta: %{error_logger: _}}), + do: IO.chardata_to_string(chardata) + + defp do_type(%{msg: {:string, chardata}, meta: %{posthog_reporter: true}}), + do: IO.chardata_to_string(chardata) + + defp do_type(%{level: level, msg: {:string, _}, meta: %{mfa: {_, _, _}, file: file, line: _}}) + when is_binary(file) or is_list(file), + do: "Logger.#{level}" + defp do_type(%{msg: {:string, chardata}}), do: IO.chardata_to_string(chardata) defp do_type(%{msg: {:report, %{label: {:gen_server, :terminate}}}}) do @@ -203,8 +225,50 @@ defmodule PostHog.Handler do ), do: %{stacktrace: do_stacktrace(stacktrace, in_app_modules, config)} + defp stacktrace( + %{meta: %{mfa: {module, function, arity_or_args}, file: file, line: line}}, + in_app_modules, + config + ) + when is_binary(file) or is_list(file) do + filename = + file |> IO.chardata_to_string() |> normalize_source_filename(config.root_source_code_paths) + + frame = + %{ + platform: "custom", + lang: "elixir", + function: Exception.format_mfa(module, function, arity_or_args), + filename: filename, + lineno: line, + module: inspect(module), + in_app: module in in_app_modules, + resolved: true, + synthetic: true + } + |> maybe_add_source_context(filename, line, config) + + %{stacktrace: %{type: "raw", frames: [frame]}} + end + defp stacktrace(_event, _, _), do: %{} + defp normalize_source_filename(filename, root_paths) do + relative_to_source_root = + root_paths + |> Enum.map(&Path.expand/1) + |> Enum.sort_by(&String.length/1, :desc) + |> Enum.find_value(fn root -> + relative = Path.relative_to(filename, root) + + if relative != filename do + relative + end + end) + + relative_to_source_root || Path.relative_to_cwd(filename) + end + defp do_stacktrace([_ | _] = stacktrace, in_app_modules, config) do frames = for {module, function, arity_or_args, location} <- stacktrace do diff --git a/test/posthog/handler_test.exs b/test/posthog/handler_test.exs index 6dd2a8d..b4c27b1 100644 --- a/test/posthog/handler_test.exs +++ b/test/posthog/handler_test.exs @@ -25,7 +25,7 @@ defmodule PostHog.HandlerTest do properties: %{ "$exception_list": [ %{ - type: "Hello World", + type: "Logger.info", value: "Hello World", mechanism: %{handled: true, type: "generic"} } @@ -53,7 +53,7 @@ defmodule PostHog.HandlerTest do foo: "bar", "$exception_list": [ %{ - type: "Hello World", + type: "Logger.info", value: "Hello World", mechanism: %{handled: true, type: "generic"} } @@ -62,6 +62,52 @@ defmodule PostHog.HandlerTest do } = event end + test "adds synthetic stacktrace frame for plain logger messages", %{ + handler_ref: ref, + config: %{supervisor_name: supervisor_name} + } do + expected_line = log_plain_message() + LoggerHandlerKit.Assert.assert_logged(ref) + + assert [event] = all_captured(supervisor_name) + + assert %{ + properties: %{ + "$exception_list": [ + %{ + type: "Logger.info", + value: "Hello World", + stacktrace: %{ + type: "raw", + frames: [ + %{ + platform: "custom", + lang: "elixir", + filename: filename, + function: function, + lineno: ^expected_line, + module: "PostHog.HandlerTest", + in_app: false, + resolved: true, + synthetic: true + } + ] + } + } + ] + } + } = event + + assert filename == "test/posthog/handler_test.exs" + assert function == "PostHog.HandlerTest.log_plain_message/0" + end + + defp log_plain_message do + expected_line = __ENV__.line + 1 + Logger.info("Hello World") + expected_line + end + @tag config: [capture_level: :warning] test "ignores messages lower than capture_level", %{ handler_ref: ref, @@ -1487,7 +1533,7 @@ defmodule PostHog.HandlerTest do extra: "Foo", "$exception_list": [ %{ - type: "Error with metadata", + type: "Logger.error", value: "Error with metadata", mechanism: %{handled: true} } @@ -1518,7 +1564,7 @@ defmodule PostHog.HandlerTest do should: "work", "$exception_list": [ %{ - type: "Error with metadata", + type: "Logger.error", value: "Error with metadata", mechanism: %{handled: true} } @@ -1607,7 +1653,7 @@ defmodule PostHog.HandlerTest do foo: "bar", "$exception_list": [ %{ - type: "Error with metadata", + type: "Logger.error", value: "Error with metadata", mechanism: %{handled: true, type: "generic"} }