1%%% Copyright 2009 Andrew Thompson <andrew@hijacked.us>. All rights reserved. 2%%% 3%%% Redistribution and use in source and binary forms, with or without 4%%% modification, are permitted provided that the following conditions are met: 5%%% 6%%% 1. Redistributions of source code must retain the above copyright notice, 7%%% this list of conditions and the following disclaimer. 8%%% 2. Redistributions in binary form must reproduce the above copyright 9%%% notice, this list of conditions and the following disclaimer in the 10%%% documentation and/or other materials provided with the distribution. 11%%% 12%%% THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR 13%%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 14%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 15%%% EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 16%%% INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 17%%% (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 18%%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 19%%% ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 20%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21%%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 23%% @doc erlang wrapper for syslog port 24 25-module(syslog). 26 27-behaviour(gen_server). 28 29-define(DRV_NAME, "syslog_drv"). 30 31%% this constant must match the same in syslog_drv.c 32-define(SYSLOGDRV_OPEN, 1). 33 34%% API 35-export([ 36 start/0, 37 start_link/0, 38 stop/0, 39 open/3, 40 log/3, 41 log/4, 42 close/1, 43 priority/1, 44 facility/1, 45 openlog_opt/1, 46 openlog_opts/1, 47 load/0, 48 unload/0 49 ]). 50 51%% gen_server callbacks 52-export([ 53 init/1, 54 handle_call/3, 55 handle_cast/2, 56 handle_info/2, 57 terminate/2, 58 code_change/3 59 ]). 60 61-ifdef(TEST). 62-include_lib("eunit/include/eunit.hrl"). 63-endif. 64 65-record(state, {}). 66 67-type priority() :: emerg | alert | crit | err | 68 warning | notice | info | debug | non_neg_integer(). 69-type facility() :: kern | user | mail | daemon | auth | syslog | 70 lpr | news | uucp | cron | authpriv | ftp | 71 netinfo | remoteauth | install | ras | 72 local0 | local1 | local2 | local3 | 73 local4 | local5 | local6 | local7 | non_neg_integer(). 74-type openlog_opt() :: pid | cons | odelay | ndelay | perror | pos_integer(). 75-export_type([priority/0, facility/0, openlog_opt/0]). 76 77%%% API %%% 78 79-spec start() -> 80 {ok, pid()} | ignore | {error, any()}. 81 82start() -> 83 gen_server:start({local, ?MODULE}, ?MODULE, [], []). 84 85-spec start_link() -> 86 {ok, pid()} | ignore | {error, any()}. 87 88start_link() -> 89 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 90 91-spec stop() -> 92 ok. 93 94stop() -> 95 gen_server:cast(?MODULE, stop). 96 97-spec open(Ident :: string(), 98 Logopt :: list(openlog_opt()), 99 Facility :: facility()) -> 100 {ok, port()} | 101 {error, any()}. 102 103open(Ident, Logopt, Facility) -> 104 Log = erlang:open_port({spawn, ?DRV_NAME}, [binary]), 105 Args = term_to_binary({Ident, openlog_opts(Logopt), facility(Facility)}), 106 try erlang:port_control(Log, ?SYSLOGDRV_OPEN, Args) of 107 <<>> -> 108 {ok, Log}; 109 BinError -> 110 binary_to_term(BinError) 111 catch 112 _:Reason -> 113 {error, Reason} 114 end. 115 116-spec log(Log :: port(), 117 Priority :: priority(), 118 Message :: iolist()) -> 119 ok. 120 121log(_Log, _Priority, []) -> 122 ok; 123log(Log, Priority, Message) -> 124 NumPri = priority(Priority), 125 %% encode the priority value as a 4-byte integer in network order, and 126 %% add a 0 byte to the end of the command data to act as a NUL character 127 true = erlang:port_command(Log, [<<NumPri:32/big>>, Message, <<0:8>>]), 128 ok. 129 130-spec log(Log :: port(), 131 Priority :: priority(), 132 FormatStr :: string(), 133 FormatArgs :: list()) -> 134 ok. 135 136log(Log, Priority, FormatStr, FormatArgs) -> 137 log(Log, Priority, io_lib:format(FormatStr, FormatArgs)). 138 139-spec close(Log :: port()) -> 140 ok. 141 142close(Log) -> 143 true = erlang:port_close(Log), 144 ok. 145 146-spec priority(N :: priority() | non_neg_integer()) -> 147 non_neg_integer(). 148 149priority(emerg) -> 0; 150priority(alert) -> 1; 151priority(crit) -> 2; 152priority(err) -> 3; 153priority(warning) -> 4; 154priority(notice) -> 5; 155priority(info) -> 6; 156priority(debug) -> 7; 157priority(N) when is_integer(N), N >= 0 -> N; 158priority(_) -> erlang:error(badarg). 159 160-spec facility(N :: facility() | non_neg_integer()) -> 161 non_neg_integer(). 162 163facility(kern) -> 0; 164facility(user) -> 8; 165facility(mail) -> 16; 166facility(daemon) -> 24; 167facility(auth) -> 32; 168facility(syslog) -> 40; 169facility(lpr) -> 48; 170facility(news) -> 56; 171facility(uucp) -> 64; 172facility(cron) -> 72; 173facility(authpriv) -> 80; 174facility(ftp) -> 88; 175facility(netinfo) -> 96; 176facility(remoteauth)-> 104; 177facility(install) -> 112; 178facility(ras) -> 120; 179facility(local0) -> 16 * 8; 180facility(local1) -> 17 * 8; 181facility(local2) -> 18 * 8; 182facility(local3) -> 19 * 8; 183facility(local4) -> 20 * 8; 184facility(local5) -> 21 * 8; 185facility(local6) -> 22 * 8; 186facility(local7) -> 23 * 8; 187facility(N) when is_integer(N), N >= 0 -> N; 188facility(_) -> erlang:error(badarg). 189 190-spec openlog_opt(N :: openlog_opt() | pos_integer()) -> 191 pos_integer(). 192 193openlog_opt(pid) -> 1; 194openlog_opt(cons) -> 2; 195openlog_opt(odelay) -> 4; 196openlog_opt(ndelay) -> 8; 197openlog_opt(perror) -> 20; 198openlog_opt(N) when is_integer(N), N >= 1 -> N; 199openlog_opt(_) -> erlang:error(badarg). 200 201-spec openlog_opts(N :: list(openlog_opt() | pos_integer()) | 202 openlog_opt() | pos_integer()) -> 203 pos_integer(). 204 205openlog_opts([Queue]) -> openlog_opt(Queue); 206openlog_opts([Tail|Queue]) -> 207 openlog_opt(Tail) bor openlog_opts(Queue); 208openlog_opts([]) -> 0; 209openlog_opts(N) -> openlog_opt(N). 210 211-spec load() -> 212 ok | {error, string()}. 213 214load() -> 215 PrivDir = case code:priv_dir(?MODULE) of 216 {error, bad_name} -> 217 EbinDir = filename:dirname(code:which(?MODULE)), 218 AppPath = filename:dirname(EbinDir), 219 filename:join(AppPath, "priv"); 220 Path -> 221 Path 222 end, 223 case erl_ddll:load_driver(PrivDir, ?DRV_NAME) of 224 ok -> ok; 225 {error, already_loaded} -> ok; 226 {error, LoadError} -> 227 LoadErrorStr = erl_ddll:format_error(LoadError), 228 ErrStr = lists:flatten( 229 io_lib:format("could not load driver ~s: ~p", 230 [?DRV_NAME, LoadErrorStr])), 231 {error, ErrStr} 232 end. 233 234-spec unload() -> 235 ok | {error, string()}. 236 237unload() -> 238 case erl_ddll:unload_driver(?DRV_NAME) of 239 ok -> ok; 240 {error, UnloadError} -> 241 UnloadErrorStr = erl_ddll:format_error(UnloadError), 242 ErrStr = lists:flatten( 243 io_lib:format("could not unload driver ~s: ~p", 244 [?DRV_NAME, UnloadErrorStr])), 245 {error, ErrStr} 246 end. 247 248%%% gen_server callbacks %%% 249 250init([]) -> 251 case load() of 252 ok -> 253 {ok, #state{}}; 254 {error, Reason} -> 255 {stop, Reason} 256 end. 257 258handle_call(_Msg, _From, State) -> 259 {reply, ok, State}. 260 261handle_cast(stop, State) -> 262 {stop, normal, State}; 263handle_cast(_Msg, State) -> 264 {noreply, State}. 265 266handle_info(_Info, State) -> 267 {noreply, State}. 268 269terminate(_Reason, _State) -> 270 ok. 271 272code_change(_, State, _) -> 273 {ok, State}. 274 275%%% internal functions %%% 276 277-ifdef(TEST). 278 279openlog_opts_test() -> 280 11 = openlog_opts([1,2,8]), 281 1 = openlog_opts(pid), 282 try 283 foo = openlog_opts(foo) 284 catch 285 error:badarg -> 286 ok; 287 Reason -> 288 throw(Reason) 289 end. 290 291closed_test() -> 292 {ok, _} = syslog:start(), 293 try 294 {ok, Log} = open("test", pid, local0), 295 Self = self(), 296 {connected,Self} = erlang:port_info(Log, connected), 297 ok = close(Log), 298 try 299 close(Log) 300 catch 301 error:badarg -> 302 ok; 303 Reason1 -> 304 throw(Reason1) 305 end, 306 try 307 ok = log(Log, 8, "writing to closed log") 308 catch 309 error:badarg -> 310 ok; 311 Reason2 -> 312 throw(Reason2) 313 end 314 after 315 syslog:stop() 316 end. 317 318-endif. 319