Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d98af49c9 | ||
|
|
98485daab0 | ||
|
|
59f47949a9 | ||
|
|
fca5f1cf73 | ||
|
|
153ec434de | ||
|
|
061a989935 | ||
|
|
21f10f7296 | ||
|
|
336452f81b |
@@ -63,6 +63,23 @@ When a task is spawned directly from your code, without a supervisor, then the p
|
||||
|
||||
This small feature is very powerful. It allows instrumentation and monitoring tools to better track and relate the events happening in your system. This feature can also be used by tools like the "Ecto Sandbox". The "Ecto Sandbox" allows developers to run tests concurrently against the database, by using transactions and an ownership mechanism where each process explicitly gets a connection assigned to it. Without `$callers`, every time you spawned a task that queries the database, the task would not know its caller, and therefore it would be unable to know which connection was assigned to it. This often meant features that relies on tasks could not be tested concurrently. With `$callers`, figuring out this relationship is trivial and you have more tests using the full power of your machine.
|
||||
|
||||
## v1.8.2 (2019-05-11)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Raise readable error message on bad EEx state
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Protocol] Ensure `:debug_info` is kept in protocols
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Make sure Logger v1.8 does not get stuck in discard mode
|
||||
* [Logger.Translator] Translate remote process crash in Logger
|
||||
|
||||
## v1.8.1 (2019-01-30)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
@@ -158,6 +158,7 @@ defmodule EEx.Engine do
|
||||
|
||||
@doc false
|
||||
def handle_begin(state) do
|
||||
check_state!(state)
|
||||
%{state | binary: [], dynamic: []}
|
||||
end
|
||||
|
||||
@@ -168,6 +169,7 @@ defmodule EEx.Engine do
|
||||
|
||||
@doc false
|
||||
def handle_body(state) do
|
||||
check_state!(state)
|
||||
%{binary: binary, dynamic: dynamic} = state
|
||||
binary = {:<<>>, [], Enum.reverse(binary)}
|
||||
dynamic = [binary | dynamic]
|
||||
@@ -207,4 +209,11 @@ defmodule EEx.Engine do
|
||||
raise EEx.SyntaxError,
|
||||
"unsupported EEx syntax <%#{marker} %> (the syntax is valid but not supported by the current EEx engine)"
|
||||
end
|
||||
|
||||
defp check_state!(%{binary: _, dynamic: _, vars_count: _}), do: :ok
|
||||
|
||||
defp check_state!(state) do
|
||||
raise "unexpected EEx.Engine state: #{inspect(state)}. " <>
|
||||
"This typically means a bug or an outdated EEx.Engine or tool"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -135,7 +135,7 @@ defmodule GenServer do
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_all)
|
||||
|
||||
In both cases, `Stack.start_link/1` is alwaus invoked.
|
||||
In both cases, `Stack.start_link/1` is always invoked.
|
||||
|
||||
`use GenServer` also accepts a list of options which configures the
|
||||
child specification and therefore how it runs under a supervisor.
|
||||
|
||||
@@ -49,8 +49,8 @@ Elixir version | Supported Erlang/OTP versions
|
||||
1.4 | 18 - 19 (and Erlang/OTP 20 from v1.4.5)
|
||||
1.5 | 18 - 20
|
||||
1.6 | 19 - 20 (and Erlang/OTP 21 from v1.6.6)
|
||||
1.7 | 19 - 21
|
||||
1.8 | 20 - 21
|
||||
1.7 | 19 - 22
|
||||
1.8 | 20 - 22
|
||||
|
||||
While Elixir often adds compatibility to new Erlang/OTP versions on released branches, such as support for Erlang/OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang/OTP release.
|
||||
|
||||
|
||||
@@ -446,10 +446,10 @@ debug_opts(Map, Specs, Opts) ->
|
||||
end.
|
||||
|
||||
take_debug_opts(Opts) ->
|
||||
case lists:keytake(debug_info, 1, Opts) of
|
||||
{value, {debug_info, true}, Rest} -> {true, Rest};
|
||||
{value, {debug_info, false}, Rest} -> {false, Rest};
|
||||
false -> {elixir_compiler:get_opt(debug_info), Opts}
|
||||
case proplists:get_value(debug_info, Opts) of
|
||||
true -> {true, proplists:delete(debug_info, Opts)};
|
||||
false -> {false, proplists:delete(debug_info, Opts)};
|
||||
undefined -> {elixir_compiler:get_opt(debug_info), Opts}
|
||||
end.
|
||||
|
||||
extra_chunks_opts([], Opts) -> Opts;
|
||||
|
||||
@@ -260,6 +260,25 @@ defmodule ProtocolTest do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Protocol.DebugInfoTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "protocols always keep debug_info" do
|
||||
Code.compiler_options(debug_info: false)
|
||||
|
||||
{:module, _, binary, _} =
|
||||
defprotocol DebugInfoProto do
|
||||
end
|
||||
|
||||
assert {:ok, {DebugInfoProto, [debug_info: debug_info]}} =
|
||||
:beam_lib.chunks(binary, [:debug_info])
|
||||
|
||||
assert {:debug_info_v1, :elixir_erl, {:elixir_v1, _, _}} = debug_info
|
||||
after
|
||||
Code.compiler_options(debug_info: true)
|
||||
end
|
||||
end
|
||||
|
||||
path = Path.expand("../ebin", __DIR__)
|
||||
File.mkdir_p!(path)
|
||||
|
||||
|
||||
@@ -555,6 +555,7 @@ defmodule Logger do
|
||||
:level,
|
||||
:utc_log,
|
||||
:discard_threshold,
|
||||
:discard_threshold_periodic_check,
|
||||
:translator_inspect_opts
|
||||
]
|
||||
@spec configure(keyword) :: :ok
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule Logger.Config do
|
||||
@behaviour :gen_event
|
||||
@name __MODULE__
|
||||
@table __MODULE__
|
||||
@check_discard {__MODULE__, :check_discard}
|
||||
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, :ok, name: @name)
|
||||
@@ -76,29 +77,7 @@ defmodule Logger.Config do
|
||||
end
|
||||
|
||||
def handle_event(_event, {state, thresholds}) do
|
||||
%{mode: mode} = state
|
||||
|
||||
case compute_mode(mode, thresholds) do
|
||||
^mode ->
|
||||
{:ok, {state, thresholds}}
|
||||
|
||||
new_mode ->
|
||||
if new_mode == :discard do
|
||||
message =
|
||||
"Logger has #{message_queue_length()} messages in its queue, " <>
|
||||
"which is above :discard_threshold. Messages will be discarded " <>
|
||||
"until the message queue goes back to 75% of the threshold size"
|
||||
|
||||
log(:warn, message, state)
|
||||
end
|
||||
|
||||
if mode == :discard do
|
||||
log(:warn, "Logger has stopped discarding messages", state)
|
||||
end
|
||||
|
||||
state = persist(%{state | mode: new_mode})
|
||||
{:ok, {state, thresholds}}
|
||||
end
|
||||
{:ok, {compute_mode_and_persist_state(state, thresholds), thresholds}}
|
||||
end
|
||||
|
||||
def handle_call({:configure, options}, {%{mode: mode}, _}) do
|
||||
@@ -127,6 +106,16 @@ defmodule Logger.Config do
|
||||
{:ok, old, state}
|
||||
end
|
||||
|
||||
def handle_info(@check_discard, {%{mode: :discard} = state, thresholds}) do
|
||||
state = compute_mode_and_persist_state(state, thresholds)
|
||||
|
||||
if state.mode == :discard do
|
||||
Process.send_after(self(), @check_discard, state.discard_threshold_periodic_check)
|
||||
end
|
||||
|
||||
{:ok, {state, thresholds}}
|
||||
end
|
||||
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
@@ -139,6 +128,31 @@ defmodule Logger.Config do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defp compute_mode_and_persist_state(%{mode: mode} = state, thresholds) do
|
||||
case compute_mode(mode, thresholds) do
|
||||
^mode ->
|
||||
state
|
||||
|
||||
new_mode ->
|
||||
if new_mode == :discard do
|
||||
Process.send_after(self(), @check_discard, state.discard_threshold_periodic_check)
|
||||
|
||||
message =
|
||||
"Logger has #{message_queue_length()} messages in its queue, " <>
|
||||
"which is above :discard_threshold. Messages will be discarded " <>
|
||||
"until the message queue goes back to 75% of the threshold size"
|
||||
|
||||
log(:warn, message, state)
|
||||
end
|
||||
|
||||
if mode == :discard do
|
||||
log(:warn, "Logger has stopped discarding messages", state)
|
||||
end
|
||||
|
||||
persist(%{state | mode: new_mode})
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp log(level, message, state) do
|
||||
@@ -163,7 +177,9 @@ defmodule Logger.Config do
|
||||
level: Application.get_env(:logger, :level),
|
||||
translators: Application.get_env(:logger, :translators),
|
||||
truncate: Application.get_env(:logger, :truncate),
|
||||
utc_log: Application.get_env(:logger, :utc_log)
|
||||
utc_log: Application.get_env(:logger, :utc_log),
|
||||
discard_threshold_periodic_check:
|
||||
Application.get_env(:logger, :discard_threshold_periodic_check)
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
@@ -126,6 +126,19 @@ defmodule Logger.Translator do
|
||||
|
||||
{:ok, msg, metadata}
|
||||
|
||||
{'Error in process ' ++ _, [pid, node, {reason, stack}]} ->
|
||||
reason = Exception.normalize(:error, reason, stack)
|
||||
|
||||
msg = [
|
||||
"Process ",
|
||||
inspect(pid),
|
||||
" on node ",
|
||||
inspect(node),
|
||||
" raised an exception" | format(:error, reason, stack)
|
||||
]
|
||||
|
||||
{:ok, msg, [crash_reason: exit_reason(:error, reason, stack)]}
|
||||
|
||||
{'Error in process ' ++ _, [pid, {reason, stack}]} ->
|
||||
reason = Exception.normalize(:error, reason, stack)
|
||||
msg = ["Process ", inspect(pid), " raised an exception" | format(:error, reason, stack)]
|
||||
|
||||
@@ -24,6 +24,7 @@ defmodule Logger.MixProject do
|
||||
handle_otp_reports: true,
|
||||
handle_sasl_reports: false,
|
||||
discard_threshold_for_error_logger: 500,
|
||||
discard_threshold_periodic_check: 30_000,
|
||||
compile_time_purge_level: :debug,
|
||||
compile_time_purge_matching: [],
|
||||
compile_time_application: nil,
|
||||
|
||||
@@ -12,11 +12,29 @@ defmodule Logger.ConfigTest do
|
||||
end
|
||||
|
||||
test "log/2 relies on discard_threshold" do
|
||||
Logger.remove_backend(:console)
|
||||
Logger.configure(discard_threshold: 0)
|
||||
for _ <- 1..1000, do: Logger.log(:info, "some message")
|
||||
after
|
||||
Logger.configure(discard_threshold: 10000)
|
||||
end
|
||||
|
||||
test "log/2 recovers from discard_threshold" do
|
||||
Logger.remove_backend(:console)
|
||||
Logger.configure(discard_threshold: 1, discard_threshold_periodic_check: 0)
|
||||
|
||||
:sys.suspend(Logger)
|
||||
assert Logger.log(:info, "BEGIN") == :ok
|
||||
send(Logger, :garbage)
|
||||
:sys.resume(Logger)
|
||||
|
||||
# Simulate a sync operation to make sure we are in :discard mode
|
||||
Logger.add_translator({Unknown, :translate})
|
||||
Logger.remove_translator({Unknown, :translate})
|
||||
|
||||
# It should eventually heal
|
||||
wait_for_mode(:async)
|
||||
after
|
||||
Logger.configure(discard_threshold: 10000, discard_threshold_periodic_check: 30000)
|
||||
Logger.add_backend(:console)
|
||||
end
|
||||
|
||||
@@ -37,4 +55,11 @@ defmodule Logger.ConfigTest do
|
||||
Logger.configure(level: :debug)
|
||||
end
|
||||
end
|
||||
|
||||
def wait_for_mode(mode) do
|
||||
unless Logger.Config.__data__().mode == mode do
|
||||
Process.sleep(10)
|
||||
wait_for_mode(mode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -981,6 +981,19 @@ defmodule Logger.TranslatorTest do
|
||||
assert {:stop, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates process crash with erts" do
|
||||
assert {:ok, msg, meta} =
|
||||
Logger.Translator.translate(
|
||||
:error,
|
||||
:error,
|
||||
:format,
|
||||
{'Error in process ~p on node ~p with exit value:~n~p~n',
|
||||
[self(), :"name@127.0.0.1", {:badarith, [{:erlang, :/, [1, 0], []}]}]}
|
||||
)
|
||||
|
||||
assert Keyword.get(meta, :crash_reason)
|
||||
end
|
||||
|
||||
test "reports :undefined MFA properly" do
|
||||
defmodule WeirdFunctionNamesGenServer do
|
||||
use GenServer
|
||||
|
||||
Reference in New Issue
Block a user