Packages
timber
2.3.3
3.1.2
3.1.1
3.1.0
3.0.0
3.0.0-alpha.3
3.0.0-alpha.2
3.0.0-alpha.1
2.8.4
2.8.3
2.8.2
2.8.1
2.8.0
2.7.0
2.6.1
2.6.0
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.4
2.3.3
2.3.1
2.3.0
2.2.1
2.2.0
2.1.8
2.1.7
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.2
2.0.1
2.0.0
2.0.0-rc7
2.0.0-rc6
2.0.0-rc5
2.0.0-rc4
2.0.0-rc3
2.0.0-rc2
2.0.0-rc1
1.1.18
1.1.17
1.1.16
1.1.15
1.1.14
1.1.13
1.1.12
1.1.11
1.1.10
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
🌲 Great Elixir Logging Made Easy. Official Timber.io Integration.
Current section
Files
Jump to
Current section
Files
lib/timber/events/error_event.ex
defmodule Timber.Events.ErrorEvent do
@moduledoc """
The `ErrorEvent` is used to track errors and exceptions.
Timber automatically tracks and structures errors and exceptions in your application. Giving
you detailed stack traces, context, and error data.
"""
@type stacktrace_entry :: {
module,
atom,
arity,
[file: IO.chardata, line: non_neg_integer] | []
}
@type backtrace_entry :: %{
app_name: String.t | nil,
function: String.t,
file: String.t | nil,
line: non_neg_integer | nil
}
@type t :: %__MODULE__{
backtrace: [backtrace_entry] | [],
name: String.t,
message: String.t,
}
@enforce_keys [:name, :message]
defstruct [:backtrace, :name, :message]
@app_name_byte_limit 256
@file_byte_limit 1_024
@function_byte_limit 256
@message_byte_limit 8_192
@name_byte_limit 256
@doc """
Builds a new struct taking care to normalize data into a valid state. This should
be used, where possible, instead of creating the struct directly.
"""
@spec new(String.t) :: {:ok, t} | {:error, atom}
def new(log_message) do
lines =
log_message
|> String.split("\n")
|> Enum.map(&({&1, String.trim(&1)}))
case do_new({nil, "", []}, lines) do
{:ok, {name, message, backtrace}} when is_binary(name) and length(backtrace) > 0 ->
name =
name
|> Timber.Utils.Logger.truncate_bytes(@name_byte_limit)
|> to_string()
message =
message
|> Timber.Utils.Logger.truncate_bytes(@message_byte_limit)
|> to_string()
backtrace = Enum.slice(backtrace, 0..9)
{:ok, %__MODULE__{name: name, message: message, backtrace: backtrace}}
_ ->
{:error, :could_not_parse_message}
end
end
# ** (exit) an exception was raised:
defp do_new({nil, message, [] = backtrace}, [{_raw_line, ("** (exit) " <> _suffix)} | lines]) do
do_new({nil, message, backtrace}, lines)
end
# ** (RuntimeError) my message
defp do_new({nil, _message, [] = backtrace}, [{_raw_line, ("** (" <> line_suffix)} | lines]) do
# Using split since it is more performance with binary scanning
case String.split(line_suffix, ")", parts: 2) do
[name, message] ->
do_new({name, message, backtrace}, lines)
_ -> {:error, :malformed_error_message}
end
end
# Ignore other leading messages
defp do_new({nil, _message, _backtrace} = acc, [_line | lines]), do: do_new(acc, lines)
# (odin_client_api) web/controllers/page_controller.ex:5: Odin.ClientAPI.PageController.index/2
defp do_new({name, message, backtrace}, [{_raw_line, ("(" <> line_suffix)} | lines]) when not is_nil(name) and not is_nil(message) do
# Using split since it is more performance with binary scanning
with [app_name, line_suffix] <- String.split(line_suffix, ") ", parts: 2),
[file, line_suffix] <- String.split(line_suffix, ":", parts: 2),
[line_number, function] <- String.split(line_suffix, ":", parts: 2)
do
app_name =
app_name
|> Timber.Utils.Logger.truncate_bytes(@app_name_byte_limit)
|> to_string()
function =
function
|> String.trim()
|> Timber.Utils.Logger.truncate_bytes(@function_byte_limit)
|> to_string()
file =
file
|> String.trim()
|> Timber.Utils.Logger.truncate_bytes(@file_byte_limit)
|> to_string()
if function != "" && file != "" do
line = %{
app_name: app_name,
function: String.trim(function),
file: String.trim(file),
line: parse_line_number(line_number)
}
do_new({name, message, [line | backtrace]}, lines)
else
{:error, :malformed_stacktrace_line}
end
else
_ ->
{:error, :malformed_stacktrace_line}
end
end
# Ignore lines we don't recognize.
defp do_new(acc, [_line | lines]), do: do_new(acc, lines)
# Finish the iteration, reversing the backtrace for performance reasons.
defp do_new({name, message, backtrace}, []) do
{:ok, {name, String.trim(message), Enum.reverse(backtrace)}}
end
defp parse_line_number(line_str) do
case Integer.parse(line_str) do
{line, _unit} -> line
:error -> nil
end
end
@doc """
Message to be used when logging.
"""
@spec message(t) :: IO.chardata
def message(%__MODULE__{name: name, message: message}), do: [?(, name, ?), ?\s, message]
end