From 0b915941b13d9fef02352d7d6d7f56e6bcc2da09 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 19 Mar 2025 14:20:57 -0400 Subject: [PATCH 1/9] improvement: add igniter installer with this change, the installation instructions can be as simple as `mix igniter.install sentry --dsn "your_dsn"` For the release task change, a notice is emitted instructing the user to add the relevant task to their release process. --- lib/mix/tasks/sentry.install.ex | 188 +++++++++++++++++++++++++++++++ mix.exs | 1 + mix.lock | 12 ++ test/mix/sentry.install_test.exs | 28 +++++ 4 files changed, 229 insertions(+) create mode 100644 lib/mix/tasks/sentry.install.ex create mode 100644 test/mix/sentry.install_test.exs diff --git a/lib/mix/tasks/sentry.install.ex b/lib/mix/tasks/sentry.install.ex new file mode 100644 index 00000000..60207838 --- /dev/null +++ b/lib/mix/tasks/sentry.install.ex @@ -0,0 +1,188 @@ +defmodule Mix.Tasks.Sentry.Install.Docs do + @moduledoc false + + def short_doc do + "Installs sentry. Requires igniter to be installed." + end + + def example do + "mix sentry.install --dsn " + end + + def long_doc do + """ + #{short_doc()} + + ## Example + + ```bash + #{example()} + ``` + + ## Options + + * `--dsn` - Your sentry `dsn` + """ + end +end + +if Code.ensure_loaded?(Igniter) do + defmodule Mix.Tasks.Sentry.Install do + @shortdoc "#{__MODULE__.Docs.short_doc()}" + + @moduledoc __MODULE__.Docs.long_doc() + + use Igniter.Mix.Task + + @impl Igniter.Mix.Task + def info(_argv, _composing_task) do + %Igniter.Mix.Task.Info{ + group: :sentry, + adds_deps: [{:jason, "~> 1.2"}, {:hackney, "~> 1.8"}], + example: __MODULE__.Docs.example(), + schema: [dsn: :string], + # Default values for the options in the `schema` + defaults: [dsn: ""] + } + end + + @impl Igniter.Mix.Task + def igniter(igniter) do + app_name = Igniter.Project.Application.app_name(igniter) + + mix_env_code = + quote do + Mix.env() + end + + cwd_code = + quote do + [File.cwd!()] + end + + # Do your work here and return an updated igniter + igniter + |> Igniter.Project.Config.configure( + "prod.exs", + app_name, + [:dsn], + igniter.args.options[:dsn] + ) + |> Igniter.Project.Config.configure( + "prod.exs", + app_name, + [:environment_name], + {:code, mix_env_code} + ) + |> Igniter.Project.Config.configure( + "prod.exs", + app_name, + [:enable_source_code_context], + true + ) + |> Igniter.Project.Config.configure( + "prod.exs", + app_name, + [:root_source_code_paths], + {:code, cwd_code} + ) + |> configure_phoenix() + |> add_logger_handler() + |> Igniter.add_notice(""" + Sentry: + + Add a call to mix sentry.package_source_code in your release script to + make sure the stacktraces you receive are complete. + """) + end + + defp add_logger_handler(igniter) do + app_module = Igniter.Project.Application.app_module(igniter) + + Igniter.Project.Module.find_and_update_module(igniter, app_module, fn zipper -> + with {:ok, zipper} <- Igniter.Code.Function.move_to_def(zipper, :start, 2), + {:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper) do + code = + """ + :logger.add_handler(:my_sentry_handler, Sentry.LoggerHandler, %{ + config: %{metadata: [:file, :line]} + }) + """ + + {:ok, Igniter.Code.Common.add_code(zipper, code, placement: :prepend)} + end + end) + |> case do + {:ok, igniter} -> igniter + _ -> igniter + end + end + + defp configure_phoenix(igniter) do + {igniter, routers} = + Igniter.Libs.Phoenix.list_routers(igniter) + + {igniter, endpoints} = + Enum.reduce(routers, {igniter, []}, fn router, {igniter, endpoints} -> + {igniter, new_endpoints} = Igniter.Libs.Phoenix.endpoints_for_router(igniter, router) + {igniter, endpoints ++ new_endpoints} + end) + + Enum.reduce(endpoints, igniter, fn endpoint, igniter -> + igniter + |> setup_endpoint(endpoint) + end) + end + + defp setup_endpoint(igniter, endpoint) do + Igniter.Project.Module.find_and_update_module!(igniter, endpoint, fn zipper -> + zipper + |> Igniter.Code.Common.within(&add_plug_capture/1) + |> Igniter.Code.Common.within(&add_plug_context/1) + |> then(&{:ok, &1}) + end) + end + + defp add_plug_capture(zipper) do + with {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, Phoenix.Endpoint) do + Igniter.Code.Common.add_code(zipper, "use Sentry.PlugCapture", placement: :before) + else + _ -> + {:ok, zipper} + end + end + + defp add_plug_context(zipper) do + with {:ok, zipper} <- + Igniter.Code.Function.move_to_function_call_in_current_scope( + zipper, + :plug, + [2, 1], + &Igniter.Code.Function.argument_equals?(&1, 0, Plug.Parsers) + ) do + Igniter.Code.Common.add_code(zipper, "use Sentry.PlugContext", placement: :after) + else + _ -> + {:ok, zipper} + end + end + end +else + defmodule Mix.Tasks.Sentry.Install do + @shortdoc "#{__MODULE__.Docs.short_doc()} | Install `igniter` to use" + + @moduledoc __MODULE__.Docs.long_doc() + + use Mix.Task + + def run(_argv) do + Mix.shell().error(""" + The task 'sentry.install' requires igniter. Please install igniter and try again. + + For more information, see: https://hexdocs.pm/igniter/readme.html#installation + """) + + exit({:shutdown, 1}) + end + end +end diff --git a/mix.exs b/mix.exs index aa2ab7fe..e429e0c0 100644 --- a/mix.exs +++ b/mix.exs @@ -100,6 +100,7 @@ defmodule Sentry.Mixfile do {:phoenix_live_view, "~> 0.20 or ~> 1.0", optional: true}, {:plug, "~> 1.6", optional: true}, {:telemetry, "~> 0.4 or ~> 1.0", optional: true}, + {:igniter, "~> 0.5", optional: true}, # Dev and test dependencies {:plug_cowboy, "~> 2.7", only: [:test]}, diff --git a/mix.lock b/mix.lock index 4d7db4c2..4ea9267f 100644 --- a/mix.lock +++ b/mix.lock @@ -16,11 +16,15 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, "excoveralls": {:hex, :excoveralls, "0.17.1", "83fa7906ef23aa7fc8ad7ee469c357a63b1b3d55dd701ff5b9ce1f72442b2874", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "95bc6fda953e84c60f14da4a198880336205464e75383ec0f570180567985ae0"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, + "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hpax": {:hex, :hpax, "1.0.1", "c857057f89e8bd71d97d9042e009df2a42705d6d690d54eca84c8b29af0787b0", [:mix], [], "hexpm", "4e2d5a4f76ae1e3048f35ae7adb1641c36265510a2d4638157fbcb53dda38445"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "igniter": {:hex, :igniter, "0.5.37", "e677c009fcad535759dfb8fec0214af38acf1f27b7d6d0277d6be61d6bb391e8", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ca4e5f7c1edead2c6b8955886b010878660c69f3a3f97bf46641b3c39ed339ce"}, + "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, @@ -28,10 +32,13 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_ownership": {:hex, :nimble_ownership, "1.0.0", "3f87744d42c21b2042a0aa1d48c83c77e6dd9dd357e425a038dd4b49ba8b79a1", [:mix], [], "hexpm", "7c16cc74f4e952464220a73055b557a273e8b1b7ace8489ec9d86e9ad56cb2cc"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "oban": {:hex, :oban, "2.18.3", "1608c04f8856c108555c379f2f56bc0759149d35fa9d3b825cb8a6769f8ae926", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36ca6ca84ef6518f9c2c759ea88efd438a3c81d667ba23b02b062a0aa785475e"}, + "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.17", "2fcdceecc6fb90bec26fab008f96abbd0fd93bc9956ec7985e5892cf545152ca", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "50e8ad537f3f7b0efb1509b2f75b5c918f697be6a45d48e49a30d3b7c0e464c9"}, "phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"}, @@ -43,9 +50,14 @@ "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "quantum": {:hex, :quantum, "3.5.3", "ee38838a07761663468145f489ad93e16a79440bebd7c0f90dc1ec9850776d99", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.14 or ~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.2", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "500fd3fa77dcd723ed9f766d4a175b684919ff7b6b8cfd9d7d0564d58eba8734"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "req": {:hex, :req, "0.5.9", "09072dcd91a70c58734c4dd4fa878a9b6d36527291152885100ec33a5a07f1d6", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2f027043003275918f5e79e6a4e57b10cb17161a1ab41c959aa40ecfb2142e5a"}, + "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, + "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"}, + "spitfire": {:hex, :spitfire, "0.2.0", "0de1f519a23f65bde40d316adad53c07a9563f25cc68915d639d8a509a0aad8a", [:mix], [], "hexpm", "743daaee2d81a0d8095431729f478ce49b47ea8943c7d770de86704975cb7775"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_registry": {:hex, :telemetry_registry, "0.3.2", "701576890320be6428189bff963e865e8f23e0ff3615eade8f78662be0fc003c", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7ed191eb1d115a3034af8e1e35e4e63d5348851d556646d46ca3d1b4e16bab9"}, + "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "thousand_island": {:hex, :thousand_island, "1.3.7", "1da7598c0f4f5f50562c097a3f8af308ded48cd35139f0e6f17d9443e4d0c9c5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0139335079953de41d381a6134d8b618d53d084f558c734f2662d1a72818dd12"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, diff --git a/test/mix/sentry.install_test.exs b/test/mix/sentry.install_test.exs new file mode 100644 index 00000000..09f212df --- /dev/null +++ b/test/mix/sentry.install_test.exs @@ -0,0 +1,28 @@ +defmodule Mix.Tasks.Sentry.InstallTest do + use Sentry.Case, async: false + + import Igniter.Test + + test "installation adds jason and hackney dependencies" do + phx_test_project() + |> Igniter.compose_task("sentry.install", ["--dsn", "test_dsn"]) + |> assert_has_patch("config/prod.exs", """ + + |config :test, + + | dsn: "test_dsn", + + | environment_name: Mix.env(), + + | enable_source_code_context: true, + + | root_source_code_paths: [File.cwd!()] + """) + |> assert_has_patch("lib/test_web/endpoint.ex", """ + + | use Sentry.PlugCapture + """) + |> assert_has_patch("lib/test_web/endpoint.ex", """ + + | use Sentry.PlugContext + """) + |> assert_has_patch("lib/test/application.ex", """ + + | :logger.add_handler(:my_sentry_handler, Sentry.LoggerHandler, %{ + + | config: %{metadata: [:file, :line]} + + | }) + """) + end +end From dc041a5b96d09f845fa0290290b326f617ea79b1 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 19 Mar 2025 19:57:36 -0400 Subject: [PATCH 2/9] fix: use `plug`, not `use` --- lib/mix/tasks/sentry.install.ex | 2 +- test/mix/sentry.install_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/sentry.install.ex b/lib/mix/tasks/sentry.install.ex index 60207838..34101208 100644 --- a/lib/mix/tasks/sentry.install.ex +++ b/lib/mix/tasks/sentry.install.ex @@ -160,7 +160,7 @@ if Code.ensure_loaded?(Igniter) do [2, 1], &Igniter.Code.Function.argument_equals?(&1, 0, Plug.Parsers) ) do - Igniter.Code.Common.add_code(zipper, "use Sentry.PlugContext", placement: :after) + Igniter.Code.Common.add_code(zipper, "plug Sentry.PlugContext", placement: :after) else _ -> {:ok, zipper} diff --git a/test/mix/sentry.install_test.exs b/test/mix/sentry.install_test.exs index 09f212df..5b1a771b 100644 --- a/test/mix/sentry.install_test.exs +++ b/test/mix/sentry.install_test.exs @@ -17,7 +17,7 @@ defmodule Mix.Tasks.Sentry.InstallTest do + | use Sentry.PlugCapture """) |> assert_has_patch("lib/test_web/endpoint.ex", """ - + | use Sentry.PlugContext + + | plug Sentry.PlugContext """) |> assert_has_patch("lib/test/application.ex", """ + | :logger.add_handler(:my_sentry_handler, Sentry.LoggerHandler, %{ From 9aa274cb55ce809df627b31a0cf65e74736d3d83 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 08:19:10 -0400 Subject: [PATCH 3/9] Update lib/mix/tasks/sentry.install.ex Co-authored-by: Andrea Leopardi --- lib/mix/tasks/sentry.install.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/sentry.install.ex b/lib/mix/tasks/sentry.install.ex index 34101208..0eefc8be 100644 --- a/lib/mix/tasks/sentry.install.ex +++ b/lib/mix/tasks/sentry.install.ex @@ -104,7 +104,7 @@ if Code.ensure_loaded?(Igniter) do {:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper) do code = """ - :logger.add_handler(:my_sentry_handler, Sentry.LoggerHandler, %{ + :logger.add_handler(:sentry_handler, Sentry.LoggerHandler, %{ config: %{metadata: [:file, :line]} }) """ From 3f9bbea3391ffe30344eac77f817024c93705962 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 08:19:28 -0400 Subject: [PATCH 4/9] Update lib/mix/tasks/sentry.install.ex Co-authored-by: Andrea Leopardi --- lib/mix/tasks/sentry.install.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/sentry.install.ex b/lib/mix/tasks/sentry.install.ex index 0eefc8be..78ea3405 100644 --- a/lib/mix/tasks/sentry.install.ex +++ b/lib/mix/tasks/sentry.install.ex @@ -91,8 +91,12 @@ if Code.ensure_loaded?(Igniter) do |> Igniter.add_notice(""" Sentry: - Add a call to mix sentry.package_source_code in your release script to - make sure the stacktraces you receive are complete. + Add a call to + + mix sentry.package_source_code + + in your release script to make sure the stacktraces you receive + are correctly categorized. """) end From 6200ea3cab27122827260455dc6dc9abb36383c0 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 08:19:34 -0400 Subject: [PATCH 5/9] Update lib/mix/tasks/sentry.install.ex Co-authored-by: Andrea Leopardi --- lib/mix/tasks/sentry.install.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/sentry.install.ex b/lib/mix/tasks/sentry.install.ex index 78ea3405..76bfc2a2 100644 --- a/lib/mix/tasks/sentry.install.ex +++ b/lib/mix/tasks/sentry.install.ex @@ -84,7 +84,7 @@ if Code.ensure_loaded?(Igniter) do "prod.exs", app_name, [:root_source_code_paths], - {:code, cwd_code} + {:code, quote(do: [File.cwd!()]} ) |> configure_phoenix() |> add_logger_handler() From ab1172944442c1c7c0b56c72086d06237f8a0664 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 08:19:40 -0400 Subject: [PATCH 6/9] Update lib/mix/tasks/sentry.install.ex Co-authored-by: Andrea Leopardi --- lib/mix/tasks/sentry.install.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/sentry.install.ex b/lib/mix/tasks/sentry.install.ex index 76bfc2a2..b3ed03e9 100644 --- a/lib/mix/tasks/sentry.install.ex +++ b/lib/mix/tasks/sentry.install.ex @@ -72,7 +72,7 @@ if Code.ensure_loaded?(Igniter) do "prod.exs", app_name, [:environment_name], - {:code, mix_env_code} + {:code, quote(do: Mix.env())} ) |> Igniter.Project.Config.configure( "prod.exs", From 96cd88e4323ad3d23094bd4b010284db24b39992 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 08:19:47 -0400 Subject: [PATCH 7/9] Update lib/mix/tasks/sentry.install.ex Co-authored-by: Andrea Leopardi --- lib/mix/tasks/sentry.install.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/sentry.install.ex b/lib/mix/tasks/sentry.install.ex index b3ed03e9..793eba74 100644 --- a/lib/mix/tasks/sentry.install.ex +++ b/lib/mix/tasks/sentry.install.ex @@ -21,7 +21,8 @@ defmodule Mix.Tasks.Sentry.Install.Docs do ## Options - * `--dsn` - Your sentry `dsn` + * `--dsn` - Your Sentry DSN. + """ end end From 06bb4b0f1ade88b2d2c8027256da05f398e6695c Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 09:13:54 -0400 Subject: [PATCH 8/9] PR feedback & ensure installer is idempotent --- lib/mix/tasks/sentry.install.ex | 43 +++++++++++++++++++------------- mix.lock | 2 +- test/mix/sentry.install_test.exs | 21 +++++++++++++++- 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/lib/mix/tasks/sentry.install.ex b/lib/mix/tasks/sentry.install.ex index 793eba74..f9451dbc 100644 --- a/lib/mix/tasks/sentry.install.ex +++ b/lib/mix/tasks/sentry.install.ex @@ -13,6 +13,10 @@ defmodule Mix.Tasks.Sentry.Install.Docs do """ #{short_doc()} + This installer is built with, and requires, igniter to be used. Igniter is a tool + for biulding package installers. For information on how to add igniter to your + project, see the documentation https://hexdocs.pm/igniter/readme.html#installation. + ## Example ```bash @@ -51,16 +55,6 @@ if Code.ensure_loaded?(Igniter) do def igniter(igniter) do app_name = Igniter.Project.Application.app_name(igniter) - mix_env_code = - quote do - Mix.env() - end - - cwd_code = - quote do - [File.cwd!()] - end - # Do your work here and return an updated igniter igniter |> Igniter.Project.Config.configure( @@ -85,7 +79,7 @@ if Code.ensure_loaded?(Igniter) do "prod.exs", app_name, [:root_source_code_paths], - {:code, quote(do: [File.cwd!()]} + {:code, quote(do: [File.cwd!()])} ) |> configure_phoenix() |> add_logger_handler() @@ -106,7 +100,14 @@ if Code.ensure_loaded?(Igniter) do Igniter.Project.Module.find_and_update_module(igniter, app_module, fn zipper -> with {:ok, zipper} <- Igniter.Code.Function.move_to_def(zipper, :start, 2), - {:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper) do + {:ok, zipper} <- Igniter.Code.Common.move_to_do_block(zipper), + :error <- + Igniter.Code.Function.move_to_function_call_in_current_scope( + zipper, + {:logger, :add_handler}, + [2, 3, 4], + &Igniter.Code.Function.argument_equals?(&1, 1, Sentry.LoggerHandler) + ) do code = """ :logger.add_handler(:sentry_handler, Sentry.LoggerHandler, %{ @@ -149,26 +150,34 @@ if Code.ensure_loaded?(Igniter) do end defp add_plug_capture(zipper) do - with {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, Phoenix.Endpoint) do + with :error <- Igniter.Code.Module.move_to_use(zipper, Sentry.PlugCapture), + {:ok, zipper} <- Igniter.Code.Module.move_to_use(zipper, Phoenix.Endpoint) do Igniter.Code.Common.add_code(zipper, "use Sentry.PlugCapture", placement: :before) else _ -> - {:ok, zipper} + zipper end end defp add_plug_context(zipper) do - with {:ok, zipper} <- + with :error <- + Igniter.Code.Function.move_to_function_call_in_current_scope( + zipper, + :plug, + [1, 2], + &Igniter.Code.Function.argument_equals?(&1, 0, Sentry.PlugContext) + ), + {:ok, zipper} <- Igniter.Code.Function.move_to_function_call_in_current_scope( zipper, :plug, - [2, 1], + [1, 2], &Igniter.Code.Function.argument_equals?(&1, 0, Plug.Parsers) ) do Igniter.Code.Common.add_code(zipper, "plug Sentry.PlugContext", placement: :after) else _ -> - {:ok, zipper} + zipper end end end diff --git a/mix.lock b/mix.lock index 4ea9267f..0b8c5168 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hpax": {:hex, :hpax, "1.0.1", "c857057f89e8bd71d97d9042e009df2a42705d6d690d54eca84c8b29af0787b0", [:mix], [], "hexpm", "4e2d5a4f76ae1e3048f35ae7adb1641c36265510a2d4638157fbcb53dda38445"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "igniter": {:hex, :igniter, "0.5.37", "e677c009fcad535759dfb8fec0214af38acf1f27b7d6d0277d6be61d6bb391e8", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "ca4e5f7c1edead2c6b8955886b010878660c69f3a3f97bf46641b3c39ed339ce"}, + "igniter": {:hex, :igniter, "0.5.38", "436a6414abc9245e539d6c92a6f4854f62270fbf6547c4acf1e5a65c0e4f4d4b", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "e5f1474a2a7ad186f3b71074d9d1ef25d306634e12af4ea01beae06ba958491d"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, diff --git a/test/mix/sentry.install_test.exs b/test/mix/sentry.install_test.exs index 5b1a771b..e0b1d97d 100644 --- a/test/mix/sentry.install_test.exs +++ b/test/mix/sentry.install_test.exs @@ -20,9 +20,28 @@ defmodule Mix.Tasks.Sentry.InstallTest do + | plug Sentry.PlugContext """) |> assert_has_patch("lib/test/application.ex", """ - + | :logger.add_handler(:my_sentry_handler, Sentry.LoggerHandler, %{ + + | :logger.add_handler(:sentry_handler, Sentry.LoggerHandler, %{ + | config: %{metadata: [:file, :line]} + | }) """) end + + test "installation is idempotent" do + phx_test_project() + |> Igniter.compose_task("sentry.install", ["--dsn", "test_dsn"]) + |> apply_igniter!() + |> Igniter.compose_task("sentry.install", ["--dsn", "test_dsn"]) + |> assert_unchanged() + end + + test "installation will reset your dsn for you, however" do + phx_test_project() + |> Igniter.compose_task("sentry.install", ["--dsn", "test_dsn"]) + |> apply_igniter!() + |> Igniter.compose_task("sentry.install", ["--dsn", "test_dsn2"]) + |> assert_has_patch("config/prod.exs", """ + - | dsn: "test_dsn", + + | dsn: "test_dsn2", + """) + end end From fa936ffdded866658908ddf559039e62328c7a6b Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 21 Mar 2025 09:22:51 -0400 Subject: [PATCH 9/9] chore: remove the comments added by `igniter.gen.task` --- lib/mix/tasks/sentry.install.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/mix/tasks/sentry.install.ex b/lib/mix/tasks/sentry.install.ex index f9451dbc..9dbcd780 100644 --- a/lib/mix/tasks/sentry.install.ex +++ b/lib/mix/tasks/sentry.install.ex @@ -46,7 +46,6 @@ if Code.ensure_loaded?(Igniter) do adds_deps: [{:jason, "~> 1.2"}, {:hackney, "~> 1.8"}], example: __MODULE__.Docs.example(), schema: [dsn: :string], - # Default values for the options in the `schema` defaults: [dsn: ""] } end @@ -55,7 +54,6 @@ if Code.ensure_loaded?(Igniter) do def igniter(igniter) do app_name = Igniter.Project.Application.app_name(igniter) - # Do your work here and return an updated igniter igniter |> Igniter.Project.Config.configure( "prod.exs",