├── .gitignore ├── Makefile ├── README.md ├── amqp_subscriber.mqdsl ├── rebar ├── rebar.config └── src ├── lager_amqp_backend.app.src └── lager_amqp_backend.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | deps 3 | ebin 4 | log -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE := lager_amqp_backend 2 | DIST_DIR := dist 3 | EBIN_DIR := ebin 4 | INCLUDE_DIRS := include 5 | DEPS_DIR := deps 6 | DEPS ?= lager 7 | DEPS_EZ := $(foreach DEP, $(DEPS), $(DEPS_DIR)/$(DEP).ez) 8 | 9 | all: compile 10 | 11 | clean: 12 | rm -rf $(DIST_DIR) 13 | rm -rf $(EBIN_DIR) 14 | 15 | distclean: clean 16 | rm -rf $(DEPS_DIR) 17 | 18 | package: compile $(DEPS_EZ) 19 | rm -f $(DIST_DIR)/$(PACKAGE).ez 20 | mkdir -p $(DIST_DIR)/$(PACKAGE) 21 | cp -r $(EBIN_DIR) $(DIST_DIR)/$(PACKAGE) 22 | $(foreach EXTRA_DIR, $(INCLUDE_DIRS), cp -r $(EXTRA_DIR) $(DIST_DIR)/$(PACKAGE);) 23 | (cd $(DIST_DIR); zip -r $(PACKAGE).ez $(PACKAGE)) 24 | 25 | $(DEPS_DIR): 26 | ./rebar get-deps 27 | 28 | $(DEPS_EZ): 29 | cd $(DEPS_DIR); $(foreach DEP, $(DEPS), zip -r $(DEP).ez $(DEP);) 30 | 31 | compile: $(DEPS_DIR) 32 | ./rebar compile -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lager AMQP Backend 2 | 3 | This is a backend for the Lager Erlang logging framework. 4 | 5 | [https://github.com/basho/lager](https://github.com/basho/lager) 6 | 7 | It will send all of your logging messages to the exchange you specify and use the logging level 8 | as the routing key. It uses a smart connection pool to your broker. If the connection drops or 9 | becomes unusable, the backend will reconnect. 10 | 11 | ### Usage 12 | 13 | Include this backend into your project using rebar: 14 | 15 | {lager_amqp_backend, ".*", {git, "https://github.com/jbrisbin/lager_amqp_backend.git", "master"}} 16 | 17 | ### Configuration 18 | 19 | You can pass the backend the following configuration (shown are the defaults): 20 | 21 | {lager, [ 22 | {handlers, [ 23 | {lager_amqp_backend, [ 24 | {name, "lager_amqp_backend"}, 25 | {level, debug}, 26 | {exchange, <<"lager_amqp_backend">>}, 27 | {amqp_user, <<"guest">>}, 28 | {amqp_pass, <<"guest">>}, 29 | {amqp_vhost, <<"/">>}, 30 | {amqp_host, "localhost"}, 31 | {amqp_port, 5672} 32 | ]} 33 | ]} 34 | ]} 35 | 36 | ### License 37 | 38 | Apache 2.0, just like everything else I do. :) -------------------------------------------------------------------------------- /amqp_subscriber.mqdsl: -------------------------------------------------------------------------------- 1 | // You need the RabbitMQ DSL to run this test file: 2 | // https://github.com/jbrisbin/rabbitmq-dsl 3 | mq.exchange(name: "lager_amqp_backend", type: "topic") { 4 | queue(name: null, routingKey: "#") { 5 | consume { msg -> 6 | println new String(msg.body) 7 | return true 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbrisbin/lager_amqp_backend/900f50086b7aa1ce8a09e0b3592a0a2edc7481d9/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {cover_enabled, true}. 2 | {erl_opts, [ 3 | debug_info 4 | %fail_on_warning 5 | ]}. 6 | 7 | {deps, [ 8 | {lager, ".*", {git, "https://github.com/basho/lager.git", {tag, "0.9.4"}}}, 9 | {amqp_client, ".*", {git, "https://github.com/jbrisbin/amqp_client.git", {tag, "rabbitmq_2.7.0"}}} 10 | ]}. 11 | -------------------------------------------------------------------------------- /src/lager_amqp_backend.app.src: -------------------------------------------------------------------------------- 1 | {application, lager_amqp_backend, [ 2 | {description, "Lager AMQP Logging Backend"}, 3 | {vsn, "1.0.0"}, 4 | {registered, []}, 5 | {applications, [ 6 | kernel, 7 | stdlib, 8 | lager 9 | ]}, 10 | {env, []} 11 | ]}. -------------------------------------------------------------------------------- /src/lager_amqp_backend.erl: -------------------------------------------------------------------------------- 1 | %% Copyright (c) 2011 by Jon Brisbin. All Rights Reserved. 2 | %% 3 | %% This file is provided to you under the Apache License, 4 | %% Version 2.0 (the "License"); you may not use this file 5 | %% except in compliance with the License. You may obtain 6 | %% a copy of the License at 7 | %% 8 | %% http://www.apache.org/licenses/LICENSE-2.0 9 | %% 10 | %% Unless required by applicable law or agreed to in writing, 11 | %% software distributed under the License is distributed on an 12 | %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | %% KIND, either express or implied. See the License for the 14 | %% specific language governing permissions and limitations 15 | %% under the License. 16 | 17 | -module(lager_amqp_backend). 18 | -behaviour(gen_event). 19 | 20 | -include_lib("amqp_client/include/amqp_client.hrl"). 21 | 22 | -export([ 23 | init/1, 24 | handle_call/2, 25 | handle_event/2, 26 | handle_info/2, 27 | terminate/2, 28 | code_change/3, 29 | test/0 30 | ]). 31 | 32 | -record(state, { 33 | name, 34 | level, 35 | exchange, 36 | params 37 | }). 38 | 39 | init(Params) -> 40 | 41 | Name = config_val(name, Params, ?MODULE), 42 | Level = config_val(level, Params, debug), 43 | Exchange = config_val(exchange, Params, list_to_binary(atom_to_list(?MODULE))), 44 | 45 | AmqpParams = #amqp_params_network { 46 | username = config_val(amqp_user, Params, <<"guest">>), 47 | password = config_val(amqp_pass, Params, <<"guest">>), 48 | virtual_host = config_val(amqp_vhost, Params, <<"/">>), 49 | host = config_val(amqp_host, Params, "localhost"), 50 | port = config_val(amqp_port, Params, 5672) 51 | }, 52 | 53 | {ok, Channel} = amqp_channel(AmqpParams), 54 | #'exchange.declare_ok'{} = amqp_channel:call(Channel, #'exchange.declare'{ exchange = Exchange, type = <<"topic">> }), 55 | 56 | {ok, #state{ 57 | name = Name, 58 | level = Level, 59 | exchange = Exchange, 60 | params = AmqpParams 61 | }}. 62 | 63 | handle_call({set_loglevel, Level}, #state{ name = _Name } = State) -> 64 | % io:format("Changed loglevel of ~s to ~p~n", [Name, Level]), 65 | {ok, ok, State#state{ level=lager_util:level_to_num(Level) }}; 66 | 67 | handle_call(get_loglevel, #state{ level = Level } = State) -> 68 | {ok, Level, State}; 69 | 70 | handle_call(_Request, State) -> 71 | {ok, ok, State}. 72 | 73 | handle_event({log, Dest, Level, {Date, Time}, Message}, #state{ name = Name, level = L} = State) when Level > L -> 74 | case lists:member({lager_amqp_backend, Name}, Dest) of 75 | true -> 76 | {ok, log(Level, Date, Time, Message, State)}; 77 | false -> 78 | {ok, State} 79 | end; 80 | 81 | handle_event({log, Level, {Date, Time}, Message}, #state{ level = L } = State) when Level =< L-> 82 | {ok, log(Level, Date, Time, Message, State)}; 83 | 84 | handle_event(_Event, State) -> 85 | {ok, State}. 86 | 87 | handle_info(_Info, State) -> 88 | {ok, State}. 89 | 90 | terminate(_Reason, _State) -> 91 | ok. 92 | 93 | code_change(_OldVsn, State, _Extra) -> 94 | {ok, State}. 95 | 96 | log(Level, Date, Time, Message, #state{params = AmqpParams } = State) -> 97 | case amqp_channel(AmqpParams) of 98 | {ok, Channel} -> 99 | send(State, Level, [Date, " ", Time, " ", Message], Channel); 100 | _ -> 101 | State 102 | end. 103 | 104 | send(#state{ name = Name, exchange = Exchange } = State, Level, Message, Channel) -> 105 | RkPrefix = atom_to_list(lager_util:num_to_level(Level)), 106 | RoutingKey = list_to_binary( case Name of 107 | [] -> 108 | RkPrefix; 109 | Name -> 110 | string:join([RkPrefix, Name], ".") 111 | end 112 | ), 113 | Publish = #'basic.publish'{ exchange = Exchange, routing_key = RoutingKey }, 114 | Props = #'P_basic'{ content_type = <<"text/plain">> }, 115 | Body = list_to_binary(lists:flatten(Message)), 116 | Msg = #amqp_msg{ payload = Body, props = Props }, 117 | 118 | % io:format("message: ~p~n", [Msg]), 119 | amqp_channel:cast(Channel, Publish, Msg), 120 | 121 | State. 122 | 123 | config_val(C, Params, Default) -> 124 | case lists:keyfind(C, 1, Params) of 125 | {C, V} -> V; 126 | _ -> Default 127 | end. 128 | 129 | amqp_channel(AmqpParams) -> 130 | case maybe_new_pid({AmqpParams, connection}, 131 | fun() -> amqp_connection:start(AmqpParams) end) of 132 | {ok, Client} -> 133 | maybe_new_pid({AmqpParams, channel}, 134 | fun() -> amqp_connection:open_channel(Client) end); 135 | Error -> 136 | Error 137 | end. 138 | 139 | maybe_new_pid(Group, StartFun) -> 140 | case pg2:get_closest_pid(Group) of 141 | {error, {no_such_group, _}} -> 142 | pg2:create(Group), 143 | maybe_new_pid(Group, StartFun); 144 | {error, {no_process, _}} -> 145 | case StartFun() of 146 | {ok, Pid} -> 147 | pg2:join(Group, Pid), 148 | {ok, Pid}; 149 | Error -> 150 | Error 151 | end; 152 | Pid -> 153 | {ok, Pid} 154 | end. 155 | 156 | test() -> 157 | application:load(lager), 158 | application:set_env(lager, handlers, [{lager_console_backend, debug}, {lager_amqp_backend, []}]), 159 | application:set_env(lager, error_logger_redirect, false), 160 | application:start(lager), 161 | lager:log(info, self(), "Test INFO message"), 162 | lager:log(debug, self(), "Test DEBUG message"), 163 | lager:log(error, self(), "Test ERROR message"). 164 | 165 | --------------------------------------------------------------------------------