1%%%============================================================================= 2%%% Copyright 2016-2017, Tobias Schlager <schlagert@github.com> 3%%% 4%%% Permission to use, copy, modify, and/or distribute this software for any 5%%% purpose with or without fee is hereby granted, provided that the above 6%%% copyright notice and this permission notice appear in all copies. 7%%% 8%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15%%% 16%%% @doc 17%%% A backend for `lager' redirecting its messages into the `syslog' 18%%% application. Configure like this: 19%%% <pre> 20%%% {handlers, [{syslog_lager_backend, []}]}, 21%%% </pre> 22%%% 23%%% @see https://github.com/basho/lager 24%%% @end 25%%%============================================================================= 26-module(syslog_lager_backend). 27 28-behaviour(gen_event). 29 30%% API 31-export([set_log_level/1]). 32 33%% gen_event callbacks 34-export([init/1, 35 handle_event/2, 36 handle_call/2, 37 handle_info/2, 38 terminate/2, 39 code_change/3]). 40 41-include("syslog.hrl"). 42 43-define(CFG, [message]). 44 45%% avoid warnings when lager is not a project dependency 46-dialyzer(no_missing_calls). 47 48%%%============================================================================= 49%%% API 50%%%============================================================================= 51 52%%------------------------------------------------------------------------------ 53%% @doc 54%% Set a specific log level for this lager backend, never fails. 55%% @end 56%%------------------------------------------------------------------------------ 57-spec set_log_level(syslog:severity() | info) -> ok. 58set_log_level(informational) -> 59 set_log_level(info); 60set_log_level(Level) -> 61 catch lager:set_loglevel(?MODULE, Level), 62 ok. 63 64%%%============================================================================= 65%%% gen_event callbacks 66%%%============================================================================= 67 68-record(state, { 69 log_level :: integer() | {mask, integer()}, 70 formatter :: atom(), 71 format_cfg :: list(), 72 sd_id :: string() | undefined, 73 metadata_keys :: [atom()], 74 appname_key :: atom() | undefined}). 75 76%%------------------------------------------------------------------------------ 77%% @private 78%%------------------------------------------------------------------------------ 79init([]) -> 80 init([syslog_lib:get_property(log_level, ?SYSLOG_LOGLEVEL)]); 81init([Level]) -> 82 init([Level, {}, {lager_default_formatter, ?CFG}]); 83init([Level, {}, {Formatter, FormatterConfig}]) when is_atom(Formatter) -> 84 init([Level, {undefined, []}, {Formatter, FormatterConfig}]); 85init([Level, SData, {Formatter, FormatterConfig}]) 86 when is_atom(Formatter) -> 87 AppnameKey = syslog_lib:get_name_metdata_key(), 88 init([Level, SData, {Formatter, FormatterConfig}, AppnameKey]); 89init([Level, {}, {Formatter, FormatterConfig}, AppnameKey]) 90 when is_atom(Formatter) -> 91 init([Level, {undefined, []}, {Formatter, FormatterConfig}, AppnameKey]); 92init([Level, {SDataId, MDKeys}, {Formatter, FormatterConfig}, AppnameKey]) 93 when is_atom(Formatter) -> 94 {ok, #state{ 95 log_level = level_to_mask(Level), 96 sd_id = SDataId, 97 metadata_keys = MDKeys, 98 formatter = Formatter, 99 format_cfg = FormatterConfig, 100 appname_key = get_appname_key(AppnameKey)}}. 101 102%%------------------------------------------------------------------------------ 103%% @private 104%%------------------------------------------------------------------------------ 105handle_event({log, Level, _, [_, Location, Message]}, State) 106 when Level =< State#state.log_level -> 107 Severity = get_severity(Level), 108 Pid = get_pid(Location), 109 Timestamp = os:timestamp(), 110 syslog_logger:log(Severity, Pid, Timestamp, [], Message, no_format), 111 {ok, State}; 112handle_event({log, Msg}, State = #state{log_level = Level}) -> 113 case lager_util:is_loggable(Msg, Level, ?MODULE) of 114 true -> 115 syslog_logger:log(get_severity(Msg), 116 get_pid(Msg), 117 lager_msg:timestamp(Msg), 118 get_structured_data(Msg, State), 119 format_msg(Msg, State), 120 no_format, 121 get_appname_override(Msg, State)); 122 false -> 123 ok 124 end, 125 {ok, State}; 126handle_event(_Event, State) -> 127 {ok, State}. 128 129%%------------------------------------------------------------------------------ 130%% @private 131%%------------------------------------------------------------------------------ 132handle_call(get_loglevel, State = #state{log_level = Level}) -> 133 {ok, Level, State}; 134handle_call({set_loglevel, Level}, State) -> 135 try 136 {ok, ok, State#state{log_level = level_to_mask(Level)}} 137 catch 138 _:_ -> {ok, {error, {bad_log_level, Level}}, State} 139 end; 140handle_call(_Request, State) -> 141 {ok, undef, State}. 142 143%%------------------------------------------------------------------------------ 144%% @private 145%%------------------------------------------------------------------------------ 146handle_info(_Info, State) -> {ok, State}. 147 148%%------------------------------------------------------------------------------ 149%% @private 150%%------------------------------------------------------------------------------ 151terminate(_Arg, #state{}) -> ok. 152 153%%------------------------------------------------------------------------------ 154%% @private 155%%------------------------------------------------------------------------------ 156code_change(_OldVsn, State, _Extra) -> {ok, State}. 157 158%%%============================================================================= 159%%% internal functions 160%%%============================================================================= 161 162%%------------------------------------------------------------------------------ 163%% @private 164%%------------------------------------------------------------------------------ 165get_severity(info) -> 166 informational; 167get_severity(Level) when is_atom(Level) -> 168 Level; 169get_severity(Level) when is_integer(Level) -> 170 get_severity(lager_util:num_to_level(Level)); 171get_severity(Msg) -> 172 get_severity(lager_msg:severity(Msg)). 173 174%%------------------------------------------------------------------------------ 175%% @private 176%%------------------------------------------------------------------------------ 177get_pid(Location) when is_list(Location) -> 178 Location; 179get_pid(Msg) -> 180 try lager_msg:metadata(Msg) of 181 Metadata -> 182 case lists:keyfind(pid, 1, Metadata) of 183 {pid, Pid} when is_pid(Pid) -> Pid; 184 {pid, List} when is_list(List) -> List; 185 _ -> self() 186 end 187 catch 188 _:_ -> self() 189 end. 190 191%%------------------------------------------------------------------------------ 192%% @private 193%%------------------------------------------------------------------------------ 194level_to_mask(informational) -> 195 level_to_mask(info); 196level_to_mask(Level) -> 197 try 198 lager_util:config_to_mask(Level) 199 catch 200 error:undef -> lager_util:level_to_num(Level) 201 end. 202 203%%------------------------------------------------------------------------------ 204%% @private 205%%------------------------------------------------------------------------------ 206get_structured_data(_Msg, #state{sd_id = undefined}) -> 207 []; 208get_structured_data(_Msg, #state{metadata_keys = []}) -> 209 []; 210get_structured_data(Msg, #state{sd_id = SDId, metadata_keys = MDKeys}) -> 211 try lager_msg:metadata(Msg) of 212 Metadata -> syslog_lib:get_structured_data(Metadata, SDId, MDKeys) 213 catch 214 _:_ -> [] 215 end. 216 217%%------------------------------------------------------------------------------ 218%% @private 219%% Map `true' to `application' for backwards compatibility. 220%%------------------------------------------------------------------------------ 221get_appname_key(true) -> application; 222get_appname_key(false) -> undefined; 223get_appname_key(Value) when is_atom(Value) -> Value. 224 225%%------------------------------------------------------------------------------ 226%% @private 227%%------------------------------------------------------------------------------ 228get_appname_override(_, #state{appname_key = undefined}) -> 229 []; 230get_appname_override(Msg, #state{appname_key = Key}) -> 231 try lager_msg:metadata(Msg) of 232 Metadata -> 233 case proplists:get_value(Key, Metadata) of 234 undefined -> []; 235 Result -> [{appname, Result}] 236 end 237 catch 238 _:_ -> [] 239 end. 240 241%%------------------------------------------------------------------------------ 242%% @private 243%%------------------------------------------------------------------------------ 244format_msg(Msg, #state{formatter = Formatter, format_cfg = FormatterConfig}) -> 245 Formatter:format(Msg, FormatterConfig). 246