Current section
Files
Jump to
Current section
Files
lib/tesla_timber_logger.ex
defmodule Tesla.Middleware.TimberLogger do
@moduledoc """
Tesla middleware for logging outgoing requests to Timber.io.
Using this middleware will log all requests and responses using Timber.io formatting and metadata.
### Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.TimberLogger, service_name: "my-service"
end
```
### Options
- `:service_name` - the name of the external service (optional)
- `:log_level` - custom function for calculating log level (see below)
### Custom log levels
By default, the following log levels will be used:
- `:error` - for errors, 5xx and 4xx responses
- `:warn` - for 3xx responses
- `:info` - for 2xx responses
You can customize this setting by providing your own `log_level/1` function:
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.TimberLogger, log_level: &my_log_level/1
def my_log_level(env) do
case env.status do
404 -> :info
_ -> :default
end
end
end
```
"""
require Logger
alias Tesla.Env
alias Timber.Events.{HTTPRequestEvent, HTTPResponseEvent}
@behaviour Tesla.Middleware
@impl true
def call(env, next, opts) do
Logger.info(fn -> log_request(env, opts) end)
timer = Timber.start_timer()
response = Tesla.run(env, next)
level = log_level(response, opts)
Logger.log(level, fn -> log_response(response, timer, opts) end)
response
end
defp log_level({:error, _}, _), do: :error
defp log_level({:ok, env}, opts) do
case Keyword.get(opts, :log_level) do
nil ->
default_log_level(env)
fun when is_function(fun) ->
case fun.(env) do
:default -> default_log_level(env)
level -> level
end
atom when is_atom(atom) ->
atom
end
end
defp default_log_level(env) do
cond do
env.status >= 400 -> :error
env.status >= 300 -> :warn
true -> :info
end
end
defp log_request(env, opts) do
event =
HTTPRequestEvent.new(
direction: "outgoing",
url: serialize_url(env),
method: env.method,
headers: env.headers,
body: env.body,
request_id: Logger.metadata()[:request_id],
service_name: opts[:service_name]
)
{HTTPRequestEvent.message(event), event: event}
end
defp log_response({:error, reason}, _, _), do: Logger.error(fn -> inspect(reason) end)
defp log_response({:ok, env}, timer, opts) do
time_ms = Timber.duration_ms(timer)
event =
HTTPResponseEvent.new(
direction: "incoming",
status: env.status,
time_ms: time_ms,
headers: env.headers,
body: normalize_body(env),
request_id: Logger.metadata()[:request_id],
service_name: opts[:service_name]
)
{HTTPResponseEvent.message(event), event: event}
end
defp serialize_url(%Env{url: url, query: query}), do: Tesla.build_url(url, query)
defp normalize_body(env) do
case Tesla.get_header(env, "content-encoding") do
"gzip" ->
"[gzipped]"
"deflate" ->
"[zipped]"
_ ->
env.body
end
end
end