diff --git a/lib/tesla/middleware/config.ex b/lib/tesla/middleware/config.ex new file mode 100644 index 00000000..5be15483 --- /dev/null +++ b/lib/tesla/middleware/config.ex @@ -0,0 +1,58 @@ +defmodule Tesla.Middleware.Config do + @moduledoc false + + def build!(middleware, schema, opts) do + global_keys = + schema + |> Enum.filter(fn {_k, v} -> v[:global] == true end) + |> Enum.map(fn {k, _v} -> k end) + + :tesla + |> Application.get_env(middleware, []) + |> Keyword.take(global_keys) + |> Keyword.merge(opts) + |> NimbleOptions.validate(to_nimble(schema)) + |> handle_validate(middleware) + end + + def docs(schema) do + schema + |> to_nimble() + |> NimbleOptions.docs() + end + + defp to_nimble(schema) do + Enum.map(schema, fn {k, v} -> + {global, value} = Keyword.pop(v, :global, false) + + if global == true do + {k, + Keyword.update(value, :doc, "", fn doc -> + doc <> " Configurable via application configuration." + end)} + else + {k, value} + end + end) + end + + defp handle_validate({:ok, opts}, _middleware), do: opts + + defp handle_validate({:error, error}, middleware) do + raise ArgumentError, format_error(error, middleware) + end + + defp format_error(%NimbleOptions.ValidationError{keys_path: [], message: message}, middleware) do + "invalid configuration given to middleware #{inspect(middleware)}, " <> message + end + + defp format_error( + %NimbleOptions.ValidationError{keys_path: keys_path, message: message}, + middleware + ) do + "invalid configuration given to to middleware #{inspect(middleware)} for key #{ + inspect(keys_path) + }, " <> + message + end +end diff --git a/lib/tesla/middleware/logger.ex b/lib/tesla/middleware/logger.ex index 95dab3b5..3984cc93 100644 --- a/lib/tesla/middleware/logger.ex +++ b/lib/tesla/middleware/logger.ex @@ -37,6 +37,33 @@ defmodule Tesla.Middleware.Logger.Formatter do end defmodule Tesla.Middleware.Logger do + @config_schema [ + format: [ + type: :string, + doc: "Log format (see below).", + default: "$method $url -> $status ($time ms)", + global: true + ], + log_level: [ + type: {:or, [{:fun, 1}, :atom]}, + doc: "Custom function for calculating log level (see below).", + default: &Tesla.Middleware.Logger.default_log_level/1, + global: true + ], + filter_headers: [ + type: {:list, :string}, + doc: "Sanitizes sensitive headers before logging in debug mode (see below).", + default: [], + global: true + ], + debug: [ + type: :boolean, + doc: "Show detailed request/response logging.", + default: true, + global: true + ] + ] + @moduledoc """ Log requests using Elixir's Logger. @@ -54,9 +81,7 @@ defmodule Tesla.Middleware.Logger do ## Options - - `:log_level` - custom function for calculating log level (see below) - - `:filter_headers` - sanitizes sensitive headers before logging in debug mode (see below) - - `:debug` - show detailed request/response logging + #{Tesla.Middleware.Config.docs(@config_schema)} ## Custom log format @@ -151,9 +176,6 @@ defmodule Tesla.Middleware.Logger do alias Tesla.Middleware.Logger.Formatter - @config Application.get_env(:tesla, __MODULE__, []) - @format Formatter.compile(@config[:format]) - @type log_level :: :info | :warn | :error require Logger @@ -162,17 +184,17 @@ defmodule Tesla.Middleware.Logger do def call(env, next, opts) do {time, response} = :timer.tc(Tesla, :run, [env, next]) - config = Keyword.merge(@config, opts) - - optional_runtime_format = Keyword.get(config, :format) + config = Tesla.Middleware.Config.build!(__MODULE__, @config_schema, opts) format = - if optional_runtime_format, do: Formatter.compile(optional_runtime_format), else: @format + config + |> Keyword.fetch!(:format) + |> Formatter.compile() level = log_level(response, config) Logger.log(level, fn -> Formatter.format(env, response, time, format) end) - if Keyword.get(config, :debug, true) do + if Keyword.fetch!(config, :debug) do Logger.debug(fn -> debug(env, response, config) end) end @@ -183,9 +205,6 @@ defmodule Tesla.Middleware.Logger do defp log_level({:ok, env}, config) do case Keyword.get(config, :log_level) do - nil -> - default_log_level(env) - fun when is_function(fun) -> case fun.(env) do :default -> default_log_level(env) @@ -252,7 +271,7 @@ defmodule Tesla.Middleware.Logger do defp debug_headers([], _config), do: @debug_no_headers defp debug_headers(headers, config) do - filtered = Keyword.get(config, :filter_headers, []) + filtered = Keyword.fetch!(config, :filter_headers) Enum.map(headers, fn {k, v} -> v = if k in filtered, do: "[FILTERED]", else: v diff --git a/mix.exs b/mix.exs index f8b9b2fc..31d9b170 100644 --- a/mix.exs +++ b/mix.exs @@ -52,6 +52,7 @@ defmodule Tesla.Mixfile do defp deps do [ {:mime, "~> 1.0"}, + {:nimble_options, "~> 0.3.0"}, # http clients {:ibrowse, "~> 4.4.0", optional: true},