Disable I/O to the console if the old-style Erlang console is used
There's a nasty problem with the Erlang VM + lager when the old-style
Erlang console is used. You can use the "-oldshell" flag to explicitly
get the old-style shell. However, if the Erlang VM is started when
*not* associated with a pseudo-tty, the VM will silently use the
old-style shell (because the new-style shell requires a pty to
support command line editing, etc.). The most common way of starting
the Erlang VM without a pty is to start it via a non-interactive
SSH session.
This patch is opinionated in what to do in the case when the old-
style shell is detected. My opinion is:
1. Shout loudly to the console log (in a separate Erlang process, so
that the main lager event handler proc won't be blocked by any
problems with the old-style console). It's almost certainly
likely that you really don't want to run Erlang with the old-style
shell. But many sysadmins don't look closely at their systems'
log files, so (for example) shouting the same message 10x in a row
is legit. Reviewers: YMMV.
2. Set a SASL alarm. Again, many sysadmins' are bad at looking at
log messages. Setting a SASL alarm is another method to try to
get their attention.
To test, create a test script called `/tmp/testit.sh`, changing the
`-pz` parameter to point to the correct place for lager's `ebin`
subdirectory:
#!/bin/sh
erl -pz /Users/fritchie/b/src/lager/ebin -eval '{application:start(sasl), lager_console_backend:init([error, true]), timer:sleep(5*1000), io:format("\n\nInfo: ~p\n\n", [{{alarms, alarm_handler:get_alarms()}, {user_drv, whereis(user_drv)}}]), erlang:halt()}.'
Then run it twice, using the following:
First time: ssh -t localhost sh /tmp/testit.sh
Second time: ssh localhost sh /tmp/testit.sh
The last lines of the first time should look like:
Info: {{alarms,[]},{user_drv,<0.22.0>}}
The last lines of the second time should look like:
Info: {{alarms,
[{lager_console_backend,
"WARNING: old-style console is in use, so lager_console_backend log output to the console is disabled. Restart the VM on a pseudo-tty to ensure use of the new-style VM console."}]},
{user_drv,undefined}}
diff --git a/src/lager_console_backend.erl b/src/lager_console_backend.erl
index 8f968e0..c0dc4e1 100644
--- a/src/lager_console_backend.erl
+++ b/src/lager_console_backend.erl
@@ -40,6 +40,13 @@
init([Level,false]) -> % for backwards compatibility
init([Level,{lager_default_formatter,?TERSE_FORMAT ++ [eol()]}]);
init([Level,{Formatter,FormatterConfig}]) when is_atom(Formatter) ->
+ IsSafe = case is_new_style_console_available() of
+ false=Res ->
+ spawn(fun warn_user/0),
+ Res;
+ Res ->
+ Res
+ end,
Colors = case application:get_env(lager, colored) of
{ok, true} ->
{ok, LagerColors} = application:get_env(lager, colors),
@@ -47,8 +54,10 @@
_ -> []
end,
- try lager_util:config_to_mask(Level) of
- Levels ->
+ try {IsSafe, lager_util:config_to_mask(Level)} of
+ {false, _} ->
+ {error, "Old style console was detected"};
+ {true, Levels} ->
{ok, #state{level=Levels,
formatter=Formatter,
format_config=FormatterConfig,
@@ -109,6 +118,36 @@
end.
-ifdef(TEST).
+is_new_style_console_available() ->
+ true.
+-else.
+is_new_style_console_available() ->
+ %% Criteria:
+ %% 1. If the user has specified '-noshell' on the command line,
+ %% then we will pretend that the new-style console is available.
+ %% If there is no shell at all, then we don't have to worry
+ %% about log events being blocked by the old-style shell.
+ %% 2. If the user_drv process iss registered, all is OK.
+ %% 'user_drv' is a registered proc name used by the "new"
+ %% console driver.
+ init:get_argument(noshell) /= error orelse
+ is_pid(whereis(user_drv)).
+-endif.
+
+warn_user() ->
+ Msg = lists:flatten(
+ io_lib:format("WARNING: old-style console is in use, so ~s "
+ "log output to the console is disabled. "
+ "Restart the VM on a pseudo-tty to ensure "
+ "use of the new-style VM console.",
+ [?MODULE])),
+ catch alarm_handler:set_alarm({?MODULE, Msg}),
+ [begin
+ error_logger:warning_msg(Msg),
+ timer:sleep(1000)
+ end || _ <- lists:seq(1, 10)].
+
+-ifdef(TEST).
console_log_test_() ->
%% tiny recursive fun that pretends to be a group leader
F = fun(Self) ->