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

Support writing structured data directly, without JSON encoding #50

Merged
merged 3 commits into from
Jan 9, 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ If it exists in your endpoint, replace it with this (using the options you
want):

```elixir
plug Uinta.Plug, json: false, log: :info
plug Uinta.Plug, format: :string, log: :info
```

You can also perform log sampling by setting the `success_log_sampling_ratio`. Following is a 20% log sampling
Expand Down
37 changes: 26 additions & 11 deletions lib/uinta/plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ if Code.ensure_loaded?(Plug) do
```
plug Uinta.Plug,
log: :info,
json: false,
format: :string,
include_variables: false,
ignored_paths: [],
filter_variables: [],
Expand All @@ -70,8 +70,8 @@ if Code.ensure_loaded?(Plug) do

- `:log` - The log level at which this plug should log its request info.
Default is `:info`
- `:json` - Whether or not this plug should log in JSON format. Default is
`false`
- `: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
is `[]`.
- `:include_variables` - Whether or not to include any GraphQL variables in
Expand All @@ -96,7 +96,7 @@ if Code.ensure_loaded?(Plug) do

@query_name_regex ~r/^\s*(?:query|mutation)\s+(\w+)|{\W+(\w+)\W+?{/m

@type format :: :json | :string
@type format :: :json | :map | :string
@type graphql_info :: %{type: String.t(), operation: String.t(), variables: String.t() | nil}
@type opts :: %{
level: Logger.level(),
Expand All @@ -110,7 +110,14 @@ if Code.ensure_loaded?(Plug) do

@impl Plug
def init(opts) do
format = if Keyword.get(opts, :json, false), do: :json, else: :string
format =
case Keyword.fetch(opts, :format) do
{:ok, value} when value in [:json, :map, :string] ->
value

:error ->
if Keyword.get(opts, :json, false), do: :json, else: :string
epinault marked this conversation as resolved.
Show resolved Hide resolved
end

%{
level: Keyword.get(opts, :log, :info),
Expand Down Expand Up @@ -195,13 +202,13 @@ if Code.ensure_loaded?(Plug) do
end
end

@spec format_line(map(), format()) :: iodata()
@spec format_line(map(), format()) :: iodata() | map()
defp format_line(info, :map) do
format_info(info)
end

defp format_line(info, :json) do
info =
info
|> Map.delete(:connection_type)
|> Enum.filter(fn {_, value} -> !is_nil(value) end)
|> Enum.into(%{})
info = format_info(info)

case Jason.encode(info) do
{:ok, encoded} -> encoded
Expand All @@ -217,6 +224,14 @@ if Code.ensure_loaded?(Plug) do
if is_nil(info.query), do: log, else: [log, "\nQuery: ", info.query]
end

# Format structured data for output
@spec format_info(map()) :: map()
defp format_info(info) do
info
|> Map.delete(:connection_type)
|> Map.reject(fn {_, value} -> is_nil(value) end)
end

defp get_first_value_for_header(conn, name) do
conn
|> Plug.Conn.get_req_header(name)
Expand Down
25 changes: 25 additions & 0 deletions test/uinta/plug_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ defmodule Uinta.PlugTest do
end
end

defmodule MapPlug do
use Plug.Builder

plug(Uinta.Plug, format: :map)
plug(:passthrough)

defp passthrough(conn, _) do
Plug.Conn.send_resp(conn, 200, "Passthrough")
end
end

defmodule JsonPlugWithDataDogFields do
use Plug.Builder

Expand Down Expand Up @@ -162,6 +173,20 @@ defmodule Uinta.PlugTest do
assert message =~ ~r"\[info\]\s+POST /hello/world - Sent 200 in [0-9]+[µm]s"u
end

test "logs map to console" do
message =
capture_log(fn ->
MapPlug.call(conn(:get, "/"), [])
end)

assert message =~ ~r/client_ip: \"127.0.0.1\"/u
assert message =~ ~r/duration_ms: [0-9]+\.?[0-9]+/u
assert message =~ ~r/method: \"GET\"/u
assert message =~ ~r"path: \"/\""u
assert message =~ ~r/status: \"200\"/u
assert message =~ ~r/timing: \"[0-9]+[µm]s\"/u
end

test "logs proper json to console" do
message =
capture_log(fn ->
Expand Down