Packages
sentry
4.0.1
13.3.0
13.2.0
13.1.0
13.0.1
13.0.0
12.0.3
12.0.2
12.0.1
12.0.0
11.0.4
11.0.3
11.0.2
11.0.1
11.0.0
10.10.0
10.9.0
10.8.1
10.8.0
10.7.1
10.7.0
10.6.2
10.6.1
10.6.0
10.5.0
10.4.0
10.3.0
10.2.1
10.2.0
10.2.0-rc.2
10.2.0-rc.1
10.1.0
10.0.3
10.0.2
10.0.1
10.0.0
9.1.0
9.0.0
8.1.0
8.0.6
8.0.5
8.0.4
8.0.3
8.0.2
8.0.1
8.0.0
8.0.0-rc.2
8.0.0-rc.1
8.0.0-rc.0
retired
7.2.5
7.2.4
7.2.3
7.2.2
7.2.1
7.2.0
7.1.0
7.0.6
7.0.5
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.4.2
6.4.1
6.4.0
6.3.0
6.2.1
6.2.0
6.1.0
6.0.5
6.0.4
6.0.3
6.0.2
6.0.1
6.0.0
5.0.1
5.0.0
4.0.3
4.0.2
4.0.1
4.0.0
3.0.0
2.2.0
2.1.0
2.0.2
2.0.1
2.0.0
1.1.2
1.1.1
1.1.0
1.0.0
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
The Official Elixir client for Sentry
Current section
Files
Jump to
Current section
Files
lib/sentry/sources.ex
defmodule Sentry.Sources do
@moduledoc """
This module is responsible for providing functionality that stores
the text of source files during compilation for displaying the
source code that caused an exception.
### Configuration
There is configuration required to set up this functionality. The options
include `:enable_source_code_context`, `:root_source_code_path`, `:context_lines`,
`:source_code_exclude_patterns`, and `:source_code_path_pattern`.
* `:enable_source_code_context` - when `true`, enables reporting source code
alongside exceptions.
* `:root_source_code_path` - The path from which to start recursively reading files from.
Should usually be set to `File.cwd!`.
* `:context_lines` - The number of lines of source code before and after the line that
caused the exception to be included. Defaults to `3`.
* `:source_code_exclude_patterns` - a list of Regex expressions used to exclude file paths that
should not be stored or referenced when reporting exceptions. Defaults to
`[~r"/_build/", ~r"/deps/", ~r"/priv/"]`.
* `:source_code_path_pattern` - a glob that is expanded to select files from the
`:root_source_code_path`. Defaults to `"**/*.ex"`.
An example configuration:
config :sentry,
dsn: "https://public:secret@app.getsentry.com/1",
enable_source_code_context: true,
root_source_code_path: File.cwd!,
context_lines: 5
### Source code storage
The file contents are saved when Sentry is compiled, which can cause some
complications. If a file is changed, and Sentry is not recompiled,
it will still report old source code.
The best way to ensure source code is up to date is to recompile Sentry
itself via `mix deps.clean sentry, compile`. It's possible to create a Mix
Task alias in `mix.exs` to do this. The example below would allow one to
run `mix.sentry_recompile` which will force recompilation of Sentry so
it has the newest source and then compile the project:
defp aliases do
[sentry_recompile: ["deps.compile sentry --force", "compile"]]
end
"""
@type file_map :: %{pos_integer() => String.t}
@type source_map :: %{String.t => file_map}
@default_exclude_patterns [~r"/_build/", ~r"/deps/", ~r"/priv/"]
@default_path_pattern "**/*.ex"
@default_context_lines 3
def load_files do
root_path = Application.fetch_env!(:sentry, :root_source_code_path)
path_pattern = Application.get_env(:sentry, :source_code_path_pattern, @default_path_pattern)
exclude_patterns = Application.get_env(:sentry, :source_code_exclude_patterns, @default_exclude_patterns)
Path.join(root_path, path_pattern)
|> Path.wildcard()
|> exclude_files(exclude_patterns)
|> Enum.reduce(%{}, fn(path, acc) ->
key = Path.relative_to(path, root_path)
value = source_to_lines(File.read!(path))
Map.put(acc, key, value)
end)
end
@doc """
Given the source code map, a filename and a line number, this method retrieves the source code context.
When reporting source code context to the Sentry API, it expects three separate values. They are the source code
for the specific line the error occurred on, the list of the source code for the lines preceding, and the
list of the source code for the lines following. The number of lines in the lists depends on what is
configured in `:context_lines`. The number configured is how many lines to get on each side of the line that
caused the error. If it is configured to be `3`, the method will attempt to get the 3 lines preceding, the
3 lines following, and the line that the error occurred on, for a possible maximum of 7 lines.
The three values are returned in a three element tuple as `{preceding_source_code_list, source_code_from_error_line, following_source_code_list}`.
"""
@spec get_source_context(source_map, String.t, pos_integer()) :: {[String.t], String.t | nil, [String.t]}
def get_source_context(files, file_name, line_number) do
context_lines = Application.get_env(:sentry, :context_lines, @default_context_lines)
file = Map.get(files, file_name)
do_get_source_context(file, line_number, context_lines)
end
defp do_get_source_context(nil, _, _), do: {[], nil, []}
defp do_get_source_context(file, line_number, context_lines) do
context_line_indices = 0..(2 * context_lines)
Enum.reduce(context_line_indices, {[], nil, []}, fn(i, {pre_context, context, post_context}) ->
context_line_number = line_number - context_lines + i
source = Map.get(file, context_line_number)
cond do
context_line_number == line_number && source ->
{pre_context, source, post_context}
context_line_number < line_number && source ->
{pre_context ++ [source], context, post_context}
context_line_number > line_number && source ->
{pre_context, context, post_context ++ [source]}
true ->
{pre_context, context, post_context}
end
end)
end
defp exclude_files(file_names, []), do: file_names
defp exclude_files(file_names, [exclude_pattern | rest]) do
Enum.reject(file_names, &(String.match?(&1, exclude_pattern)))
|> exclude_files(rest)
end
defp source_to_lines(source) do
String.replace_suffix(source, "\n", "")
|> String.split("\n")
|> Enum.with_index
|> Enum.reduce(%{}, fn({line_string, line_number}, acc) ->
Map.put(acc, line_number + 1, line_string)
end)
end
end