Compare commits

...
21 Commits
Author SHA1 Message Date
Vishal ddc554a7ef Fix typo in changelog (#8114)
beind => being
2018-08-19 13:54:25 +02:00
Arkadiusz Gil b6d27b5333 Ignore unknown child info in Logger.Translator (#7892)
This prevents Logger application from crashing when supervisors report
children progress with fields unknown to Logger.Translator.

Closes #7889

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-07-12 20:20:08 +02:00
YMasuo 2e54537524 Fix typo on mix run docs (#7840)
ant -> and
2018-07-08 13:11:57 +02:00
José Valim 0d8fa1a0ff Add missing backtick to logger docs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-06-23 19:12:17 +02:00
Unai Esteibar 764d0c1008 Take into account structs from stale local deps (#7776)
Closes #7765
2018-06-21 17:53:53 +02:00
José Valim 5f86d6158b Document long running processes pitfall in mix cmd, closes #7495 2018-06-21 12:55:35 +02:00
José Valim 1ec9d1d7bd Release v1.6.6 2018-06-20 00:21:31 +02:00
José Valim 462a9deef3 Add missing erlang handler 2018-06-19 21:25:49 +02:00
José Valim c9d6ea50db Port Erlang/OTP 21.0 logger from master
Erlang/OTP 21.0 only forwards certain events to the error_logger,
which means we need to bring the full logger implementation from
master.
2018-06-19 21:03:37 +02:00
Sihui Huang d0694b5d58 When fn is followed by a newline, use multi-clause formatting style (#7737)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-06-19 19:38:35 +02:00
Allen Madsen 16fe53bc10 Fix type spec for Task.Supervisor.start_child/4 (#7745) 2018-06-13 09:37:33 +02:00
José Valim a9a271e360 Update to OTP 21.0-rc2 2018-05-31 12:43:08 +02:00
Chandra Tungathurthi 62697dfa00 Add performant impl for string upcase/downcase :ascii mode (#7680)
The existing implementation with binary comprehensions turned
out to be slower than the other modes.

The current implementation is >= 2.5X faster than the earlier
implementation.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-05-31 12:41:44 +02:00
Idean 132b80acb7 Add bad digit clause to Base.decode32!/1,2 for case: :mixed (#7715)
* Add failing test for Base.decode32!/2 throwing a case error instead of an argument error (Issue #7703)

* Fix Base.decode32!/2 throwing a case error instead of an argument error when using :mixed case (Issue #7703)

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-05-31 12:41:08 +02:00
Fernando Tapia Rico f5e35dade5 Match files starting with dot in "mix format" (#7697)
Here is the example `.formatter.exs` file given in the
documentation of `mix format`:

    [
      inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
    ]

With the previous implementation, the `.formatter.exs` file would
not be formatted because internally `Path.wildcard/2` was used
without expanding files starting with dot ".":

    iex> Path.wildcard("{mix,.formatter}.exs")
    ["mix.exs"]

    iex> Path.wildcard("{mix,.formatter}.exs", match_dot: true)
    [".formatter.exs", "mix.exs"]

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-05-26 19:31:22 +02:00
Avraham Lembke 3d72978af2 Avoid calling truncate_n/2 if truncate is :infinity (#7701)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-05-26 19:31:11 +02:00
John Eckersberg 51d61cbdfb Only test the open/1 IEx helper on Erlang source if it exists (#7350) (#7711) 2018-05-26 00:05:56 +02:00
José Valim 4d3084b3fb No longer make DynamicSupervisor sup_flags opaque, closes #7666
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-05-10 10:16:09 +02:00
José Valim 09a13b0600 Improve docs for IEx.Helpers.open
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-05-07 22:33:50 +02:00
José Valim 5f96a9b37f Improve docs for DynamicSupervisor.count_children, closes #7656
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-05-07 22:33:49 +02:00
José Valim 762b5c0870 Count children restarting on exit in DynamicSupervisor max_children
Closes #7655

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-05-07 21:25:31 +02:00
39 changed files with 935 additions and 374 deletions
+25 -1
View File
@@ -16,7 +16,7 @@ The Elixir codebase itself has been already fully formatted and all further cont
Supervisors in Elixir are responsible for starting, shutting down and restarting child process when things go wrong. Most of the interaction with supervisors happen with the Supervisor module and it contains three main strategies: `:one_for_one`, `:rest_for_one` and `:one_for_all`.
However, sometimes the children of a supervisor are not known upfront and are rather started dynamically. For example, if you are building a web server, you have each request beind handled by a separate supervised process. Those cases were handled in the Supervisor module under a special strategy called `:simple_one_for_one`.
However, sometimes the children of a supervisor are not known upfront and are rather started dynamically. For example, if you are building a web server, you have each request being handled by a separate supervised process. Those cases were handled in the Supervisor module under a special strategy called `:simple_one_for_one`.
Unfortunately, this special strategy changed the semantics of the supervisor in regards to initialization and shutdown. Plus some APIs expected different inputs or would be completely unavailable depending on the supervision strategy.
@@ -123,12 +123,36 @@ Those improvements will help developers better understand the relationship betwe
Other improvements in Mix include better compiler diagnostics for editor integration, support for the `--slowest N` flag in `mix test` that shows the slowest tests in your suite, and a new `mix profile.eprof` task that provides time based profiling, complementing the existing `mix profile.cprof` (count based) and `mix profile.fprof` (flame based).
## v1.6.6 (2018-06-20)
This release supports Erlang/OTP 21.0 by removing all warnings and by properly supporting the new Erlang logger module.
### 1. Bug fixes
#### Elixir
* [Base] Do not raise when finding bad digits in `Base.decode32!` with `case: :mixed`
* [Code] Preserve the user's choice when `fn` is followed by a newline and it has only a single clause
* [DynamicSupervisor] Properly account for restarting children in the `:max_children` configuration
* [String] Add performant impl for string upcase/downcase `:ascii` mode
* [Task.Supervisor] Fix type spec for `start_child/4`
#### Logger
* [Logger] Do not crash truncation when truncate is set to infinity
#### Mix
* [mix format] Match files starting with dot
## v1.6.5 (2018-05-07)
This release supports Erlang/OTP 21.0-rc by removing all warnings and by properly redirecting logger output. Note it is not guaranteed it will support Erlang/OTP 21.0 final.
### 1. Bug fixes
#### Elixir
* [Code] Preserve the user's choice in the formatter on parens call with next break fits
* [Code] Preserve the user's choice in the formatter on calls without parens when we have one argument per line
* [Code] Fix formatting when there is a tilde in the first element of a bitstring
+1 -1
View File
@@ -1 +1 @@
1.6.5
1.6.6
+13 -10
View File
@@ -184,16 +184,19 @@ defmodule Base do
end
defp decode_char_clauses(alphabet, :mixed) when length(alphabet) == 32 do
alphabet
|> Stream.with_index()
|> Enum.flat_map(fn {encoding, value} = pair ->
if encoding in ?A..?Z do
[pair, {encoding - ?A + ?a, value}]
else
[pair]
end
end)
|> decode_clauses()
clauses =
alphabet
|> Stream.with_index()
|> Enum.flat_map(fn {encoding, value} = pair ->
if encoding in ?A..?Z do
[pair, {encoding - ?A + ?a, value}]
else
[pair]
end
end)
|> decode_clauses()
clauses ++ bad_digit_clause()
end
defp decode_mixed_clauses(first, second) do
+20 -4
View File
@@ -522,7 +522,7 @@ defmodule Code.Formatter do
end
defp quoted_to_algebra({:fn, meta, [_ | _] = clauses}, _context, state) do
anon_fun_to_algebra(clauses, line(meta), end_line(meta), state)
anon_fun_to_algebra(clauses, line(meta), end_line(meta), state, eol?(meta))
end
defp quoted_to_algebra({fun, meta, args}, context, state) when is_atom(fun) and is_list(args) do
@@ -1598,7 +1598,13 @@ defmodule Code.Formatter do
## Anonymous functions
# fn -> block end
defp anon_fun_to_algebra([{:->, meta, [[], body]}] = clauses, _min_line, max_line, state) do
defp anon_fun_to_algebra(
[{:->, meta, [[], body]}] = clauses,
_min_line,
max_line,
state,
_multi_clauses_style
) do
min_line = line(meta)
{body_doc, state} = block_to_algebra(body, min_line, max_line, state)
@@ -1617,7 +1623,13 @@ defmodule Code.Formatter do
# fn x ->
# y
# end
defp anon_fun_to_algebra([{:->, meta, [args, body]}] = clauses, _min_line, max_line, state) do
defp anon_fun_to_algebra(
[{:->, meta, [args, body]}] = clauses,
_min_line,
max_line,
state,
false = _multi_clauses_style
) do
min_line = line(meta)
{args_doc, state} = clause_args_to_algebra(args, min_line, state)
{body_doc, state} = block_to_algebra(body, min_line, max_line, state)
@@ -1647,7 +1659,7 @@ defmodule Code.Formatter do
# args2 ->
# block2
# end
defp anon_fun_to_algebra(clauses, min_line, max_line, state) do
defp anon_fun_to_algebra(clauses, min_line, max_line, state, _multi_clauses_style) do
{clauses_doc, state} = clauses_to_algebra(clauses, min_line, max_line, state)
{"fn" |> line(clauses_doc) |> nest(2) |> line("end") |> force_unfit(), state}
end
@@ -2139,6 +2151,10 @@ defmodule Code.Formatter do
false
end
defp eol?(meta) do
Keyword.get(meta, :eol, false)
end
defp line(meta) do
Keyword.get(meta, :line, @max_line)
end
+19 -20
View File
@@ -137,13 +137,14 @@ defmodule DynamicSupervisor do
"""
@callback init(args :: term) :: {:ok, sup_flags()} | :ignore
@opaque sup_flags() :: %{
strategy: strategy(),
intensity: non_neg_integer(),
period: pos_integer(),
max_children: non_neg_integer() | :infinity,
extra_arguments: [term()]
}
@typedoc "The supervisor flags returned on init"
@type sup_flags() :: %{
strategy: strategy(),
intensity: non_neg_integer(),
period: pos_integer(),
max_children: non_neg_integer() | :infinity,
extra_arguments: [term()]
}
@typedoc "Option values used by the `start*` functions"
@type option :: {:name, Supervisor.name()} | init_option()
@@ -179,7 +180,6 @@ defmodule DynamicSupervisor do
:max_restarts,
:max_seconds,
children: %{},
dynamic: 0,
restarts: []
]
@@ -409,7 +409,7 @@ defmodule DynamicSupervisor do
The map contains the following keys:
* `:specs` - always 1 as dynamic supervisors have a single specification
* `:specs` - the number of children processes
* `:active` - the count of all actively running child processes managed by
this supervisor
@@ -625,10 +625,10 @@ defmodule DynamicSupervisor do
end
def handle_call({:start_child, child}, _from, state) do
%{dynamic: dynamic, max_children: max_children} = state
%{children: children, max_children: max_children} = state
if dynamic < max_children do
handle_start_child(child, %{state | dynamic: dynamic + 1})
if map_size(children) < max_children do
handle_start_child(child, state)
else
{:reply, {:error, :max_children}, state}
end
@@ -645,7 +645,7 @@ defmodule DynamicSupervisor do
{:reply, reply, save_child(pid, mfa, restart, shutdown, type, modules, state)}
_ ->
{:reply, reply, update_in(state.dynamic, &(&1 - 1))}
{:reply, reply, state}
end
end
@@ -664,14 +664,14 @@ defmodule DynamicSupervisor do
end
end
defp save_child(pid, {m, f, _}, :temporary, shutdown, type, modules, state) do
put_in(state.children[pid], {{m, f, :undefined}, :temporary, shutdown, type, modules})
end
defp save_child(pid, mfa, restart, shutdown, type, modules, state) do
mfa = mfa_for_restart(mfa, restart)
put_in(state.children[pid], {mfa, restart, shutdown, type, modules})
end
defp mfa_for_restart({m, f, _}, :temporary), do: {m, f, :undefined}
defp mfa_for_restart(mfa, _), do: mfa
defp exit_reason(:exit, reason, _), do: reason
defp exit_reason(:error, reason, stack), do: {reason, stack}
defp exit_reason(:throw, value, stack), do: {{:nocatch, value}, stack}
@@ -888,9 +888,8 @@ defmodule DynamicSupervisor do
{:ok, delete_child(pid, state)}
end
defp delete_child(pid, state) do
%{children: children, dynamic: dynamic} = state
%{state | children: Map.delete(children, pid), dynamic: dynamic - 1}
defp delete_child(pid, %{children: children} = state) do
%{state | children: Map.delete(children, pid)}
end
defp restart_child(pid, child, state) do
+1 -1
View File
@@ -117,7 +117,7 @@ defmodule Port do
reimplementing core part of the Runtime System, such as the `:user` and
`:shell` processes.
## Zombie processes
## Zombie OS processes
A port can be closed via the `close/1` function or by sending a `{pid, :close}`
message. However, if the VM crashes, a long-running program started by the port
+14 -6
View File
@@ -644,15 +644,19 @@ defmodule String do
end
def upcase(string, :ascii) when is_binary(string) do
for <<x <- string>>,
do: if(x >= ?a and x <= ?z, do: <<x - 32>>, else: <<x>>),
into: ""
IO.iodata_to_binary(upcase_ascii(string))
end
def upcase(string, mode) when mode in @conditional_mappings do
String.Casing.upcase(string, [], mode)
end
defp upcase_ascii(<<char, rest::bits>>) when char >= ?a and char <= ?z,
do: [char - 32 | upcase_ascii(rest)]
defp upcase_ascii(<<char, rest::bits>>), do: [char | upcase_ascii(rest)]
defp upcase_ascii(<<>>), do: []
@doc """
Converts all characters in the given string to lowercase according to `mode`.
@@ -700,15 +704,19 @@ defmodule String do
end
def downcase(string, :ascii) when is_binary(string) do
for <<x <- string>>,
do: if(x >= ?A and x <= ?Z, do: <<x + 32>>, else: <<x>>),
into: ""
IO.iodata_to_binary(downcase_ascii(string))
end
def downcase(string, mode) when mode in @conditional_mappings do
String.Casing.downcase(string, [], mode)
end
defp downcase_ascii(<<char, rest::bits>>) when char >= ?A and char <= ?Z,
do: [char + 32 | downcase_ascii(rest)]
defp downcase_ascii(<<char, rest::bits>>), do: [char | downcase_ascii(rest)]
defp downcase_ascii(<<>>), do: []
@doc """
Converts the first character in the given string to
uppercase and the remainder to lowercase according to `mode`.
+3 -2
View File
@@ -317,7 +317,7 @@ defmodule Task.Supervisor do
or an integer indicating the timeout value, defaults to 5000 milliseconds.
"""
@spec start_child(Supervisor.supervisor(), (() -> any)) :: {:ok, pid}
@spec start_child(Supervisor.supervisor(), (() -> any)) :: DynamicSupervisor.on_start_child()
def start_child(supervisor, fun, options \\ []) do
restart = options[:restart]
shutdown = options[:shutdown]
@@ -331,7 +331,8 @@ defmodule Task.Supervisor do
Similar to `start_child/2` except the task is specified
by the given `module`, `fun` and `args`.
"""
@spec start_child(Supervisor.supervisor(), module, atom, [term]) :: {:ok, pid}
@spec start_child(Supervisor.supervisor(), module, atom, [term]) ::
DynamicSupervisor.on_start_child()
def start_child(supervisor, module, fun, args, options \\ [])
when is_atom(fun) and is_list(args) do
restart = options[:restart]
@@ -12,7 +12,7 @@ Elixir version | Supported Erlang/OTP versions
1.3 | 18 - 19
1.4 | 18 - 19 (and OTP 20 from v1.4.5)
1.5 | 18 - 20
1.6 | 19 - 20 (and OTP 21 from v1.6.5)
1.6 | 19 - 20 (and OTP 21 from v1.6.6)
While Elixir often adds compatibility to new Erlang versions on released branches, such as support for 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 release.
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.6.5"},
{vsn, "1.6.6"},
{modules, [
elixir
]},
+2 -2
View File
@@ -305,7 +305,7 @@ eoe -> ';' : '$1'.
eoe -> eol ';' : '$1'.
fn_eoe -> 'fn' : '$1'.
fn_eoe -> 'fn' eoe : '$1'.
fn_eoe -> 'fn' eoe : next_is_eol('$1').
do_eoe -> 'do' : '$1'.
do_eoe -> 'do' eoe : '$1'.
@@ -659,7 +659,7 @@ meta_from_token_with_end_line(Begin, End) ->
case ?formatter_metadata() of
true ->
[{end_line, line_from_location(?location(End))}
| meta_from_token(Begin)];
| eol_op(?location(Begin)) ++ meta_from_token(Begin)];
false ->
meta_from_token(Begin)
end.
+5 -1
View File
@@ -473,7 +473,7 @@ defmodule BaseTest do
assert :error == decode32("66FF", case: :lower)
end
test "decode32!/1,2 error on non-alphabet digit" do
test "decode32!/1,2 argument error on non-alphabet digit" do
assert_raise ArgumentError, "non-alphabet digit found: \")\" (byte 41)", fn ->
decode32!("MZX)6YTB")
end
@@ -485,6 +485,10 @@ defmodule BaseTest do
assert_raise ArgumentError, "non-alphabet digit found: \"M\" (byte 77)", fn ->
decode32!("MZXW6YTBOI======", case: :lower)
end
assert_raise ArgumentError, "non-alphabet digit found: \"0\" (byte 48)", fn ->
decode32!("0ZXW6YTB0I======", case: :mixed)
end
end
test "decode32/1 errors on incorrect padding" do
@@ -449,14 +449,15 @@ defmodule Code.Formatter.CommentsTest do
assert_format bad, ~S"""
# fn
# before head
# middle head
# after head
fn hello ->
# before body
# middle body
world
# after body
fn
# before head
# middle head
hello ->
# after head
# before body
# middle body
world
# after body
end
"""
end
@@ -187,6 +187,22 @@ defmodule Code.Formatter.GeneralTest do
assert_same code, @short_length
end
test "with a single clause, followed by a newline, and can fit in one line" do
assert_same """
fn
hello -> world
end
"""
end
test "with a single clause, followed by a newline, and can not fit in one line" do
assert_same """
SomeModule.long_function_name_that_approaches_max_columns(argument, acc, fn
%SomeStruct{key: key}, acc -> more_code(key, acc)
end)
"""
end
test "with multiple clauses" do
code = """
fn
@@ -340,7 +340,7 @@ defmodule DynamicSupervisorTest do
assert %{workers: 4, active: 2} = DynamicSupervisor.count_children(pid)
end
test "restarting children counted in max_children" do
test "restarting on init children counted in max_children" do
child = current_module_worker([:restart, :error], restart: :permanent)
opts = [strategy: :one_for_one, max_children: 1, max_restarts: 100_000]
{:ok, pid} = DynamicSupervisor.start_link(opts)
@@ -353,7 +353,20 @@ defmodule DynamicSupervisorTest do
assert {:error, :max_children} = DynamicSupervisor.start_child(pid, child)
end
test "restarting a child with extra_args successfully restarts child" do
test "restarting on exit children counted in max_children" do
child = current_module_worker([:ok2], restart: :permanent)
opts = [strategy: :one_for_one, max_children: 1, max_restarts: 100_000]
{:ok, pid} = DynamicSupervisor.start_link(opts)
assert {:ok, child_pid} = DynamicSupervisor.start_child(pid, child)
assert_kill(child_pid, :shutdown)
assert %{workers: 1, active: 1} = DynamicSupervisor.count_children(pid)
child = current_module_worker([:ok2], restart: :permanent)
assert {:error, :max_children} = DynamicSupervisor.start_child(pid, child)
end
test "restarting a child with extra_arguments successfully restarts child" do
parent = self()
fun = fn ->
+1 -1
View File
@@ -70,7 +70,7 @@ defmodule ExUnit.CaptureLog do
{:ok, string_io} = StringIO.open("")
try do
_ = :gen_event.which_handlers(:error_logger)
_ = Process.whereis(:error_logger) && :gen_event.which_handlers(:error_logger)
:ok = add_capture(string_io, opts)
ref = ExUnit.CaptureServer.log_capture_on(self())
+10 -5
View File
@@ -240,10 +240,15 @@ defmodule IEx.Helpers do
subl path/to/file:line
Custom editors are supported by using the __FILE__ and __LINE__
notations. For example, vi/vim users can set `ELIXIR_EDITOR` to:
It is important that you choose an editor command that does
not block nor that attempts to run an editor directly in the
terminal. Command-line based editors likely extra configuration
so they open up the given file and line in a separate window.
ELIXIR_EDITOR="vi +__LINE__ __FILE__"
Custom editors are supported by using the __FILE__ and __LINE__
notations, for example:
ELIXIR_EDITOR="my_editor +__LINE__ __FILE__"
and Elixir will properly interpolate values.
@@ -251,8 +256,8 @@ defmodule IEx.Helpers do
`ELIXIR_EDITOR` can be set "echo" if you prefer to display the
location rather than opening it.
Keep in mind the location may not exist when opening
precompiled source code, such as Elixir itself.
Keep in mind the location may not exist when opening precompiled
source code.
## Examples
+28 -20
View File
@@ -195,30 +195,38 @@ defmodule IEx.HelpersTest do
~r/#{@elixir_erl}:\d+$/
end
test "opens OTP lists module" do
assert capture_iex("open(:lists)") |> maybe_trim_quotes() =~ ~r/#{@lists_erl}:\d+$/
# Some installations remove the source file once Erlang is compiled. See #7348.
if File.regular?(@lists_erl) do
test "opens OTP lists module" do
assert capture_iex("open(:lists)") |> maybe_trim_quotes() =~ ~r/#{@lists_erl}:\d+$/
end
test "opens OTP lists module.function" do
assert capture_iex("open(:lists.reverse)") |> maybe_trim_quotes() =~
~r/#{@lists_erl}:\d+$/
end
test "opens OTP lists module.function/arity" do
assert capture_iex("open(:lists.reverse/1)") |> maybe_trim_quotes() =~
~r/#{@lists_erl}:\d+$/
end
end
test "opens OTP lists module.function" do
assert capture_iex("open(:lists.reverse)") |> maybe_trim_quotes() =~ ~r/#{@lists_erl}:\d+$/
end
# Some installations remove the source file once Erlang is compiled. See #7348.
if File.regular?(@httpc_erl) do
test "opens OTP httpc module" do
assert capture_iex("open(:httpc)") |> maybe_trim_quotes() =~ ~r/#{@httpc_erl}:\d+$/
end
test "opens OTP lists module.function/arity" do
assert capture_iex("open(:lists.reverse/1)") |> maybe_trim_quotes() =~
~r/#{@lists_erl}:\d+$/
end
test "opens OTP httpc module.function" do
assert capture_iex("open(:httpc.request)") |> maybe_trim_quotes() =~
~r/#{@httpc_erl}:\d+$/
end
test "opens OTP httpc module" do
assert capture_iex("open(:httpc)") |> maybe_trim_quotes() =~ ~r/#{@httpc_erl}:\d+$/
end
test "opens OTP httpc module.function" do
assert capture_iex("open(:httpc.request)") |> maybe_trim_quotes() =~ ~r/#{@httpc_erl}:\d+$/
end
test "opens OTP httpc module.function/arity" do
assert capture_iex("open(:httpc.request/1)") |> maybe_trim_quotes() =~
~r/#{@httpc_erl}:\d+$/
test "opens OTP httpc module.function/arity" do
assert capture_iex("open(:httpc.request/1)") |> maybe_trim_quotes() =~
~r/#{@httpc_erl}:\d+$/
end
end
test "errors OTP preloaded module" do
+43 -35
View File
@@ -16,8 +16,10 @@ defmodule Logger do
performant when required but also apply backpressure
when under stress.
* Wraps OTP's [`:error_logger`](http://erlang.org/doc/man/error_logger.html)
to prevent it from overflowing.
* Plugs into Erlang's [`:logger`](http://erlang.org/doc/man/logger.html)
(from Erlang/OTP 21) to convert terms to Elixir syntax or wraps
Erlang's [`:error_logger`](http://erlang.org/doc/man/error_logger.html)
in earlier Erlang/OTP versions to prevent it from overflowing.
Logging is useful for tracking when an event of interest happens in your
system. For example, it may be helpful to log whenever a user is deleted.
@@ -62,8 +64,8 @@ defmodule Logger do
* Runtime configuration - can be set before the `:logger`
application is started, but may be changed during runtime
* Error logger configuration - configuration for the
wrapper around OTP's [`:error_logger`](http://erlang.org/doc/man/error_logger.html)
* Erlang configuration - options that handle integration with
Erlang's logging facilities
### Application configuration
@@ -152,19 +154,24 @@ defmodule Logger do
### Error logger configuration
The following configuration applies to `Logger`'s wrapper around
OTP's [`:error_logger`](http://erlang.org/doc/man/error_logger.html).
All the configurations below must be set before the `:logger` application starts.
Erlang's logging functionalities. All the configurations below must
be set before the `:logger` application starts.
* `:handle_otp_reports` - redirects OTP reports to `Logger` so
they are formatted in Elixir terms. This uninstalls OTP's
logger that prints terms to terminal. Defaults to `true`.
they are formatted in Elixir terms. This effectively disables
Erlang standard logger. Defaults to `true`.
* `:handle_sasl_reports` - redirects supervisor, crash and
progress reports to `Logger` so they are formatted in Elixir
terms. Your application must guarantee `:sasl` is started before
`:logger`. This means you may see some initial reports written
in Erlang syntax until the Logger application kicks in and
uninstalls SASL's logger in favor of its own. Defaults to `false`.
in Erlang syntax until the Logger application kicks in.
Defaults to `false`.
From Erlang/OTP 21, `:handle_sasl_reports` only has an effect if
`:handle_otp_reports` is true.
The following configurations apply only for Erlang/OTP 20 and earlier:
* `:discard_threshold_for_error_logger` - if `:error_logger` has more than
`discard_threshold` messages in its inbox, messages will be dropped
@@ -176,19 +183,17 @@ defmodule Logger do
350 (0.75 * threshold) entries and 50 (0.1 * theshold) messages will
be processed before the threshold is checked once again.
For example, to configure `Logger` to redirect all
[`:error_logger`](http://erlang.org/doc/man/error_logger.html) messages
using a `config/config.exs` file:
For example, to configure `Logger` to redirect all Erlang messages using a
`config/config.exs` file:
config :logger,
handle_otp_reports: true,
handle_sasl_reports: true
Furthermore, `Logger` allows messages sent by OTP's `:error_logger`
to be translated into an Elixir format via translators. Translators
can be dynamically added at any time with the `add_translator/1`
and `remove_translator/1` APIs. Check `Logger.Translator` for more
information.
Furthermore, `Logger` allows messages sent by Erlang to be translated
into an Elixir format via translators. Translators can be added at any
time with the `add_translator/1` and `remove_translator/1` APIs. Check
`Logger.Translator` for more information.
## Backends
@@ -523,7 +528,7 @@ defmodule Logger do
"""
@spec flush :: :ok
def flush do
_ = :gen_event.which_handlers(:error_logger)
_ = Process.whereis(:error_logger) && :gen_event.which_handlers(:error_logger)
:gen_event.sync_notify(Logger, :flush)
end
@@ -533,8 +538,7 @@ defmodule Logger do
## Options
* `:flush` - when `true`, guarantees all messages currently sent
to both Logger and OTP's [`:error_logger`](http://erlang.org/doc/man/error_logger.html)
are processed before the backend is added
to `Logger` are processed before the backend is added
"""
@spec add_backend(atom, keyword) :: Supervisor.on_start_child()
@@ -560,8 +564,7 @@ defmodule Logger do
## Options
* `:flush` - when `true`, guarantees all messages currently sent
to both Logger and OTP's [`:error_logger`](http://erlang.org/doc/man/error_logger.html)
are processed before the backend is removed
to `Logger` are processed before the backend is removed
"""
@spec remove_backend(atom, keyword) :: :ok | {:error, term}
@@ -610,14 +613,17 @@ defmodule Logger do
when level in @levels and is_list(metadata) do
case __metadata__() do
{true, pdict} ->
%{mode: mode, truncate: truncate, level: min_level, utc_log: utc_log?} =
Logger.Config.__data__()
%{
mode: mode,
truncate: truncate,
level: min_level,
utc_log: utc_log?
} = Logger.Config.__data__()
if compare_levels(level, min_level) != :lt and mode != :discard do
metadata = [pid: self()] ++ Keyword.merge(pdict, metadata)
{message, metadata} = normalize_message(chardata_or_fun, metadata)
with true <- compare_levels(level, min_level) != :lt and mode != :discard,
metadata = [pid: self()] ++ Keyword.merge(pdict, metadata),
{message, metadata} <- normalize_message(chardata_or_fun, metadata) do
truncated = truncate(message, truncate)
tuple = {Logger, truncated, Logger.Utils.timestamp(utc_log?), metadata}
try do
@@ -629,7 +635,7 @@ defmodule Logger do
:exit, reason -> {:error, reason}
end
else
:ok
_ -> :ok
end
{false, _} ->
@@ -755,15 +761,17 @@ defmodule Logger do
end
defp normalize_message(fun, metadata) when is_function(fun, 0) do
normalize_message(fun.(), metadata)
case fun.() do
{message, fun_metadata} -> {message, Keyword.merge(metadata, fun_metadata)}
:skip -> :skip
message -> {message, metadata}
end
end
defp normalize_message({message, fun_metadata}, metadata) when is_list(fun_metadata) do
{message, Keyword.merge(metadata, fun_metadata)}
defp normalize_message(message, metadata) do
{message, metadata}
end
defp normalize_message(message, metadata), do: {message, metadata}
defp truncate(data, n) when is_list(data) or is_binary(data), do: Logger.Utils.truncate(data, n)
defp truncate(data, n), do: Logger.Utils.truncate(to_string(data), n)
+58 -24
View File
@@ -7,8 +7,15 @@ defmodule Logger.App do
def start(_type, _args) do
otp_reports? = Application.get_env(:logger, :handle_otp_reports)
sasl_reports? = Application.get_env(:logger, :handle_sasl_reports)
threshold = Application.get_env(:logger, :discard_threshold_for_error_logger)
error_handler = {:error_logger, Logger.ErrorHandler, {otp_reports?, sasl_reports?, threshold}}
otp_children =
if otp_logger?() do
[]
else
threshold = Application.get_env(:logger, :discard_threshold_for_error_logger)
arg = {:error_logger, Logger.ErrorHandler, {otp_reports?, sasl_reports?, threshold}}
[%{id: Logger.ErrorHandler, start: {Logger.Watcher, :start_link, [arg]}}]
end
children = [
%{
@@ -17,19 +24,21 @@ defmodule Logger.App do
modules: :dynamic
},
{Logger.Watcher, {Logger, Logger.Config, []}},
{Logger.WatcherSupervisor, {Logger.Config, :handlers, []}},
%{
id: Logger.ErrorHandler,
start: {Logger.Watcher, :start_link, [error_handler]}
}
{Logger.WatcherSupervisor, {Logger.Config, :handlers, []}}
| otp_children
]
config = Logger.Config.new()
case Supervisor.start_link(children, strategy: :rest_for_one, name: Logger.Supervisor) do
{:ok, sup} ->
if otp_reports? or sasl_reports? do
delete_handlers(otp_reports?, sasl_reports?)
if otp_logger?() do
if otp_reports? do
add_elixir_handler(sasl_reports?)
delete_erlang_handler()
end
else
delete_old_handlers(otp_reports?, sasl_reports?)
end
{:ok, sup, config}
@@ -47,6 +56,10 @@ defmodule Logger.App do
@doc false
def stop(config) do
if otp_logger?() do
_ = :logger.remove_handler(Logger)
end
add_handlers(Logger.Config.deleted_handlers())
Logger.Config.delete(config)
end
@@ -73,20 +86,35 @@ defmodule Logger.App do
end
end
defp delete_handlers(otp_reports?, sasl_reports?) do
# TODO: Remove conditional error_logger code once we require Erlang/OTP 21+.
defp otp_logger? do
is_pid(Process.whereis(:logger))
end
defp add_elixir_handler(sasl_reports?) do
config = %{level: :debug, sasl_reports?: sasl_reports?}
:logger.add_handler(Logger, Logger.ErlangHandler, config)
end
defp delete_erlang_handler() do
with {:ok, %{module: module} = config} <- :logger.get_handler_config(:default),
:ok <- :logger.remove_handler(:default) do
%{level: level} = :logger.get_primary_config()
:logger.update_primary_config(%{level: :debug})
primary_config = {:primary, %{level: level}}
handler_config = {:default, module, config}
[] = Logger.Config.deleted_handlers([primary_config, handler_config])
:ok
else
_ -> :ok
end
end
defp delete_old_handlers(otp_reports?, sasl_reports?) do
deleted =
if is_pid(Process.whereis(:logger)) and Code.ensure_loaded?(:logger) do
with {:ok, {module, config}} <- :logger.get_handler_config(:logger_std_h),
:ok <- :logger.remove_handler(:logger_std_h) do
[{:logger_std_h, module, config}]
else
_ -> []
end
else
for {tty, true} <- [error_logger_tty_h: otp_reports?, sasl_report_tty_h: sasl_reports?],
:error_logger.delete_report_handler(tty) != {:error, :module_not_found},
do: tty
end
for {tty, true} <- [error_logger_tty_h: otp_reports?, sasl_report_tty_h: sasl_reports?],
:error_logger.delete_report_handler(tty) != {:error, :module_not_found},
do: tty
[] = Logger.Config.deleted_handlers(deleted)
:ok
@@ -95,8 +123,14 @@ defmodule Logger.App do
defp add_handlers(handlers) do
for handler <- handlers do
case handler do
{handler, module, config} -> :logger.add_handler(handler, module, config)
handler -> :error_logger.add_report_handler(handler)
{handler, module, config} ->
:logger.add_handler(handler, module, config)
{:primary, config} ->
:logger.update_primary_config(config)
handler ->
:error_logger.add_report_handler(handler)
end
end
+133
View File
@@ -0,0 +1,133 @@
defmodule Logger.ErlangHandler do
@moduledoc false
@doc """
Hook required by `:logger`.
"""
def log(%{meta: %{domain: [:otp, :sasl | _]}}, %{sasl_reports?: false}) do
:ok
end
def log(%{meta: %{domain: [:supervisor_report]}}, %{sasl_reports?: false}) do
:ok
end
def log(%{level: level, msg: msg, meta: erl_meta}, _config) do
level = erlang_level_to_elixir_level(level)
Logger.bare_log(level, fn ->
try do
meta = extract_metadata(erl_meta)
case msg do
{:string, string} ->
{string, meta}
{:report, %{label: label, report: report} = complete} when map_size(complete) == 2 ->
translate(level, :report, {label, report}, meta, erl_meta)
{:report, %{label: {:error_logger, _}, format: format, args: args}} ->
translate(level, :format, {format, args}, meta, erl_meta)
{:report, report} ->
translate(level, :report, {:logger, report}, meta, erl_meta)
{format, args} ->
translate(level, :format, {format, args}, meta, erl_meta)
end
rescue
e ->
[
"Failure while translating Erlang's logger event\n",
Exception.format(:error, e, System.stacktrace())
]
end
end)
end
defp erlang_level_to_elixir_level(:emergency), do: :error
defp erlang_level_to_elixir_level(:alert), do: :error
defp erlang_level_to_elixir_level(:critical), do: :error
defp erlang_level_to_elixir_level(:error), do: :error
defp erlang_level_to_elixir_level(:warning), do: :warn
defp erlang_level_to_elixir_level(:notice), do: :info
defp erlang_level_to_elixir_level(:info), do: :info
defp erlang_level_to_elixir_level(:debug), do: :debug
defp extract_metadata(map) do
metadata = []
metadata =
case map do
%{mfa: {mod, fun, arity}} -> [module: mod, function: form_fa(fun, arity)] ++ metadata
_ -> metadata
end
metadata =
case map do
%{file: file, line: line} -> [file: List.to_string(file), line: line] ++ metadata
_ -> metadata
end
metadata =
case map do
%{pid: pid} -> [pid: pid] ++ metadata
_ -> metadata
end
metadata
rescue
_ -> []
end
defp form_fa(fun, arity) do
Atom.to_string(fun) <> "/" <> Integer.to_string(arity)
end
@doc """
Shared translation convenience.
"""
def translate(level, kind, data, meta, erl_meta) do
%{
level: min_level,
truncate: truncate,
translators: translators
} = Logger.Config.__data__()
case translate(translators, min_level, level, kind, data, meta) do
:none -> {translate_fallback(kind, data, erl_meta, truncate), meta}
other -> other
end
end
defp translate([{mod, fun} | t], min_level, level, kind, data, meta) do
case apply(mod, fun, [min_level, level, kind, data]) do
{:ok, chardata, transdata} -> {chardata, Keyword.merge(meta, transdata)}
{:ok, chardata} -> {chardata, meta}
:skip -> :skip
:none -> translate(t, min_level, level, kind, data, meta)
end
end
defp translate([], _min_level, _level, _kind, _data, _meta) do
:none
end
defp translate_fallback(:report, {:logger, data}, %{report_cb: callback} = meta, truncate) do
translate_fallback(:format, callback.(data), meta, truncate)
end
defp translate_fallback(:format, {format, args}, _meta, truncate) do
format
|> Logger.Utils.scan_inspect(args, truncate)
|> :io_lib.build_text()
end
defp translate_fallback(:report, {_type, %{} = data}, _meta, _truncate) do
Kernel.inspect(Map.to_list(data))
end
defp translate_fallback(:report, {_type, data}, _meta, _truncate) do
Kernel.inspect(data)
end
end
+5 -28
View File
@@ -2,6 +2,8 @@ defmodule Logger.ErrorHandler do
@moduledoc false
@behaviour :gen_event
# TODO: Remove this when we require Erlang/OTP 21+.
def init({otp?, sasl?, threshold}) do
# We store the Logger PID in the state because when we are shutting
# down the Logger application, the Logger process may be terminated
@@ -83,17 +85,13 @@ defmodule Logger.ErrorHandler do
%{
mode: mode,
level: min_level,
truncate: truncate,
utc_log: utc_log?,
translators: translators
utc_log: utc_log?
} = Logger.Config.__data__()
with true <- Logger.compare_levels(level, min_level) != :lt and mode != :discard,
{:ok, message} <- translate(translators, min_level, level, kind, data, truncate) do
message = Logger.Utils.truncate(message, truncate)
meta = [pid: ensure_pid(pid), error_logger: ensure_type(type)],
{message, meta} <- Logger.ErlangHandler.translate(level, kind, data, meta, %{}) do
# Mode is always async to avoid clogging the error_logger
meta = [pid: ensure_pid(pid), error_logger: ensure_type(type)]
event = {Logger, message, Logger.Utils.timestamp(utc_log?), meta}
:gen_event.notify(state.logger, {level, gl, event})
end
@@ -155,25 +153,4 @@ defmodule Logger.ErrorHandler do
0 -> :ok
end
end
defp translate([{mod, fun} | t], min_level, level, kind, data, truncate) do
case apply(mod, fun, [min_level, level, kind, data]) do
{:ok, chardata} -> {:ok, chardata}
:skip -> :skip
:none -> translate(t, min_level, level, kind, data, truncate)
end
end
defp translate([], _min_level, _level, :format, {format, args}, truncate) do
msg =
format
|> Logger.Utils.scan_inspect(args, truncate)
|> :io_lib.build_text()
{:ok, msg}
end
defp translate([], _min_level, _level, :report, {_type, data}, _truncate) do
{:ok, Kernel.inspect(data)}
end
end
+1 -1
View File
@@ -20,7 +20,7 @@ defmodule Logger.Formatter do
* `$message` - the log message
* `$level` - the log level
* `$node` - the node that prints the message
* `$metadata` - user controlled data presented in `"key=val key2=val2"` format
* `$metadata` - user controlled data presented in `"key=val key2=val2 "` format
* `$levelpad` - sets to a single space if level is 4 characters long,
otherwise set to the empty space. Used to align the message after level.
+260 -130
View File
@@ -3,7 +3,7 @@ defmodule Logger.Translator do
Default translation for Erlang log messages.
Logger allows developers to rewrite log messages provided by
Erlang applications into a format more compatible with Elixir
OTP applications into a format more compatible with Elixir
log messages by providing a translator.
A translator is simply a tuple containing a module and a function
@@ -13,14 +13,15 @@ defmodule Logger.Translator do
* `min_level` - the current Logger level
* `level` - the level of the message being translated
* `kind` - if the message is a report or a format
* `message` - the message to format. If it is a report, it is a tuple
with `{report_type, report_data}`, if it is a format, it is a
tuple with `{format_message, format_args}`
* `kind` - if the message is a `:report` or `:format`
* `message` - the message to format. If it is `:report`, it is a tuple
with `{report_type, report_data}`, if it is `:format`, it is a
tuple with `{format_message, format_args}`.
The function must return:
* `{:ok, chardata}` - if the message was translated with its translation
* `{:ok, chardata, metadata}` - if the message translation with its metadata
* `{:ok, chardata}` - the translated message
* `:skip` - if the message is not meant to be translated nor logged
* `:none` - if there is no translation, which triggers the next translator
@@ -28,17 +29,67 @@ defmodule Logger.Translator do
and the default messages translated by Logger.
"""
# The name_or_id checks are required to support old OTP projects.
@doc """
Built-in translation function.
"""
def translate(min_level, level, kind, message)
## Erlang/OTP 21 and after
def translate(min_level, _level, :report, {:logger, %{label: label} = report}) do
case label do
{:gen_server, :terminate} ->
report_gen_server_terminate(min_level, report)
{:gen_event, :terminate} ->
report_gen_event_terminate(min_level, report)
_ ->
:skip
end
end
def translate(min_level, _level, :report, {{:proc_lib, :crash}, data}) do
report_crash(min_level, data)
end
def translate(min_level, _level, :report, {{:supervisor, :progress}, data}) do
report_supervisor_progress(min_level, data)
end
def translate(min_level, _level, :report, {{:supervisor, _}, data}) do
report_supervisor(min_level, data)
end
def translate(
_min_level,
_level,
:report,
{{:application_controller, :progress}, [application: app, started_at: node]}
) do
{:ok, ["Application ", Atom.to_string(app), " started at " | inspect(node)]}
end
def translate(
_min_level,
_level,
:report,
{{:application_controller, :exit}, [application: app, exited: reason, type: _type]}
) do
{:ok, ["Application ", Atom.to_string(app), " exited: " | Application.format_error(reason)]}
end
## Erlang/OTP 20 and before
def translate(min_level, :error, :format, message) do
opts = Application.get_env(:logger, :translator_inspect_opts)
case message do
{'** Generic server ' ++ _, [name, last, state, reason | client]} ->
{formatted, _reason} = format_reason(reason)
msg =
["GenServer #{inspect(name)} terminating", format_stop(reason)] ++
["GenServer #{inspect(name)} terminating", formatted] ++
["\nLast message#{format_from(client)}: #{inspect(last, opts)}"]
if min_level == :debug do
@@ -48,9 +99,11 @@ defmodule Logger.Translator do
end
{'** gen_event handler ' ++ _, [name, manager, last, state, reason]} ->
{formatted, _reason} = format_reason(reason)
msg =
["GenEvent handler #{inspect(name)} installed in #{inspect(manager)} terminating"] ++
[format_stop(reason), "\nLast message: #{inspect(last, opts)}"]
[formatted, "\nLast message: #{inspect(last, opts)}"]
if min_level == :debug do
{:ok, [msg | "\nState: #{inspect(state, opts)}"]}
@@ -59,9 +112,11 @@ defmodule Logger.Translator do
end
{'** Task ' ++ _, [name, starter, function, args, reason]} ->
{formatted, _reason} = format_reason(reason)
msg =
["Task #{inspect(name)} started from #{inspect(starter)} terminating"] ++
[format_stop(reason), "\nFunction: #{inspect(function, opts)}"] ++
[formatted, "\nFunction: #{inspect(function, opts)}"] ++
["\n Args: #{inspect(args, opts)}"]
{:ok, msg}
@@ -80,109 +135,102 @@ defmodule Logger.Translator do
:std_info,
[application: app, exited: reason, type: _type]
}) do
{:ok, "Application #{app} exited: #{Application.format_error(reason)}"}
{:ok, ["Application ", Atom.to_string(app), " exited: " | Application.format_error(reason)]}
end
def translate(min_level, :error, :report, {{:error_logger, :error_report}, data}) do
report_supervisor(min_level, data)
end
def translate(min_level, :error, :report, {:supervisor_report, data}) do
translate_supervisor(min_level, data)
report_supervisor(min_level, data)
end
def translate(min_level, :error, :report, {:crash_report, data}) do
translate_crash(min_level, data)
report_crash(min_level, data)
end
def translate(min_level, :info, :report, {:progress, data}) do
translate_progress(min_level, data)
def translate(min_level, :info, :report, {:progress, [{:supervisor, _} | _] = data}) do
report_supervisor_progress(min_level, data)
end
def translate(_min_level, :info, :report, {:progress, [application: app, started_at: node]}) do
{:ok, ["Application ", Atom.to_string(app), " started at " | inspect(node)]}
end
## Helpers
def translate(_min_level, _level, _kind, _message) do
:none
end
defp translate_supervisor(
min_level,
supervisor: sup,
errorContext: context,
reason: reason,
offender: [{:pid, pid}, {name_or_id, name} | offender]
)
when is_pid(pid) and context !== :shutdown and name_or_id in [:name, :id] do
msg =
["Child ", inspect(name), " of Supervisor ", sup_name(sup)] ++
[?\s, sup_context(context), "\n** (exit) "] ++
[offender_reason(reason, context), "\nPid: ", inspect(pid)] ++
child_info(min_level, offender)
defp report_gen_server_terminate(min_level, report) do
inspect_opts = Application.get_env(:logger, :translator_inspect_opts)
{:ok, msg}
%{
client_info: client,
last_message: last,
name: name,
reason: reason,
state: state
} = report
{formatted, _reason} = format_reason(reason)
msg =
["GenServer ", inspect(name), " terminating", formatted] ++
["\nLast message", format_last_message_from(client), ": ", inspect(last, inspect_opts)]
if min_level == :debug do
{:ok, [msg, "\nState: ", inspect(state, inspect_opts) | format_client_info(client)]}
else
{:ok, msg}
end
end
defp translate_supervisor(
min_level,
supervisor: sup,
errorContext: context,
reason: reason,
offender: [{:pid, _pid}, {name_or_id, name} | offender]
)
when name_or_id in [:name, :id] do
msg =
["Child ", inspect(name), " of Supervisor ", sup_name(sup)] ++
[?\s, sup_context(context), "\n** (exit) ", offender_reason(reason, context)] ++
child_info(min_level, offender)
defp report_gen_event_terminate(min_level, report) do
inspect_opts = Application.get_env(:logger, :translator_inspect_opts)
{:ok, msg}
%{
handler: handler,
last_message: last,
name: name,
reason: reason,
state: state
} = report
reason =
case reason do
{:EXIT, why} -> why
_ -> reason
end
{formatted, _reason} = format_reason(reason)
msg =
["GenEvent handler ", inspect(handler), " installed in ", inspect(name), " terminating"] ++
[formatted, "\nLast message: ", inspect(last, inspect_opts)]
if min_level == :debug do
{:ok, [msg, "\nState: ", inspect(state, inspect_opts)]}
else
{:ok, msg}
end
end
defp translate_supervisor(
defp report_supervisor_progress(
min_level,
supervisor: sup,
errorContext: context,
reason: reason,
offender: [{:pid, pid} | offender]
started: [{:pid, pid}, {:id, id} | started]
) do
msg =
["Child of Supervisor ", sup_name(sup), ?\s, sup_context(context), "\n** (exit) "] ++
[offender_reason(reason, context), "\nPid: ", inspect(pid)] ++
child_info(min_level, offender)
["Child ", inspect(id), " of Supervisor ", sup_name(sup), " started"] ++
["\nPid: ", inspect(pid)] ++ child_info(min_level, started)
{:ok, msg}
end
defp translate_supervisor(
min_level,
supervisor: sup,
errorContext: context,
reason: reason,
offender: [{:nb_children, n}, {name_or_id, name} | offender]
)
when name_or_id in [:name, :id] do
msg =
["Children ", inspect(name), " of Supervisor ", sup_name(sup), ?\s, sup_context(context)] ++
["\n** (exit) ", offender_reason(reason, context), "\nNumber: ", inspect(n)] ++
child_info(min_level, offender)
{:ok, msg}
end
defp translate_supervisor(_min_level, _other), do: :none
defp translate_progress(_min_level, application: app, started_at: node_name) do
{:ok, ["Application ", to_string(app), " started at " | inspect(node_name)]}
end
defp translate_progress(
min_level,
supervisor: sup,
started: [{:pid, pid}, {name_or_id, name} | started]
)
when name_or_id in [:name, :id] do
msg =
["Child ", inspect(name), " of Supervisor ", sup_name(sup)] ++
[" started", "\nPid: ", inspect(pid)] ++ child_info(min_level, started)
{:ok, msg}
end
defp translate_progress(
defp report_supervisor_progress(
min_level,
supervisor: sup,
started: [{:pid, pid} | started]
@@ -194,7 +242,70 @@ defmodule Logger.Translator do
{:ok, msg}
end
defp translate_progress(_min_level, _other), do: :none
defp report_supervisor_progress(_min_level, _other), do: :none
defp report_supervisor(
min_level,
supervisor: sup,
errorContext: context,
reason: reason,
offender: [{:pid, pid}, {:id, id} | offender]
) do
pid_info =
if is_pid(pid) and context != :shutdown do
["\nPid: ", inspect(pid)]
else
[]
end
msg =
["Child ", inspect(id), " of Supervisor ", sup_name(sup)] ++
[?\s, sup_context(context), "\n** (exit) ", offender_reason(reason, context)] ++
pid_info ++ child_info(min_level, offender)
{:ok, msg}
end
defp report_supervisor(
min_level,
supervisor: sup,
errorContext: context,
reason: reason,
offender: [{:nb_children, n}, {:id, id} | offender]
) do
msg =
["Children ", inspect(id), " of Supervisor ", sup_name(sup), ?\s, sup_context(context)] ++
["\n** (exit) ", offender_reason(reason, context), "\nNumber: ", Integer.to_string(n)] ++
child_info(min_level, offender)
{:ok, msg}
end
defp report_supervisor(
min_level,
supervisor: sup,
errorContext: context,
reason: reason,
offender: [{:pid, pid} | offender]
) do
msg =
["Child of Supervisor ", sup_name(sup), ?\s, sup_context(context)] ++
["\n** (exit) ", offender_reason(reason, context), "\nPid: ", inspect(pid)] ++
child_info(min_level, offender)
{:ok, msg}
end
defp report_supervisor(_min_level, _other), do: :none
# If start call raises reason will be of form {:EXIT, reason}
defp offender_reason({:EXIT, reason}, :start_error) do
Exception.format_exit(reason)
end
defp offender_reason(reason, _context) do
Exception.format_exit(reason)
end
defp sup_name({:local, name}), do: inspect(name)
defp sup_name({:global, name}), do: inspect(name)
@@ -210,14 +321,20 @@ defmodule Logger.Translator do
["\nStart Call: ", format_mfa(mod, fun, args) | child_debug(min_level, debug)]
end
# Comes from bridge with MFA
defp child_info(min_level, [{:mfa, {mod, fun, args}} | debug]) do
["\nStart Call: ", format_mfa(mod, fun, args) | child_debug(min_level, debug)]
end
# Comes from bridge with Mod
defp child_info(min_level, [{:mod, mod} | debug]) do
["\nStart Module: ", inspect(mod) | child_debug(min_level, debug)]
end
defp child_info(_min_level, _child) do
[]
end
defp child_debug(:debug, restart_type: restart, shutdown: shutdown, child_type: type) do
["\nRestart: ", inspect(restart), "\nShutdown: ", inspect(shutdown)] ++
["\nType: ", inspect(type)]
@@ -227,16 +344,7 @@ defmodule Logger.Translator do
[]
end
# If start call raises reason will be of form {:EXIT, reason}
defp offender_reason({:EXIT, reason}, :start_error) do
Exception.format_exit(reason)
end
defp offender_reason(reason, _context) do
Exception.format_exit(reason)
end
defp translate_crash(min_level, [
defp report_crash(min_level, [
[
{:initial_call, _} = initial_call,
{:pid, pid},
@@ -245,14 +353,16 @@ defmodule Logger.Translator do
],
linked
]) do
reason = Exception.normalize(kind, exception, stack)
msg =
["Process ", crash_name(pid, name), " terminating", format(kind, exception, stack)] ++
["Process ", crash_name(pid, name), " terminating", format(kind, reason, stack)] ++
[crash_info(min_level, [initial_call | crashed])] ++ crash_linked(min_level, linked)
{:ok, msg}
end
defp translate_crash(min_level, [
defp report_crash(min_level, [
[
{:pid, pid},
{:registered_name, name},
@@ -260,8 +370,10 @@ defmodule Logger.Translator do
],
linked
]) do
reason = Exception.normalize(kind, exception, stack)
msg =
["Process ", crash_name(pid, name), " terminating", format(kind, exception, stack)] ++
["Process ", crash_name(pid, name), " terminating", format(kind, reason, stack)] ++
[crash_info(min_level, crashed), crash_linked(min_level, linked)]
{:ok, msg}
@@ -309,7 +421,6 @@ defmodule Logger.Translator do
defp crash_debug(:current_stacktrace, stack, prefix) do
stack_prefix = [prefix | " "]
stacktrace = Enum.map(stack, &[stack_prefix | Exception.format_stacktrace_entry(&1)])
[prefix, "Current Stacktrace:" | stacktrace]
end
@@ -339,43 +450,59 @@ defmodule Logger.Translator do
end)
end
defp crash_neighbour(min_level, [{:pid, pid}, {:registered_name, []} | info]) do
indent = " "
@indent " "
[?\n, indent, inspect(pid) | crash_info(min_level, info, [?\n, indent | indent])]
defp crash_neighbour(min_level, [{:pid, pid}, {:registered_name, []} | info]) do
[?\n, @indent, inspect(pid) | crash_info(min_level, info, [?\n, @indent | @indent])]
end
defp crash_neighbour(min_level, [{:pid, pid}, {:registered_name, name} | info]) do
indent = " "
[?\n, indent, inspect(name), " (", inspect(pid), ")"] ++
crash_info(min_level, info, [?\n, indent | indent])
[?\n, @indent, inspect(name), " (", inspect(pid), ")"] ++
crash_info(min_level, info, [?\n, @indent | @indent])
end
defp format_stop({maybe_exception, [_ | _] = maybe_stacktrace} = reason) do
defp format_last_message_from({_, {name, _}}), do: [" (from ", inspect(name), ")"]
defp format_last_message_from({from, _}), do: [" (from ", inspect(from), ")"]
defp format_last_message_from(_), do: []
defp format_client_info({from, :dead}),
do: ["\nClient ", inspect(from), " is dead"]
defp format_client_info({from, :remote}),
do: ["\nClient ", inspect(from), " is remote on node ", inspect(node(from))]
defp format_client_info({_, {name, stacktrace}}),
do: ["\nClient ", inspect(name), " is alive\n" | format_stacktrace(stacktrace)]
defp format_client_info(_),
do: []
defp format_reason({maybe_exception, [_ | _] = maybe_stacktrace} = reason) do
try do
format_stacktrace(maybe_stacktrace)
catch
:error, _ ->
format_stop_banner(reason)
{format_stop(reason), {reason, []}}
else
formatted_stacktrace ->
[format_stop_banner(maybe_exception, maybe_stacktrace) | formatted_stacktrace]
{formatted, reason} = maybe_normalize(maybe_exception, maybe_stacktrace)
{[formatted | formatted_stacktrace], {reason, maybe_stacktrace}}
end
end
defp format_stop(reason) do
format_stop_banner(reason)
defp format_reason(reason) do
{format_stop(reason), {reason, []}}
end
defp format_stop_banner(reason) do
defp format_stop(reason) do
["\n** (stop) " | Exception.format_exit(reason)]
end
# OTP processes rewrite the :undef error to these reasons when logging
# Erlang processes rewrite the :undef error to these reasons when logging
# TODO: This clause can be removed when we support only Erlang/OTP 21+.
@gen_undef [:"module could not be loaded", :"function not exported"]
defp format_stop_banner(undef, [{mod, fun, args, _info} | _] = stacktrace)
defp maybe_normalize(undef, [{mod, fun, args, _info} | _] = stacktrace)
when undef in @gen_undef and is_atom(mod) and is_atom(fun) do
cond do
is_list(args) ->
@@ -385,47 +512,44 @@ defmodule Logger.Translator do
format_undef(mod, fun, args, undef, stacktrace)
true ->
format_stop_banner(undef)
{format_stop(undef), undef}
end
end
defp format_stop_banner(reason, stacktrace) do
defp maybe_normalize(reason, stacktrace) do
# If this is already an exception (even an ErlangError), we format it as an
# exception. Otherwise, we try to normalize it, and if it's normalized as an
# ErlangError we instead format it as an exit.
if Exception.exception?(reason) do
[?\n | Exception.format_banner(:error, reason, stacktrace)]
{[?\n | Exception.format_banner(:error, reason, stacktrace)], reason}
else
case Exception.normalize(:error, reason, stacktrace) do
%ErlangError{} ->
format_stop_banner(reason)
{format_stop(reason), reason}
exception ->
[?\n | Exception.format_banner(:error, exception, stacktrace)]
{[?\n | Exception.format_banner(:error, exception, stacktrace)], exception}
end
end
end
defp format_undef(mod, fun, arity, undef, stacktrace) do
opts = [module: mod, function: fun, arity: arity, reason: undef]
exception = UndefinedFunctionError.exception(opts)
[?\n | Exception.format_banner(:error, exception, stacktrace)]
end
defp format(kind, payload, stacktrace) do
[?\n, Exception.format_banner(kind, payload, stacktrace) | format_stacktrace(stacktrace)]
end
defp format_stacktrace(stacktrace) do
for entry <- stacktrace do
[<<"\n ">> | Exception.format_stacktrace_entry(entry)]
["\n " | Exception.format_stacktrace_entry(entry)]
end
end
defp format_mfa(mod, fun, :undefined),
do: [inspect(mod), ?., Code.Identifier.inspect_as_function(fun) | "/?"]
defp format_mfa(mod, fun, args), do: Exception.format_mfa(mod, fun, args)
defp format_mfa(mod, fun, args),
do: Exception.format_mfa(mod, fun, args)
## Deprecated helpers
defp format_from([]), do: ""
defp format_from([from]), do: " (from #{inspect(from)})"
@@ -439,10 +563,16 @@ defmodule Logger.Translator do
end
defp format_client([from, stacktrace]) when is_list(stacktrace) do
["\nClient #{inspect(from)} is alive\n" | Exception.format_stacktrace(stacktrace)]
["\nClient #{inspect(from)} is alive\n" | format_stacktrace(stacktrace)]
end
defp format_client(_) do
[]
end
defp format_undef(mod, fun, arity, undef, stacktrace) do
opts = [module: mod, function: fun, arity: arity, reason: undef]
exception = UndefinedFunctionError.exception(opts)
{[?\n | Exception.format_banner(:error, exception, stacktrace)], exception}
end
end
+1 -1
View File
@@ -139,7 +139,7 @@ defmodule Logger.Utils do
# arguments according to the truncate limit.
{args, _} =
Enum.map_reduce(args, truncate, fn arg, acc ->
if is_binary(arg) do
if is_binary(arg) and acc != :infinity do
truncate_n(arg, acc)
else
{arg, acc}
-7
View File
@@ -20,13 +20,6 @@ defmodule Logger.Watcher do
def init({mod, handler, args}) do
Process.flag(:trap_exit, true)
# This is required for OTP 21. A better fix would be to not include
# the error_logger handler in the first place but we will do so only
# in future Elixir versions.
unless Process.whereis(mod) do
_ = :logger.add_handler(mod, mod, %{level: :info, filter_default: :log})
end
case :gen_event.delete_handler(mod, handler, :ok) do
{:error, :module_not_found} ->
case :gen_event.add_sup_handler(mod, handler, args) do
-1
View File
@@ -24,7 +24,6 @@ defmodule Logger.ConfigTest do
Process.whereis(Logger) |> Process.exit(:kill)
wait_for_logger()
wait_for_handler(Logger, Logger.Config)
wait_for_handler(:error_logger, Logger.ErrorHandler)
end
test "Logger.Config updates config on config_change/3" do
@@ -0,0 +1,109 @@
defmodule Logger.ErlangHandlerTest do
use Logger.Case
@moduletag :logger
defmodule CustomTranslator do
def t(:debug, :info, :format, {'hello: ~p', [:ok]}) do
:skip
end
def t(:debug, :info, :format, {'world: ~p', [:ok]}) do
{:ok, "rewritten"}
end
def t(:debug, :info, :report, {:logger, %{hello: :ok}}) do
:skip
end
def t(:debug, :info, :report, {:logger, %{world: :ok}}) do
{:ok, "rewritten"}
end
def t(:debug, :info, :report, {:logger, %{error: error}}) do
raise(error)
end
def t(_, _, _, _) do
:none
end
end
test "add_translator/1 and remove_translator/1 for error_logger" do
assert Logger.add_translator({CustomTranslator, :t})
assert capture_log(fn ->
:error_logger.info_msg('hello: ~p', [:ok])
end) == ""
assert capture_log(fn ->
:error_logger.info_msg('world: ~p', [:ok])
end) =~ "[info] rewritten"
after
assert Logger.remove_translator({CustomTranslator, :t})
end
test "add_translator/1 and remove_translator/1 for logger formats" do
assert Logger.add_translator({CustomTranslator, :t})
assert capture_log(fn ->
:logger.info('hello: ~p', [:ok])
end) == ""
assert capture_log(fn ->
:logger.info('world: ~p', [:ok])
end) =~ "[info] rewritten"
assert capture_log(fn ->
:logger.info(%{hello: :ok})
end) == ""
assert capture_log(fn ->
:logger.info(%{world: :ok})
end) =~ "[info] rewritten"
after
assert Logger.remove_translator({CustomTranslator, :t})
end
test "handles translation error" do
assert Logger.add_translator({CustomTranslator, :t})
message = capture_log(fn -> :logger.info(%{error: "oops"}) end)
assert message =~ "[info] Failure while translating Erlang's logger event\n"
assert message =~ "** (RuntimeError) oops\n"
after
assert Logger.remove_translator({CustomTranslator, :t})
end
test "converts Erlang metadata" do
Logger.configure_backend(:console, metadata: [:file, :line, :module, :function])
message =
capture_log(fn ->
:logger.info("ok", %{file: 'file.erl', line: 13, mfa: {Foo, :bar, 3}})
end)
assert message =~ "module=Foo"
assert message =~ "function=bar/3"
assert message =~ "file=file.erl"
assert message =~ "line=13"
after
Logger.configure_backend(:console, metadata: [])
end
test "uses reporting callback with Elixir inspection" do
assert capture_log(fn ->
callback = fn %{hello: :world} -> {"~p~n", [:formatted]} end
:logger.info(%{hello: :world}, %{report_cb: callback})
end) =~ "[info] :formatted"
end
test "converts log levels" do
assert capture_log(fn -> :logger.emergency('ok') end) =~ "[error] ok"
assert capture_log(fn -> :logger.alert('ok') end) =~ "[error] ok"
assert capture_log(fn -> :logger.critical('ok') end) =~ "[error] ok"
assert capture_log(fn -> :logger.error('ok') end) =~ "[error] ok"
assert capture_log(fn -> :logger.warning('ok') end) =~ "[warn] ok"
assert capture_log(fn -> :logger.info('ok') end) =~ "[info] ok"
assert capture_log(fn -> :logger.debug('ok') end) =~ "[debug] ok"
end
end
@@ -1,5 +1,34 @@
defmodule Logger.ErrorHandlerTest do
use Logger.Case
@moduletag :error_logger
test "add_translator/1 and remove_translator/1" do
defmodule CustomTranslator do
def t(:debug, :info, :format, {'hello: ~p', [:ok]}) do
:skip
end
def t(:debug, :info, :format, {'world: ~p', [:ok]}) do
{:ok, "rewritten"}
end
def t(_, _, _, _) do
:none
end
end
assert Logger.add_translator({CustomTranslator, :t})
assert capture_log(fn ->
:error_logger.info_msg('hello: ~p', [:ok])
end) == ""
assert capture_log(fn ->
:error_logger.info_msg('world: ~p', [:ok])
end) =~ "\[info\] rewritten"
after
assert Logger.remove_translator({CustomTranslator, :t})
end
test "survives after crashes" do
expected_result =
+12 -12
View File
@@ -339,15 +339,6 @@ defmodule Logger.TranslatorTest do
"""s
end
test "translates application stop" do
assert capture_log(fn ->
:ok = Application.start(:eex)
Application.stop(:eex)
end) =~ """
Application eex exited: :stopped
"""
end
test "translates application start" do
assert capture_log(fn ->
Application.start(:eex)
@@ -357,6 +348,15 @@ defmodule Logger.TranslatorTest do
"""
end
test "translates application stop" do
assert capture_log(fn ->
:ok = Application.start(:eex)
Application.stop(:eex)
end) =~ """
Application eex exited: :stopped
"""
end
test "translates Process crashes" do
assert capture_log(:info, fn ->
{_, ref} = spawn_monitor(fn -> raise "oops" end)
@@ -484,7 +484,7 @@ defmodule Logger.TranslatorTest do
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
end) =~ ~r"""
Ancestors: \[#PID<\d+\.\d+\.\d+>\](?:
Message Queue Length: 1(?#TODO: Require once depend on 20)|)
Message Queue Length: 1(?#TODO: Require once depend on Erlang/OTP 20)|)
Messages: \[:message\]
Links: \[\]
Dictionary: \[\]
@@ -516,7 +516,7 @@ defmodule Logger.TranslatorTest do
Stack Size: \d+
Reductions: \d+(?:
Current Stacktrace:
test/logger/translator_test.exs:\d+: Logger.TranslatorTest.sleep/1(?#TODO: Require once depend on 20)|)
test/logger/translator_test.exs:\d+: Logger.TranslatorTest.sleep/1(?#TODO: Require once depend on Erlang/OTP 20)|)
"""
end
@@ -775,7 +775,7 @@ defmodule Logger.TranslatorTest do
"""
end
test "handles :undefined MFA properly" do
test "reports :undefined MFA properly" do
defmodule WeirdFunctionNamesGenServer do
use GenServer
+14 -9
View File
@@ -5,9 +5,9 @@ defmodule Logger.UtilsTest do
import Kernel, except: [inspect: 2]
defp inspect(format, args) do
defp inspect(format, args, truncate \\ 10) do
format
|> Logger.Utils.scan_inspect(args, 10)
|> Logger.Utils.scan_inspect(args, truncate)
|> :io_lib.unscan_format()
end
@@ -81,13 +81,13 @@ defmodule Logger.UtilsTest do
assert truncate(long_string, :infinity) == long_string
end
test "inspect/2 formats" do
test "scan_inspect/3 formats" do
assert inspect('~p', [1]) == {'~ts', [["1"]]}
assert inspect("~p", [1]) == {'~ts', [["1"]]}
assert inspect(:"~p", [1]) == {'~ts', [["1"]]}
end
test "inspect/2 sigils" do
test "scan_inspect/3 sigils" do
assert inspect('~10.10tp', [1]) == {'~ts', [["1"]]}
assert inspect('~-10.10tp', [1]) == {'~ts', [["1"]]}
@@ -95,24 +95,24 @@ defmodule Logger.UtilsTest do
assert inspect('~10.10x~p~n', [1, 2, 3]) == {'~10.10x~ts~n', [1, 2, ["3"]]}
end
test "inspect/2 with modifier t has no effect (as it is the default)" do
test "scan_inspect/3 with modifier t has no effect (as it is the default)" do
assert inspect('~tp', [1]) == {'~ts', [["1"]]}
assert inspect('~tw', [1]) == {'~ts', [["1"]]}
end
test "inspect/2 with modifier l always prints lists" do
test "scan_inspect/3 with modifier l always prints lists" do
assert inspect('~lp', ['abc']) == {'~ts', [["[", "97", ",", " ", "98", ",", " ", "99", "]"]]}
assert inspect('~lw', ['abc']) == {'~ts', [["[", "97", ",", " ", "98", ",", " ", "99", "]"]]}
end
test "inspect/2 with modifier for width" do
test "scan_inspect/3 with modifier for width" do
assert inspect('~5lp', ['abc']) ==
{'~ts', [["[", "97", ",", "\n ", "98", ",", "\n ", "99", "]"]]}
assert inspect('~5lw', ['abc']) == {'~ts', [["[", "97", ",", " ", "98", ",", " ", "99", "]"]]}
end
test "inspect/2 with modifier for limit" do
test "scan_inspect/3 with modifier for limit" do
assert inspect('~5lP', ['abc', 2]) ==
{'~ts', [["[", "97", ",", "\n ", "98", ",", "\n ", "...", "]"]]}
@@ -120,13 +120,18 @@ defmodule Logger.UtilsTest do
{'~ts', [["[", "97", ",", " ", "98", ",", " ", "...", "]"]]}
end
test "inspect/2 truncates binaries" do
test "scan_inspect/3 truncates binaries" do
assert inspect('~ts', ["abcdeabcdeabcdeabcde"]) == {'~ts', ["abcdeabcde"]}
assert inspect('~ts~ts~ts', ["abcdeabcde", "abcde", "abcde"]) ==
{'~ts~ts~ts', ["abcdeabcde", "", ""]}
end
test "scan_inspect/3 with :infinity truncate" do
long_string = String.duplicate("foo", 10000)
assert inspect('~ts', [long_string], :infinity) == {'~ts', [long_string]}
end
test "timestamp/1" do
assert {{_, _, _}, {_, _, _, _}} = timestamp(true)
end
+1 -32
View File
@@ -4,44 +4,13 @@ defmodule LoggerTest do
setup_all do
Logger.configure_backend(:console, metadata: [:application, :module])
on_exit(fn ->
Logger.configure_backend(:console, metadata: [])
end)
on_exit(fn -> Logger.configure_backend(:console, metadata: []) end)
end
defp msg_with_meta(text) do
msg("module=LoggerTest #{text}")
end
test "add_translator/1 and remove_translator/1" do
defmodule CustomTranslator do
def t(:debug, :info, :format, {'hello: ~p', [:ok]}) do
:skip
end
def t(:debug, :info, :format, {'world: ~p', [:ok]}) do
{:ok, "rewritten"}
end
def t(_, _, _, _) do
:none
end
end
assert Logger.add_translator({CustomTranslator, :t})
assert capture_log(fn ->
:error_logger.info_msg('hello: ~p', [:ok])
end) == ""
assert capture_log(fn ->
:error_logger.info_msg('world: ~p', [:ok])
end) =~ "\[info\] rewritten"
after
assert Logger.remove_translator({CustomTranslator, :t})
end
test "add_backend/1 and remove_backend/1" do
assert :ok = Logger.remove_backend(:console)
assert Application.get_env(:logger, :backends) == []
+3 -1
View File
@@ -1,5 +1,7 @@
Logger.configure_backend(:console, colors: [enabled: false])
ExUnit.start()
exclude = if Process.whereis(:logger), do: [:error_logger], else: [:logger]
ExUnit.start(exclude: exclude)
defmodule Logger.Case do
use ExUnit.CaseTemplate
+7 -1
View File
@@ -84,7 +84,13 @@ defmodule Mix.Compilers.Elixir do
stale_local_deps = stale_local_deps(manifest, modified)
{modules, structs, changed} =
update_stale_entries(all_modules, all_sources, removed ++ changed, stale_local_deps, %{})
update_stale_entries(
all_modules,
all_sources,
removed ++ changed,
stale_local_deps,
stale_local_deps
)
stale = changed -- removed
+12
View File
@@ -18,6 +18,18 @@ defmodule Mix.Tasks.Cmd do
mix cmd --app app1 --app app2 echo pwd
Aborts when a command exits with a non-zero status.
## Zombie OS processes
Beware that the Erlang VM does not terminate child processes
when it shuts down. Therefore, if you use `mix cmd` to start
long running processes and then shutdown the VM, it is likely
that those child processes won't be terminated with the VM.
A solution is to make sure the child processes listen to the
stdndard input and terminate when standard input is closed.
We discuss this topic at length in the "Zombie OS processes"
of the `Port` module documentation.
"""
def run(args) do
+2 -2
View File
@@ -352,7 +352,7 @@ defmodule Mix.Tasks.Format do
map =
for input <- List.wrap(formatter_opts[:inputs]),
file <- Path.wildcard(Path.join(prefix ++ [input])),
file <- Path.wildcard(Path.join(prefix ++ [input]), match_dot: true),
do: {file, formatter_opts},
into: %{}
@@ -375,7 +375,7 @@ defmodule Mix.Tasks.Format do
end
defp stdin_or_wildcard("-"), do: [:stdin]
defp stdin_or_wildcard(path), do: Path.wildcard(path)
defp stdin_or_wildcard(path), do: Path.wildcard(path, match_dot: true)
defp read_file(:stdin) do
{IO.stream(:stdio, :line) |> Enum.to_list() |> IO.iodata_to_binary(), file: "stdin"}
+1 -1
View File
@@ -7,7 +7,7 @@ defmodule Mix.Tasks.Run do
Starts and runs the current application.
`mix run` can be used to start the current application dependencies
ant the application itself. For long running systems, this is typically
and the application itself. For long running systems, this is typically
done with the `--no-halt` option:
mix run --no-halt
+28
View File
@@ -180,6 +180,34 @@ defmodule Mix.Tasks.FormatTest do
end)
end
test "expands patterns in inputs from .formatter.exs", context do
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
[
inputs: ["{a,.b}.ex"]
]
""")
File.write!("a.ex", """
foo bar
""")
File.write!(".b.ex", """
foo bar
""")
Mix.Tasks.Format.run([])
assert File.read!("a.ex") == """
foo(bar)
"""
assert File.read!(".b.ex") == """
foo(bar)
"""
end)
end
test "uses inputs and configuration from --dot-formatter", context do
in_tmp(context.test, fn ->
File.write!("custom_formatter.exs", """
+32 -3
View File
@@ -326,7 +326,7 @@ defmodule Mix.UmbrellaTest do
end
end
test "recompiles after path dependency changes" do
test "recompiles after runtime path dependency changes" do
in_fixture("umbrella_dep/deps/umbrella/apps", fn ->
Mix.Project.in_project(:bar, "bar", fn _ ->
Mix.Task.run("compile", ["--verbose"])
@@ -360,22 +360,51 @@ defmodule Mix.UmbrellaTest do
# Noop for runtime dependencies
mtime = File.stat!("_build/dev/lib/bar/.mix/compile.elixir").mtime
ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam", mtime)
mtime = File.stat!("_build/dev/lib/bar/.mix/compile.elixir").mtime
ensure_touched("_build/dev/lib/foo/.mix/compile.elixir", mtime)
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []}
end)
end)
end
test "recompiles after compile time path dependency changes" do
in_fixture("umbrella_dep/deps/umbrella/apps", fn ->
Mix.Project.in_project(:bar, "bar", fn _ ->
Mix.Task.run("compile", ["--verbose"])
# Add compile time dependency
File.write!("lib/bar.ex", "defmodule Bar, do: Foo.foo")
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
# Recompiles for compile time dependencies
mtime = File.stat!("_build/dev/lib/bar/.mix/compile.elixir").mtime
ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam", mtime)
ensure_touched("_build/dev/lib/foo/.mix/compile.elixir", mtime)
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
end)
end)
end
test "recompiles after struct path dependency changes" do
in_fixture("umbrella_dep/deps/umbrella/apps", fn ->
Mix.Project.in_project(:bar, "bar", fn _ ->
File.write!("../foo/lib/foo.ex", "defmodule Foo, do: defstruct [:bar]")
Mix.Task.run("compile", ["--verbose"])
# Add struct dependency
File.write!("lib/bar.ex", "defmodule Bar, do: %Foo{bar: true}")
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
# Recompiles for struct dependencies
mtime = File.stat!("_build/dev/lib/bar/.mix/compile.elixir").mtime
ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam", mtime)
ensure_touched("_build/dev/lib/foo/.mix/compile.elixir", mtime)
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}