@@ -43,8 +43,16 @@ filter_config(Config) ->
4343log (#{meta := #{mfa := {? MODULE , _ , _ }}}, _ ) ->
4444 ok ;
4545log (LogEvent , Config ) ->
46+ % % Publishing the log message to an exchange might trigger more logging,
47+ % % triggering an infinite logging loop. To prevent that, we make use the
48+ % % process dictionary to record the fact that this logger was already
49+ % % entered. If that's the case when this function is called, we just drop
50+ % % the log event.
51+ Key = ? MODULE ,
52+ ReEntered = erlang :get (Key ) =/= undefined ,
4653 case rabbit_boot_state :get () of
47- ready ->
54+ ready when not ReEntered ->
55+ erlang :put (Key , ? FUNCTION_NAME ),
4856 try
4957 do_log (LogEvent , Config )
5058 catch
@@ -53,22 +61,30 @@ log(LogEvent, Config) ->
5361 % % removes the logger_exchange handler, which in
5462 % % turn deletes the log exchange and its bindings
5563 erlang :display ({? MODULE , crashed , {C , R , S }})
64+ after
65+ erlang :erase (Key )
5666 end ,
5767 ok ;
5868 _ -> ok
5969 end .
6070
61- do_log (LogEvent , #{config := #{exchange := Exchange }} = Config ) ->
71+ do_log (
72+ LogEvent ,
73+ #{config := #{exchange := Exchange ,
74+ setup_proc := Pid }} = Config ) ->
6275 RoutingKey = make_routing_key (LogEvent , Config ),
6376 PBasic = log_event_to_amqp_msg (LogEvent , Config ),
6477 Body = try_format_body (LogEvent , Config ),
6578 Content = rabbit_basic :build_content (PBasic , Body ),
6679 case mc_amqpl :message (Exchange , RoutingKey , Content ) of
6780 {ok , Msg } ->
68- case rabbit_queue_type :publish_at_most_once (Exchange , Msg ) of
69- ok -> ok ;
70- {error , not_found } -> ok
71- end ;
81+ % % Publishing a message might involve a Erlang process, like a Ra
82+ % % server process, to log something and call itself. We need to
83+ % % publish the message asynchronously from a separate process and
84+ % % ignore the fate of that publish, to not block an Erlang
85+ % % process.
86+ Pid ! {publish , Msg },
87+ ok ;
7288 {error , _Reason } ->
7389 % % it would be good to log this error but can we?
7490 ok
@@ -164,12 +180,19 @@ wait_for_initial_pass(N) ->
164180 end .
165181
166182setup_proc (
167- #{config := #{exchange := Exchange }} = Config ) ->
183+ #{id := Id ,
184+ config := #{exchange := Exchange }} = Config ) ->
185+ % % We register this process using the logger handler ID. It makes
186+ % % debugging convenient but it's not critical. That's why we catch any
187+ % % exceptions and ignore the return value.
188+ _ = catch erlang :register (Id , self ()),
189+
168190 case declare_exchange (Config ) of
169191 ok ->
170192 ? LOG_INFO (
171193 " Logging to ~ts ready" , [rabbit_misc :rs (Exchange )],
172- #{domain => ? RMQLOG_DOMAIN_GLOBAL });
194+ #{domain => ? RMQLOG_DOMAIN_GLOBAL }),
195+ loop (Config );
173196 error ->
174197 ? LOG_DEBUG (
175198 " Logging to ~ts not ready, trying again in ~b second(s)" ,
@@ -182,6 +205,15 @@ setup_proc(
182205 end
183206 end .
184207
208+ loop (#{config := #{exchange := Exchange }} = Config ) ->
209+ receive
210+ {publish , Msg } ->
211+ _ = rabbit_queue_type :publish_at_most_once (Exchange , Msg ),
212+ loop (Config );
213+ stop ->
214+ ok
215+ end .
216+
185217declare_exchange (#{config := #{exchange := Exchange }}) ->
186218 try rabbit_exchange :declare (
187219 Exchange , topic , true , false , true , [], ? INTERNAL_USER ) of
0 commit comments