Packages

Zero-config APM SDK for Elixir — Phoenix, Plug, Ecto. Add the dep and set METRIXWIRE_KEY; every request/query/HTTP call is traced automatically.

Current section

Files

Jump to
metrixwire lib metrixwire instrument source_location.ex
Raw

lib/metrixwire/instrument/source_location.ex

defmodule MetrixWire.Instrument.SourceLocation do
@moduledoc """
Best-effort `file:line` of the application frame that issued a span, so the
dashboard can point at the line that ran a query / HTTP call.
Walks the current process stacktrace and returns the first frame that belongs to
the host application — skipping the SDK itself, the standard library, and known
instrumentation/driver dependencies (Ecto, Phoenix, Plug, Finch, Tesla, Postgrex,
DBConnection, …). Returns nil rather than throwing.
"""
# Skip frames whose source file lives in the SDK, a dependency, or a known
# framework/driver. Matches both absolute (`/deps/...`) and dep-relative paths.
@skip_file ~r{(metrixwire/|/deps/|telemetry|/ecto|/phoenix|/plug|/finch|/tesla|db_connection|postgrex|bandit|thousand_island|cowboy)}
# Modules that are never application code. Their frames are always skipped.
@skip_prefixes ~w(
Elixir.Process Elixir.GenServer Elixir.Task Elixir.Enum Elixir.Stream
Elixir.Kernel Elixir.Ecto Elixir.Phoenix Elixir.Plug Elixir.Finch
Elixir.Tesla Elixir.Postgrex Elixir.DBConnection Elixir.Bandit
Elixir.MetrixWire Elixir.ThousandIsland
)
@doc "The nearest application caller frame as \"file.ex:42\", or nil."
def nearest do
if MetrixWire.capture_source?() do
{:current_stacktrace, stack} = Process.info(self(), :current_stacktrace)
find_frame(stack)
end
rescue
_ -> nil
end
defp find_frame(stack) do
Enum.find_value(stack, fn
{mod, _fun, _arity, meta} ->
file = meta[:file]
line = meta[:line]
cond do
is_nil(file) or is_nil(line) -> nil
skip_module?(mod) -> nil
stdlib_file?(to_string(file)) -> nil
Regex.match?(@skip_file, to_string(file)) -> nil
true -> "#{Path.basename(to_string(file))}:#{line}"
end
_ ->
nil
end)
rescue
_ -> nil
end
defp skip_module?(mod) do
name = Atom.to_string(mod)
# Erlang modules (`:elixir_compiler`, `:gen_server`, …) are never app code.
not String.starts_with?(name, "Elixir.") or
Enum.any?(@skip_prefixes, &String.starts_with?(name, &1))
rescue
_ -> false
end
# Elixir/Erlang standard-library sources are stored as bare relative paths like
# "lib/process.ex" or "src/elixir_compiler.erl" (no leading directory). Real app
# frames carry a directory (e.g. "lib/native/scenarios.ex").
defp stdlib_file?(file) do
Regex.match?(~r{^lib/[^/]+\.ex$}, file) or Regex.match?(~r{^src/[^/]+\.erl$}, file)
rescue
_ -> false
end
end