defmodule Mix.Tasks.Sentry.PackageSourceCode do
@shortdoc "Packages source code for Sentry to use when reporting errors"
@moduledoc """
Packages source code for Sentry to use when reporting errors.
This task should be used in production settings, before building a release of your
application. It packages all the source code of your application in a single file
(called `sentry.map`), which is optimized for fast retrieval of source code lines.
Sentry then uses this to report source code context. See the documentation for the
`Sentry` module for configuration options related to the source code context.
*This task is available since v10.0.0 of this library*.
## Usage
```shell
mix sentry.package_source_code
```
### Using in Production
In production settings, call this task before building a release. This way, the source
code packaged by this task will be included in the release.
For example, in a release script (this could also be in a `Dockerfile`, if you're using
Docker):
```shell
# ...
mix sentry.package_source_code
mix release
```
## Options
* `--debug` - print more information about collecting and encoding source code
"""
@moduledoc since: "10.0.0"
use Mix.Task
alias Sentry.Sources
@bytes_in_kb 1024
@bytes_in_mb 1024 * 1024
@bytes_in_gb 1024 * 1024 * 1024
@switches [debug: :boolean]
@requirements ["app.config"]
@impl true
def run(args) do
{opts, _args} = OptionParser.parse!(args, strict: @switches)
{elapsed, source_map} =
:timer.tc(fn ->
case Sources.load_files() do
{:ok, source_map} -> source_map
{:error, message} -> Mix.raise(message)
end
end)
log_debug(
opts,
"Loaded source code map with #{map_size(source_map)} files in #{format_time(elapsed)}"
)
{elapsed, contents} = :timer.tc(fn -> Sources.encode_source_code_map(source_map) end)
log_debug(opts, "Encoded source code map in #{format_time(elapsed)}")
output_path = Sources.path_of_packaged_source_code()
File.mkdir_p!(Path.dirname(output_path))
File.write!(output_path, contents)
Mix.shell().info([
"Wrote ",
:cyan,
format_bytes(byte_size(contents)),
:reset,
" to: #{Path.relative_to_cwd(output_path)}"
])
end
## Helpers
defp log_debug(opts, str) do
if opts[:debug] do
Mix.shell().info([:magenta, str, :reset])
end
end
defp format_bytes(n) when n < @bytes_in_kb, do: "#{n} bytes"
defp format_bytes(n) when n < @bytes_in_mb, do: "#{Float.round(n / @bytes_in_kb, 2)} kb"
defp format_bytes(n) when n < @bytes_in_gb, do: "#{Float.round(n / @bytes_in_mb, 2)} Mb"
defp format_time(n) when n < 1000, do: "#{n} µs"
defp format_time(n) when n < 1_000_000, do: "#{div(n, 1000)} ms"
defp format_time(n), do: "#{Float.round(n / 1_000_000, 2)} s"
end