Page MenuHomePhorge

No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla/middleware/logger.ex b/lib/tesla/middleware/logger.ex
index aafc630..d98ea44 100644
--- a/lib/tesla/middleware/logger.ex
+++ b/lib/tesla/middleware/logger.ex
@@ -1,192 +1,220 @@
defmodule Tesla.Middleware.Logger do
@behaviour Tesla.Middleware
@moduledoc """
Log requests as single line.
Logs request method, url, response status and time taken in milliseconds.
### Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.Logger
end
```
+ ### Custom log levels
+ ```
+ defmodule MyClient do
+ use Tesla
+
+ plug Tesla.Middleware.Logger, log_level: &my_log_level/1
+ end
+
+ def my_log_level(env) do
+ case env.status do
+ 404 -> :info
+ _ -> Tesla.Middleware.Logger.default_log_level(env)
+ end
+ end
+ ```
+
### Logger output
```
2017-09-30 13:39:06.663 [info] GET http://example.com -> 200 (736.988 ms)
```
See `Tesla.Middleware.DebugLogger` to log request/response body
"""
+ @type log_level :: :info | :warn | :error
+
require Logger
- def call(env, next, _opts) do
+ def call(env, next, opts) do
+ log_level = Keyword.get(opts, :log_level, &default_log_level/1)
{time, result} = :timer.tc(Tesla, :run, [env, next])
- _ = log(env, result, time)
+ _ = log(env, result, time, log_level)
result
end
- defp log(env, {:error, reason}, _time) do
+ defp log(env, {:error, reason}, _time, _log_level) do
Logger.error("#{normalize_method(env)} #{env.url} -> #{inspect(reason)}")
end
- defp log(_env, {:ok, env}, time) do
+ defp log(_env, {:ok, env}, time, log_level) do
ms = :io_lib.format("~.3f", [time / 1000])
message = "#{normalize_method(env)} #{env.url} -> #{env.status} (#{ms} ms)"
+ case log_level.(env) do
+ :info -> Logger.info(message)
+ :warn -> Logger.warn(message)
+ :error -> Logger.error(message)
+ end
+ end
+
+ @spec default_log_level(Tesla.Env.t()) :: log_level
+ def default_log_level(env) do
cond do
- env.status >= 400 -> Logger.error(message)
- env.status >= 300 -> Logger.warn(message)
- true -> Logger.info(message)
+ env.status >= 400 -> :error
+ env.status >= 300 -> :warn
+ true -> :info
end
end
defp normalize_method(env) do
env.method |> to_string() |> String.upcase()
end
end
defmodule Tesla.Middleware.DebugLogger do
@behaviour Tesla.Middleware
@moduledoc """
Log full reqeust/response content
### Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.DebugLogger
end
```
### Logger output
```
2017-09-30 13:41:56.281 [debug] > POST https://httpbin.org/post
2017-09-30 13:41:56.281 [debug]
2017-09-30 13:41:56.281 [debug] > a=3
2017-09-30 13:41:56.432 [debug]
2017-09-30 13:41:56.432 [debug] < HTTP/1.1 200
2017-09-30 13:41:56.432 [debug] < access-control-allow-credentials: true
2017-09-30 13:41:56.432 [debug] < access-control-allow-origin: *
2017-09-30 13:41:56.432 [debug] < connection: keep-alive
2017-09-30 13:41:56.432 [debug] < content-length: 280
2017-09-30 13:41:56.432 [debug] < content-type: application/json
2017-09-30 13:41:56.432 [debug] < date: Sat, 30 Sep 2017 11:41:55 GMT
2017-09-30 13:41:56.432 [debug] < server: meinheld/0.6.1
2017-09-30 13:41:56.432 [debug] < via: 1.1 vegur
2017-09-30 13:41:56.432 [debug] < x-powered-by: Flask
2017-09-30 13:41:56.432 [debug] < x-processed-time: 0.0011260509491
2017-09-30 13:41:56.432 [debug]
2017-09-30 13:41:56.432 [debug] > {
"args": {},
"data": "a=3",
"files": {},
"form": {},
"headers": {
"Connection": "close",
"Content-Length": "3",
"Content-Type": "",
"Host": "httpbin.org"
},
"json": null,
"origin": "0.0.0.0",
"url": "https://httpbin.org/post"
}
```
"""
require Logger
def call(env, next, _opts) do
env
|> log_request
|> log_headers("> ")
|> log_params("> ")
|> log_body("> ")
|> Tesla.run(next)
|> case do
{:ok, env} ->
env
|> log_response
|> log_headers("< ")
|> log_body("< ")
{:ok, env}
{:error, reason} ->
log_exception(reason, "< ")
{:error, reason}
end
end
defp log_request(env) do
_ = Logger.debug("> #{env.method |> to_string |> String.upcase()} #{env.url}")
env
end
defp log_response(env) do
_ = Logger.debug("")
_ = Logger.debug("< HTTP/1.1 #{env.status}")
env
end
defp log_headers(env, prefix) do
for {k, v} <- env.headers do
_ = Logger.debug("#{prefix}#{k}: #{v}")
end
env
end
defp log_params(env, prefix) do
encoded_query = Enum.flat_map(env.query, &Tesla.encode_pair/1)
for {k, v} <- encoded_query do
_ = Logger.debug("#{prefix} Query Param '#{k}': '#{v}'")
end
env
end
defp log_body(%Tesla.Env{} = env, _prefix) do
Map.update!(env, :body, &log_body(&1, "> "))
end
defp log_body(nil, _), do: nil
defp log_body([], _), do: nil
defp log_body(%Stream{} = stream, prefix), do: log_body_stream(stream, prefix)
defp log_body(stream, prefix) when is_function(stream), do: log_body_stream(stream, prefix)
defp log_body(%Tesla.Multipart{} = mp, prefix), do: log_multipart_body(mp, prefix)
defp log_body(data, prefix) when is_binary(data) or is_list(data) do
_ = Logger.debug("")
_ = Logger.debug(prefix <> to_string(data))
data
end
defp log_body_stream(stream, prefix) do
_ = Logger.debug("")
Stream.each(stream, fn line -> Logger.debug(prefix <> line) end)
end
defp log_multipart_body(%Tesla.Multipart{} = mp, prefix) do
_ = Logger.debug("")
_ = Logger.debug(prefix <> "boundary: " <> mp.boundary)
_ = Logger.debug(prefix <> "content_type_params: " <> inspect(mp.content_type_params))
Enum.each(mp.parts, &Logger.debug(prefix <> inspect(&1)))
mp
end
defp log_exception(reason, prefix) do
_ = Logger.debug(prefix <> " (#{inspect(reason)})")
end
end
diff --git a/test/tesla/middleware/logger_test.exs b/test/tesla/middleware/logger_test.exs
index 111a834..a808cc1 100644
--- a/test/tesla/middleware/logger_test.exs
+++ b/test/tesla/middleware/logger_test.exs
@@ -1,86 +1,133 @@
defmodule Tesla.Middleware.LoggerTest do
use ExUnit.Case, async: false
defmodule Client do
use Tesla
plug Tesla.Middleware.Logger
plug Tesla.Middleware.DebugLogger
adapter fn env ->
env = Tesla.put_header(env, "content-type", "text/plain")
case env.url do
"/connection-error" ->
{:error, :econnrefused}
"/server-error" ->
{:ok, %{env | status: 500, body: "error"}}
"/client-error" ->
{:ok, %{env | status: 404, body: "error"}}
"/redirect" ->
{:ok, %{env | status: 301, body: "moved"}}
"/ok" ->
{:ok, %{env | status: 200, body: "ok"}}
end
end
end
import ExUnit.CaptureLog
test "connection error" do
log =
capture_log(fn ->
assert {:error, _} = Client.get("/connection-error")
end)
assert log =~ "/connection-error -> :econnrefused"
end
test "server error" do
log = capture_log(fn -> Client.get("/server-error") end)
assert log =~ "/server-error -> 500"
end
test "client error" do
log = capture_log(fn -> Client.get("/client-error") end)
assert log =~ "/client-error -> 404"
end
test "redirect" do
log = capture_log(fn -> Client.get("/redirect") end)
assert log =~ "/redirect -> 301"
end
test "ok" do
log = capture_log(fn -> Client.get("/ok") end)
assert log =~ "/ok -> 200"
end
test "ok with params" do
log = capture_log(fn -> Client.get("/ok", query: %{"test" => "true"}) end)
assert log =~ "Query Param 'test': 'true'"
end
test "ok with list params" do
log = capture_log(fn -> Client.get("/ok", query: %{"test" => ["first", "second"]}) end)
assert log =~ "Query Param 'test[]': 'first'"
assert log =~ "Query Param 'test[]': 'second'"
end
test "multipart" do
mp = Tesla.Multipart.new() |> Tesla.Multipart.add_field("field1", "foo")
log = capture_log(fn -> Client.post("/ok", mp) end)
assert log =~ "boundary: #{mp.boundary}"
assert log =~ inspect(List.first(mp.parts))
end
test "stream" do
stream = Stream.map(1..10, fn i -> "chunk: #{i}" end)
log = capture_log(fn -> Client.post("/ok", stream) end)
assert log =~ "/ok -> 200"
end
+
+ describe "with log_level" do
+ defmodule ClientWithLogLevel do
+ use Tesla
+
+ require Logger
+
+ plug Tesla.Middleware.Logger, log_level: &log_level/1
+
+ defp log_level(env) do
+ cond do
+ env.status == 404 -> :info
+ true -> Tesla.Middleware.Logger.default_log_level(env)
+ end
+ end
+
+ adapter fn env ->
+ env = Tesla.put_header(env, "content-type", "text/plain")
+
+ case env.url do
+ "/bad-request" ->
+ {:ok, %{env | status: 400, body: "bad request"}}
+
+ "/not-found" ->
+ {:ok, %{env | status: 404, body: "not found"}}
+
+ "/ok" ->
+ {:ok, %{env | status: 200, body: "ok"}}
+ end
+ end
+ end
+
+ test "not found" do
+ log = capture_log(fn -> ClientWithLogLevel.get("/not-found") end)
+ assert log =~ "[info] GET /not-found -> 404"
+ end
+
+ test "bad request" do
+ log = capture_log(fn -> ClientWithLogLevel.get("/bad-request") end)
+ assert log =~ "[error] GET /bad-request -> 400"
+ end
+
+ test "ok" do
+ log = capture_log(fn -> ClientWithLogLevel.get("/ok") end)
+ assert log =~ "[info] GET /ok -> 200"
+ end
+ end
end

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 24, 3:42 AM (20 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39320
Default Alt Text
(9 KB)

Event Timeline