1defmodule Logger.Config do 2 @moduledoc false 3 4 @behaviour :gen_event 5 @name __MODULE__ 6 @update_counter_message {__MODULE__, :update_counter} 7 8 def configure(options) do 9 :gen_event.call(Logger, @name, {:configure, options}) 10 end 11 12 def add_translator(translator) do 13 :gen_event.call(Logger, @name, {:add_translator, translator}) 14 end 15 16 def remove_translator(translator) do 17 :gen_event.call(Logger, @name, {:remove_translator, translator}) 18 end 19 20 ## Callbacks 21 22 def init(counter) do 23 state = load_state(counter) 24 state = update_counter(state, false) 25 schedule_update_counter(state) 26 {:ok, state} 27 end 28 29 defp load_state(counter) do 30 {counter, :log, Application.fetch_env!(:logger, :discard_threshold), 31 Application.fetch_env!(:logger, :discard_threshold_periodic_check)} 32 end 33 34 def handle_event(_event, state) do 35 {:ok, update_counter(state, false)} 36 end 37 38 def handle_call({:configure, options}, {counter, _, _, _}) do 39 Enum.each(options, fn 40 {:level, level} -> 41 :logger.set_primary_config(:level, Logger.Handler.elixir_level_to_erlang_level(level)) 42 43 {key, value} -> 44 Application.put_env(:logger, key, value) 45 end) 46 47 {:ok, :ok, load_state(counter)} 48 end 49 50 def handle_call({:add_translator, translator}, state) do 51 update_translators(fn t -> [translator | List.delete(t, translator)] end) 52 {:ok, :ok, state} 53 end 54 55 def handle_call({:remove_translator, translator}, state) do 56 update_translators(&List.delete(&1, translator)) 57 {:ok, :ok, state} 58 end 59 60 def handle_info(@update_counter_message, state) do 61 state = update_counter(state, true) 62 schedule_update_counter(state) 63 {:ok, state} 64 end 65 66 def handle_info(_, state) do 67 {:ok, state} 68 end 69 70 def terminate(_reason, _state) do 71 :ok 72 end 73 74 def code_change(_old, state, _extra) do 75 {:ok, state} 76 end 77 78 @counter_pos 1 79 80 defp update_counter({counter, log, discard_threshold, discard_period}, periodic_check?) do 81 # If length is more than the total, it means the counter is behind, 82 # due to non-log messages, so we need to increase the counter. 83 # 84 # If length is less than the total, then we either have a spike or 85 # the counter drifted due to failures. 86 # 87 # Because we always bump the counter and then we send the message, 88 # there is a chance clients have bumped the counter but they did not 89 # deliver the message yet. Those bumps will be lost. At the same time, 90 # we are careful to read the counter first here, so if the counter is 91 # bumped after we read from it, those bumps won't be lost. 92 total = :counters.get(counter, @counter_pos) 93 {:message_queue_len, length} = Process.info(self(), :message_queue_len) 94 :counters.add(counter, @counter_pos, length - total) 95 96 # In case we are logging but we reached the threshold, we log that we 97 # started discarding messages. This can only be reverted by the periodic 98 # discard check. 99 cond do 100 total >= discard_threshold -> 101 if log == :log or periodic_check? do 102 warn("Attempted to log #{total} messages, which is above :discard_threshold") 103 end 104 105 {counter, :discard, discard_threshold, discard_period} 106 107 log == :discard and periodic_check? -> 108 warn("Attempted to log #{total} messages, which is below :discard_threshold") 109 {counter, :log, discard_threshold, discard_period} 110 111 true -> 112 {counter, log, discard_threshold, discard_period} 113 end 114 end 115 116 defp warn(message) do 117 utc_log = Application.fetch_env!(:logger, :utc_log) 118 event = {Logger, message, Logger.Utils.timestamp(utc_log), pid: self()} 119 :gen_event.notify(self(), {:warn, Process.group_leader(), event}) 120 end 121 122 defp schedule_update_counter({_, _, _, discard_period}) do 123 Process.send_after(self(), @update_counter_message, discard_period) 124 end 125 126 ## Data helpers 127 128 defp update_translators(fun) do 129 {:ok, %{config: data}} = :logger.get_handler_config(Logger) 130 translators = fun.(data.translators) 131 Application.put_env(:logger, :translators, translators) 132 :ok = :logger.update_handler_config(Logger, :config, translators: translators) 133 end 134end 135