defmodule Driftwood do @moduledoc false end #@default_format "$dateT$time [$level] $metadata $message\n" #defstruct level: :info, format: @default_format, metadata: [:request_id] #defmodule Driftwood do #@moduledoc false #use GenEvent #@default_format "$dateT$time [$level] $metadata $message\n" #defstruct level: nil, format: @default_format, metadata: nil ##defstruct [format: nil, metadata: nil, level: nil, colors: nil, device: nil, ##max_buffer: nil, buffer_size: 0, buffer: [], ref: nil, output: nil] #def init({__MODULE__, opts}) when is_list(opts) do #config = Keyword.merge(Application.get_env(:logger, :driftwood), opts) #{:ok, init(config, %__MODULE__{})} #end #def handle_call({:configure, options}, state) do #{:ok, :ok, configure(options, state)} #end #def handle_event({_level, gl, _event}, state) when node(gl) != node() do #{:ok, state} #end #def handle_event({level, _gl, {Logger, msg, ts, md}}, state) do #%{level: log_level} = state #cond do #meet_level?(level, log_level) -> ## do the logging #output = format_event(level, msg, ts, md, state) #end #{:ok, state} #end #def handle_event(:flush, state) do ## we don't buffer #{:ok, state} #end #def handle_event(_, state) do #{:ok, state} #end #def code_change(_old_vsn, state, _extra) do #{:ok, state} #end #def terminate(_reason, _state) do #:ok #end #def handle_info(_, state) do #{:ok, state} #end #defp configure(options, state) do #config = Keyword.merge(Application.get_env(:logger, :console), options) #Application.put_env(:logger, :driftwood, config) #init(config, state) #end #defp init(config, state) do #level = Keyword.get(config, :level) #format = Logger.Formatter.compile Keyword.get(config, :format) #metadata = Keyword.get(config, :metadata, []) #%{state | format: format, metadata: Enum.reverse(metadata), #level: level} #end #defp meet_level?(_lvl, nil), do: true #defp meet_level?(lvl, min) do #Logger.compare_levels(lvl, min) != :lt #end #defp format_event(level, msg, ts, md, %{format: format, metadata: keys}) do #Logger.Formatter.format(format, level, msg, ts, take_metadata(md, keys)) #end #defp take_metadata(metadata, keys) do #Enum.reduce keys, [], fn key, acc -> #case Keyword.fetch(metadata, key) do #{:ok, val} -> [{key, val} | acc] #:error -> acc #end #end #end #end #defmodule Driftwood.Cache do #@moduledoc """ #Cache for the logs ### Usage #children = [ ## ... #worker(Driftwood.Cache, []) #] #""" #use GenServer #def start_link(name, _opts) do #GenServer.start_link(__MODULE__, , opts) #end #def init({name, clean_period}) do #^name = :ets.new(name, [:named_table, :public, #write_concurrency: true, read_concurrency: true]) #schedule(clean_period) #{:ok, %{clean_period: clean_period, name: name}} #end #@doc """ #Implementation for the PlugAttack.Storage.increment/4 callback. #""" #def increment(name, key, inc, expires_at) do #:ets.update_counter(name, key, inc, {key, 0, expires_at}) #end #@doc """ #Implementation for the PlugAttack.Storage.write_sliding_counter/3 callback. #""" #def write_sliding_counter(name, key, now, expires_at) do #true = :ets.insert(name, {{key, now}, 0, expires_at}) #:ok #end #@doc """ #Implementation for the PlugAttack.Storage.read_sliding_counter/3 callback. #""" #def read_sliding_counter(name, key, now) do #ms = :ets.fun2ms(fn {{^key, _}, _, expires_at} -> #expires_at > now #end) #:ets.select_count(name, ms) #end #@doc """ #Implementation for the PlugAttack.Storage.write/4 callback. #""" #def write(name, key, value, expires_at) do #true = :ets.insert(name, {key, value, expires_at}) #:ok #end #@doc """ #Implementation for the PlugAttack.Storage.read/3 callback. #""" #def read(name, key, now) do #case :ets.lookup(name, key) do #[{^key, value, expires_at}] when expires_at > now -> #{:ok, value} #_ -> #:error #end #end #@doc """ #Forcefully clean the storage. #""" #def clean(name) do #:ets.delete_all_objects(name) #end #@doc """ #Starts the storage table and cleaner process. #The process is registered under `name` and a public, named ets table #with that name is created as well. ### Options #* `:clean_period` - how often the ets table should be cleaned of stale #data. The key scheme guarantees stale data won't be used for making #decisions. This is only about limiting memory consumption #(default: 5000 ms). #""" #def start_link(name, opts \\ []) do #clean_period = Keyword.get(opts, :clean_period, 5_000) #GenServer.start_link(__MODULE__, {name, clean_period}, opts) #end #@doc false #def init() do #db = :ets.new(__MODULE__, [:named_table, :public, #write_concurrency: true, read_concurrency: true]) #{:ok, %{db: db}} #end #end