Added syslog logger.
authorStanislaw Klekot <dozzie@jarowit.net>
Wed, 17 Sep 2014 22:22:41 +0000 (00:22 +0200)
committerStanislaw Klekot <dozzie@jarowit.net>
Wed, 17 Sep 2014 22:22:41 +0000 (00:22 +0200)
src/haircut.app.src
src/haircut_log_operational.erl
src/haircut_log_sup.erl
src/haircut_syslog.erl [new file with mode: 0644]
src/haircut_syslog_term_h.erl [new file with mode: 0644]

index a9a61e1..017c987 100644 (file)
@@ -4,6 +4,7 @@
   {registered, [
     haircut_sup,
     haircut_commander,
+    haircut_syslog,
     haircut_log_sup,
     haircut_log_activity,
     haircut_log_operational,
index 20aee50..40dd751 100644 (file)
@@ -26,8 +26,8 @@ start_link() ->
 
 add_handlers(_Pid, none = _LogSink) ->
   ok;
-add_handlers(_Pid, syslog = _LogSink) ->
-  'TODO';
+add_handlers(Pid, syslog = _LogSink) ->
+  gen_event:add_handler(Pid, haircut_syslog_term_h, []);
 add_handlers(Pid, LogSink) when is_list(LogSink) ->
   gen_event:add_handler(Pid, haircut_file_term_h, LogSink).
 
index 03e4d41..a82e78e 100644 (file)
@@ -24,8 +24,10 @@ start_link() ->
 %%% supervisor callbacks
 
 init([] = _Args) ->
-  % TODO: haircut_log_syslog, but some time later it will get moved to Indira
   Children = [
+    {haircut_syslog,
+      {haircut_syslog, start_link, []},
+      permanent, 5000, worker, [haircut_syslog]},
     {haircut_log_activity,
       {haircut_log_activity, start_link, []},
       permanent, 5000, worker, dynamic},
diff --git a/src/haircut_syslog.erl b/src/haircut_syslog.erl
new file mode 100644 (file)
index 0000000..6ff19c1
--- /dev/null
@@ -0,0 +1,131 @@
+%%%---------------------------------------------------------------------------
+%%% @doc
+%%%   Syslog connector.
+%%%
+%%% @TODO Move this to Indira.
+%%% @end
+%%%---------------------------------------------------------------------------
+
+-module(haircut_syslog).
+
+-behaviour(gen_server).
+
+%%% public API
+-export([start_link/0]).
+
+%%% gen_server callbacks
+-export([init/1, terminate/2]).
+-export([handle_call/3, handle_cast/2, handle_info/2]).
+-export([code_change/3]).
+
+%%%---------------------------------------------------------------------------
+
+-record(state, {
+  path = "/dev/log",
+  handle
+}).
+
+%%%---------------------------------------------------------------------------
+%%% public API {{{
+
+%% @doc Start syslog connector process.
+
+-spec start_link() ->
+  {ok, pid()} | {error, term()}.
+
+start_link() ->
+  gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+%%% }}}
+%%%---------------------------------------------------------------------------
+%%% gen_server callbacks
+
+%%----------------------------------------------------------
+%% initialization and cleanup {{{
+
+%% @private
+%% @doc Initialize {@link gen_server} state.
+
+init(_Args) ->
+  {ok, #state{}}.
+
+%% @private
+%% @doc Clean up {@link gen_server} state.
+
+terminate(_Reason, _State = #state{handle = undefined}) ->
+  ok;
+terminate(_Reason, _State = #state{handle = Syslog}) ->
+  indira_syslog:close(Syslog).
+
+%% }}}
+%%----------------------------------------------------------
+%% communication {{{
+
+%% @private
+%% @doc Handle {@link gen_server:call/2}.
+
+handle_call(_Request, _From, State) ->
+  {reply, {error, unknown}, State}.
+
+%% @private
+%% @doc Handle {@link gen_server:cast/2}.
+
+handle_cast(_Request, State) ->
+  {noreply, State}.
+
+%% @private
+%% @doc Handle incoming messages.
+
+handle_info({syslog, _Ident, _Facility, _Priority, _Data} = Msg,
+            State = #state{path = Path, handle = undefined}) ->
+  case indira_syslog:open_local(Path) of
+    {ok, Syslog} ->
+      handle_info(Msg, State#state{handle = Syslog});
+    {error, _Reason} ->
+      {noreply, State}
+  end;
+
+handle_info({syslog, Ident, Facility, Priority, Data} = _Msg,
+            State = #state{handle = Syslog}) ->
+  % 2G columns should be \infty, term should fit in this width
+  DataLine = io_lib:print(Data, 1, 16#ffffffff, -1),
+  Line = indira_syslog:format(Facility, Priority, Ident, DataLine),
+  case indira_syslog:send(Syslog, Line) of
+    ok ->
+      {noreply, State};
+    {error, _Reason} ->
+      indira_syslog:close(Syslog),
+      NewSyslog = try_reconnect_and_send(State#state.path, Line),
+      {noreply, State#state{handle = NewSyslog}}
+  end;
+
+handle_info(_Msg, State) ->
+  {noreply, State}.
+
+%% }}}
+%%----------------------------------------------------------
+%% code change {{{
+
+%% @private
+%% @doc Handle code change.
+
+code_change(_OldVsn, State, _Extra) ->
+  {ok, State}.
+
+%% }}}
+%%----------------------------------------------------------
+
+%%%---------------------------------------------------------------------------
+%%% helper functions
+
+try_reconnect_and_send(Path, Line) ->
+  case indira_syslog:open_local(Path) of
+    {ok, Syslog} ->
+      indira_syslog:send(Syslog, Line), % this may fail, but we won't care
+      Syslog;
+    {error, _Reason} ->
+      undefined
+  end.
+
+%%%---------------------------------------------------------------------------
+%%% vim:ft=erlang:foldmethod=marker
diff --git a/src/haircut_syslog_term_h.erl b/src/haircut_syslog_term_h.erl
new file mode 100644 (file)
index 0000000..9d30cff
--- /dev/null
@@ -0,0 +1,95 @@
+%%%---------------------------------------------------------------------------
+%%% @doc
+%%%   Log handler for {@link error_logger} writing to syslog with term
+%%%   formatting. This handler writes all events (without regard to whether it
+%%%   was `*_msg' or `*_report') to a file formatted as Erlang term.
+%%% @end
+%%%---------------------------------------------------------------------------
+
+-module(haircut_syslog_term_h).
+
+-behaviour(gen_event).
+
+%%% gen_event callbacks
+-export([init/1, terminate/2]).
+-export([handle_event/2, handle_call/2, handle_info/2]).
+-export([code_change/3]).
+
+%%%---------------------------------------------------------------------------
+%%% types
+
+-record(state, {}).
+
+%%%---------------------------------------------------------------------------
+%%% gen_event callbacks
+
+%%----------------------------------------------------------
+%% initialization and cleanup {{{
+
+%% @private
+%% @doc Initialize {@link gen_event} state.
+
+init(_Args) ->
+  {ok, #state{}}.
+
+%% @private
+%% @doc Clean up {@link gen_event} state.
+
+terminate(_Reason, _State) ->
+  ok.
+
+%% }}}
+%%----------------------------------------------------------
+%% communication {{{
+
+%% @private
+%% @doc Handle {@link gen_event:notify/2}.
+
+handle_event({Level, Message} = _Event, State) ->
+  try_syslog(Level, Message),
+  {ok, State};
+
+handle_event(Event, State) ->
+  try_syslog(unknown, Event),
+  {ok, State}.
+
+%% @private
+%% @doc Handle {@link gen_event:call/3}.
+
+handle_call(_Request, State) ->
+  {ok, {error, unknown}, State}.
+
+%% @private
+%% @doc Handle incoming messages.
+
+handle_info(_Msg, State) ->
+  {ok, State}.
+
+%% }}}
+%%----------------------------------------------------------
+%% code change {{{
+
+%% @private
+%% @doc Handle code change.
+
+code_change(_OldVsn, State, _Extra) ->
+  {ok, State}.
+
+%% }}}
+%%----------------------------------------------------------
+
+try_syslog(Level, Message) ->
+  SyslogPriority = case Level of
+    info     -> info;
+    error    -> err;
+    critical -> crit;
+    _        -> warning
+  end,
+  try
+    haircut_syslog ! {syslog, haircut, daemon, SyslogPriority, Message}
+  catch
+    error:badarg -> ignore
+  end.
+
+%%%---------------------------------------------------------------------------
+%%% vim:ft=erlang:foldmethod=marker