Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F112972
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Nov 24, 3:42 AM (17 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39320
Default Alt Text
(9 KB)
Attached To
Mode
R28 tesla
Attached
Detach File
Event Timeline
Log In to Comment