%% 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 should 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}.