Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for dynamic log level in Uinta.Plug #63

Merged
merged 2 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## v0.14.0 (2024-04-16)
* Support dynamic log level in `Uinta.Plug`.
* Option `:log` now accepts `{module, function, args}` tuple called with prepended `conn` to determine log level.

## v0.13.0 (2024-01-09)
### Changed
* Support not double encoding the payload. In order to do that, a new plugs option `format` was added. We are deprecating the `json` option instead though it is backward compatible for a little while
Expand Down
19 changes: 17 additions & 2 deletions lib/uinta/plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ if Code.ensure_loaded?(Plug) do

- `:log` - The log level at which this plug should log its request info.
Default is `:info`
- Can be a `{module, function_name, args}` tuple where function is applied with `conn` prepended to args to determine log level.
- `:format` - Output format, either :json, :string, or :map. Default is `:string`
- `:json` - Whether or not plug should log in JSON format. Default is `false` (obsolete)
- `:ignored_paths` - A list of paths that should not log requests. Default
Expand Down Expand Up @@ -99,7 +100,7 @@ if Code.ensure_loaded?(Plug) do
@type format :: :json | :map | :string
@type graphql_info :: %{type: String.t(), operation: String.t(), variables: String.t() | nil}
@type opts :: %{
level: Logger.level(),
level: Logger.level() | {module(), atom(), list()},
format: format(),
include_unnamed_queries: boolean(),
include_variables: boolean(),
Expand Down Expand Up @@ -148,7 +149,9 @@ if Code.ensure_loaded?(Plug) do

defp log_request(conn, start, opts) do
if should_log_request?(conn, opts) do
Logger.log(opts.level, fn ->
level = log_level(conn, opts)

Logger.log(level, fn ->
stop = System.monotonic_time()
diff = System.convert_time_unit(stop - start, :native, :microsecond)

Expand All @@ -160,6 +163,18 @@ if Code.ensure_loaded?(Plug) do
end
end

@spec log_level(Plug.Conn.t(), opts()) :: Logger.level()
defp log_level(conn, opts)

defp log_level(_conn, %{level: level}) when is_atom(level) do
level
end

defp log_level(conn, %{level: {module, function, args}})
when is_atom(module) and is_atom(function) and is_list(args) do
apply(module, function, [conn | args])
end

@spec info(Plug.Conn.t(), graphql_info(), integer(), opts()) :: map()
defp info(conn, graphql_info, diff, opts) do
info = %{
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Uinta.MixProject do
use Mix.Project

@project_url "https://github.com/podium/uinta"
@version "0.13.0"
@version "0.14.0"

def project do
[
Expand Down
73 changes: 73 additions & 0 deletions test/uinta/plug_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,37 @@ defmodule Uinta.PlugTest do
end
end

defmodule MyDynamicLevelPlug do
use Plug.Builder

plug(Uinta.Plug, log: {__MODULE__, :level, [:some, :opts]})
plug(:passthrough)

def level(conn, :some, :opts) do
case conn.status do
info when info in 100..199 ->
:debug

success when success in 200..299 ->
:info

redirect when redirect in 300..399 ->
:notice

client_error when client_error in 400..499 ->
:warning

server_error when server_error in 500..599 ->
:error
end
end

defp passthrough(conn, _opts) do
status = Map.fetch!(conn.private, :dynamic_status)
Plug.Conn.send_resp(conn, status, "Body shouldn't matter")
end
end

defmodule SampleSuccessPlug do
use Plug.Builder

Expand Down Expand Up @@ -342,6 +373,48 @@ defmodule Uinta.PlugTest do
assert message =~ ~r"\[debug\] GET / - Sent 200 in [0-9]+[µm]s"u
end

test "logs dynamic log low level" do
conn =
:get
|> conn("/")
|> put_private(:dynamic_status, 100)

message =
capture_log(fn ->
MyDynamicLevelPlug.call(conn, [])
end)

assert message =~ ~r"\[debug\] GET / - Sent 100 in [0-9]+[µm]s"u
end

test "logs dynamic log mid level" do
conn =
:get
|> conn("/")
|> put_private(:dynamic_status, 307)

message =
capture_log(fn ->
MyDynamicLevelPlug.call(conn, [])
end)

assert message =~ ~r"\[notice\] GET / - Sent 307 in [0-9]+[µm]s"u
end

test "logs dynamic log high level" do
conn =
:get
|> conn("/")
|> put_private(:dynamic_status, 502)

message =
capture_log(fn ->
MyDynamicLevelPlug.call(conn, [])
end)

assert message =~ ~r"\[error\] GET / - Sent 502 in [0-9]+[µm]s"u
end

test "ignores ignored_paths when a 200-level status is returned" do
message =
capture_log(fn ->
Expand Down