Пару раз в день, просматривая трассировку стека ошибок в моем приложении Phoenix, я замечаю, что некоторые исполняемые файлы не отображаются в трассировке стека.
Пример 1. Помощник видаметод опущен
Вот один пример использования помощника format_date
, вызванного из моего шаблона.Код:
# The template, `...web/templates/home/index.html.eex`:
<h1>My Super Old Company</h1>
<p>We've been operating since <%= format_date(Timex.now()) %>!!</p>
# The `format_date` helper lives in the corresponding View module:
def format_date(date) do
template = "%b %d, %Y"
method = :strftime
# Whoops, it looks like I passed a bad value to Timex here
Timex.format!(nil, template, method)
end
Попытка визуализации этой страницы приводит к исключению:
Request: GET /
** (exit) an exception was raised:
** (ArgumentError) invalid_date
(timex) lib/format/datetime/formatter.ex:60: Timex.Format.DateTime.Formatter.lformat!/4
(rtl) lib/rtl_web/templates/home/index.html.eex:3: RTLWeb.HomeView."index.html"/1
(rtl) lib/rtl_web/templates/layout/app.html.eex:27: RTLWeb.LayoutView."app.html"/1
...
Понятно, что Timex.format!/3
не нравится nil
и он взрывается.Но подождите, почему мой помощник format_date/1
не упоминается в трассировке стека?В конце концов, этот помощник, где ошибка.И код определенно проходит через эту функцию;если я изменю nil
, чтобы передать date
var, ошибка исчезнет.
Я мог бы взглянуть на код шаблона, чтобы сделать вывод, что этот конкретный помощник был вызван, но я не могу сказать, какая строкаошибка была включена.Если функция длинная или включает несколько вызовов Timex, я могу легко запутаться в том, какой конкретный вызов произошел с ошибкой.
Пример 2: функция контекста опущена
Вот еще один пример.В этом случае мой контроллер вызывает контекстный помощник для извлечения записи Ecto.
# The controller action:
def index(conn, _params) do
# Whoops, looks like I hard-coded the project id by accident
project = MyContext.get_project!(42)
render(conn, "index.html", project: project)
end
# The MyContext context defines `get_project!/1` to fetch that id from the repo:
def get_project!(id) do
Repo.get!(Project, id)
end
Как и ожидалось, когда я запускаю тест моего контроллера, я получаю Ecto.NoResultsError
, потому что нет проекта с таким идентификатором.Но опять же, я замечаю, что вызов MyContext.get_project!/1
не включен в трассировку стека:
** (Ecto.NoResultsError) expected at least one result but got none in query:
from p in RTL.Projects.Project,
where: p.id == ^42
code: conn = get(conn, Routes.home_path(conn, :index))
stacktrace:
(ecto) lib/ecto/repo/queryable.ex:80: Ecto.Repo.Queryable.one!/4
(rtl) lib/rtl_web/controllers/home_controller.ex:5: RTLWeb.HomeController.index/2
(rtl) lib/rtl_web/controllers/home_controller.ex:1: RTLWeb.HomeController.action/2
(rtl) lib/rtl_web/controllers/home_controller.ex:1: RTLWeb.HomeController.phoenix_controller_pipeline/2
...
Еще более странно, я замечаю, что если я добавлю оператор в конце get_project/1
, как это, трассировка стека меняется , чтобы включить эту строку.
# When I add this nil at the tail...
def get_project!(id) do
Repo.get!(Project, id)
nil
end
# The stacktrace includes the function call.
...
(ecto) lib/ecto/repo/queryable.ex:80: Ecto.Repo.Queryable.one!/4
(rtl) lib/rtl/projects/projects.ex:12: RTL.Projects.get_project!/1
(rtl) lib/rtl_web/controllers/home_controller.ex:5: RTLWeb.HomeController.index/2
...
Но если я переместу этот ноль в начало функции, строка снова исчезнет из трассировки стека.
# When I move the nil to above the Repo.get! call...
def get_project!(id) do
nil
Repo.get!(Project, id)
end
# The stacktrace excludes the function call.
...
(ecto) lib/ecto/repo/queryable.ex:80: Ecto.Repo.Queryable.one!/4
(rtl) lib/rtl_web/controllers/home_controller.ex:5: RTLWeb.HomeController.index/2
...
Пример 3: тестовые помощники утверждений опущены
Вот тест контроллера, который вызывает помощника подтверждения, который вызывает другого помощника, который вызывает другого помощника, который, в конце концов, проваливает утверждение.Но в моей трассировке стека ошибок я не вижу никаких признаков того, что эти помощники вызваны;Я получаю только строку самого высокого уровня в моем тестовом блоке.
# The test example and helpers:
test "#index renders the page", %{conn: conn} do
conn = get(conn, Routes.home_path(conn, :index))
assert_foo("foo")
assert html_response(conn, 200) =~ "bring personal storytelling back"
end
defp assert_foo(object) do
assert_bar(object)
end
defp assert_bar(object) do
assert_baz(object)
end
defp assert_baz(object) do
learn_secret_of_life(object)
end
defp learn_secret_of_life(object) do
assert object == "elixir"
end
# The test failure message:
Assertion with == failed
code: assert object == "elixir"
left: "foo"
right: "elixir"
stacktrace:
test/rtl_web/controllers/home_controller_test.exs:6: (test)
Если я вызову исключение в sub-sub-sub-helper, а не просто сделаю обреченное утверждение, трассировка стекаупоминает последний вспомогательный метод, но продолжает опускать промежуточные:
# Modified helper function:
defp learn_secret_of_life(object) do
assert object == raise("OMG")
end
# Now the stacktrace includes this helper, but not any of the chain of helpers that lead to its invocation:
** (RuntimeError) OMG
code: assert_foo("foo")
stacktrace:
test/rtl_web/controllers/home_controller_test.exs:23: RTLWeb.HomeControllerTest.learn_secret_of_life/1
test/rtl_web/controllers/home_controller_test.exs:6: (test)
Очевидно, что в этом надуманном примере цепочку вызовов можно легко отследить.Но в реальном приложении, которое я недавно устранял, мне, возможно, нужно было переключиться между несколькими различными модулями, чтобы понять, где вызвано исключение, что немного напрягает, когда я не могу рассчитывать на каждую из этих цепочекпоказать в сообщении трассировки стека.Наверное, я думал, что задача трассировки стека состояла в том, чтобы перечислить все файлы и строки кода, которые выполнялись в момент возникновения исключения.Поэтому меня очень смущает поведение, показанное выше.
Мои вопросы:
- Почему Elixir иногда пропускает определенные файлы / строки из трассировки стека?
- есть ли способ сказать это, пожалуйста, не делайте этого?
- Это меня смущает только я?