Current section

Files

Jump to
systemd src systemd_journal_h.erl
Raw

src/systemd_journal_h.erl

%% Licensed to the Apache Software Foundation (ASF) under one
%% or more contributor license agreements. See the NOTICE file
%% distributed with this work for additional information
%% regarding copyright ownership. The ASF licenses this file
%% to you under the Apache License, Version 2.0 (the
%% "License"); you may not use this file except in compliance
%% with the License. You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% @doc
%% Logger handler for sending messages to systemd's `journal'.
%%
%% == Usage ==
%%
%% Run this after the `systemd' application is started:
%%
%% ```
%% logger:add_handler(journal,
%% systemd_journal_h,
%% #{formatter => {systemd_journal_formatter, #{}}).
%% '''
%%
%% It is very important to use `systemd_journal_formatter' here, otherwise the
%% messages will not be recorded.
%%
%% == Warning ==
%%
%% This logger <b>should</b> always be used with `systemd_journal_formatter' unless
%% you are completely sure what you are trying to do. Otherwise you can loose your
%% log data.
%%
%% @since 0.3.0
%% @end
-module(systemd_journal_h).
-behaviour(gen_server).
-include("systemd_internal.hrl").
-define(JOURNAL_SOCKET, {local, <<"/run/systemd/journal/socket">>}).
% logger handler callbacks
-export([adding_handler/1,
% filter_config/1,
removing_handler/1,
log/2]).
% gen_server callbacks
-export([start_link/0,
init/1,
handle_call/3,
handle_cast/2,
handle_info/2]).
-define(FORMATTER, {systemd_journal_formatter, #{}}).
-define(CHILD_SPEC(Id, Args), #{id => Id,
start => {?MODULE, start_link, Args},
restart => temporary}).
% -----------------------------------------------------------------------------
% Logger Handler
%% @hidden
-spec adding_handler(logger:handler_config()) -> {ok, logger:handler_config()} |
{error, term()}.
adding_handler(HConfig) ->
Config = maps:get(config, HConfig, #{}),
Path = maps:get(path, Config, ?JOURNAL_SOCKET),
case start_connection(HConfig) of
{ok, Pid} ->
{ok, Socket} = gen_server:call(Pid, get),
{ok, HConfig#{config => Config#{pid => Pid,
socket => Socket,
path => Path}}};
Err ->
Err
end.
start_connection(#{id := Id}) ->
case supervisor:start_child(?SUPERVISOR, ?CHILD_SPEC(Id, [])) of
{ok, Pid} -> {ok, Pid};
{error, Error} -> {error, {spawn_error, Error}}
end.
%% @hidden
-spec removing_handler(logger:handler_config()) -> ok.
removing_handler(#{config := #{pid := Pid}}) ->
ok = gen_server:call(Pid, stop),
ok.
%% @hidden
-spec log(logger:log_event(), logger:handler_config()) -> ok.
log(LogEvent, #{config := #{socket := Socket, path := Path}}=Config) ->
{FMod, FConf} = maps:get(formatter, Config, ?FORMATTER),
Buffer = FMod:format(LogEvent, FConf),
case string:is_empty(Buffer) of
false ->
ok = gen_udp:send(Socket, Path, 0, Buffer);
true -> ok
end.
% -----------------------------------------------------------------------------
% Socket handler
%% @hidden
start_link() ->
gen_server:start_link(?MODULE, [], []).
%% @hidden
init(_Arg) ->
% We never receive on this socket, so we set is as {active, false}
{ok, Socket} = gen_udp:open(0, [binary, local, {active, false}]),
{ok, Socket}.
%% @hidden
% TODO: Implement async handler and overload protectopn
handle_call(get, _Ref, Socket) ->
{reply, {ok, Socket}, Socket};
handle_call(stop, _Ref, Socket) ->
{stop, normal, ok, Socket}.
%% @hidden
handle_cast(_Msg, Socket) ->
{noreply, Socket}.
%% @hidden
handle_info(_Msg, State) ->
{noreply, State}.