From 09f81640c5d1c608c655074fef8c0137c6364e2d Mon Sep 17 00:00:00 2001 From: Andrew Dryga Date: Tue, 21 May 2024 17:06:34 -0600 Subject: [PATCH] Do not crash when Plug is not added to project dependencies Closes #118 --- lib/logger_json/formatters/basic.ex | 6 +- lib/logger_json/formatters/datadog.ex | 8 +- lib/logger_json/formatters/elastic.ex | 8 +- lib/logger_json/formatters/google_cloud.ex | 8 +- lib/logger_json/plug.ex | 172 +++++++++++---------- mix.exs | 2 +- 6 files changed, 103 insertions(+), 101 deletions(-) diff --git a/lib/logger_json/formatters/basic.ex b/lib/logger_json/formatters/basic.ex index 2205dc8..97d36ac 100644 --- a/lib/logger_json/formatters/basic.ex +++ b/lib/logger_json/formatters/basic.ex @@ -15,7 +15,7 @@ defmodule LoggerJSON.Formatters.Basic do } """ import Jason.Helpers, only: [json_map: 1] - import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, Plug, RedactorEncoder} + import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, RedactorEncoder} @behaviour LoggerJSON.Formatter @@ -83,8 +83,8 @@ defmodule LoggerJSON.Formatters.Basic do ), client: json_map( - user_agent: get_header(conn, "user-agent"), - ip: remote_ip(conn) + user_agent: LoggerJSON.Formatter.Plug.get_header(conn, "user-agent"), + ip: LoggerJSON.Formatter.Plug.remote_ip(conn) ) ) end diff --git a/lib/logger_json/formatters/datadog.ex b/lib/logger_json/formatters/datadog.ex index 51edd69..15fa98f 100644 --- a/lib/logger_json/formatters/datadog.ex +++ b/lib/logger_json/formatters/datadog.ex @@ -42,7 +42,7 @@ defmodule LoggerJSON.Formatters.Datadog do } """ import Jason.Helpers, only: [json_map: 1] - import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, Code, Plug, RedactorEncoder} + import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, Code, RedactorEncoder} @behaviour LoggerJSON.Formatter @@ -197,9 +197,9 @@ defmodule LoggerJSON.Formatters.Datadog do if Code.ensure_loaded?(Plug.Conn) do defp format_http_request(%{conn: %Plug.Conn{} = conn} = meta) do request_url = Plug.Conn.request_url(conn) - user_agent = get_header(conn, "user-agent") - remote_ip = remote_ip(conn) - referer = get_header(conn, "referer") + user_agent = LoggerJSON.Formatter.Plug.get_header(conn, "user-agent") + remote_ip = LoggerJSON.Formatter.Plug.remote_ip(conn) + referer = LoggerJSON.Formatter.Plug.get_header(conn, "referer") %{ http: diff --git a/lib/logger_json/formatters/elastic.ex b/lib/logger_json/formatters/elastic.ex index 1848bad..2fd87ca 100644 --- a/lib/logger_json/formatters/elastic.ex +++ b/lib/logger_json/formatters/elastic.ex @@ -97,7 +97,7 @@ defmodule LoggerJSON.Formatters.Elastic do ``` """ import Jason.Helpers, only: [json_map: 1] - import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, Plug, RedactorEncoder} + import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, RedactorEncoder} @behaviour LoggerJSON.Formatter @@ -230,13 +230,13 @@ defmodule LoggerJSON.Formatters.Elastic do # - user_agent.original: https://www.elastic.co/guide/en/ecs/8.11/ecs-user_agent.html defp format_http_request(%{conn: %Plug.Conn{} = conn}) do json_map( - "client.ip": remote_ip(conn), + "client.ip": LoggerJSON.Formatter.Plug.remote_ip(conn), "http.version": Plug.Conn.get_http_protocol(conn), "http.request.method": conn.method, - "http.request.referrer": get_header(conn, "referer"), + "http.request.referrer": LoggerJSON.Formatter.Plug.get_header(conn, "referer"), "http.response.status_code": conn.status, "url.path": conn.request_path, - "user_agent.original": get_header(conn, "user-agent") + "user_agent.original": LoggerJSON.Formatter.Plug.get_header(conn, "user-agent") ) end end diff --git a/lib/logger_json/formatters/google_cloud.ex b/lib/logger_json/formatters/google_cloud.ex index 85c7137..fe7d1ca 100644 --- a/lib/logger_json/formatters/google_cloud.ex +++ b/lib/logger_json/formatters/google_cloud.ex @@ -89,7 +89,7 @@ defmodule LoggerJSON.Formatters.GoogleCloud do } """ import Jason.Helpers, only: [json_map: 1] - import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, Code, Plug, RedactorEncoder} + import LoggerJSON.Formatter.{MapBuilder, DateTime, Message, Metadata, Code, RedactorEncoder} @behaviour LoggerJSON.Formatter @@ -224,9 +224,9 @@ defmodule LoggerJSON.Formatters.GoogleCloud do request_method = conn.method |> to_string() |> String.upcase() request_url = Plug.Conn.request_url(conn) status = conn.status - user_agent = get_header(conn, "user-agent") - remote_ip = remote_ip(conn) - referer = get_header(conn, "referer") + user_agent = LoggerJSON.Formatter.Plug.get_header(conn, "user-agent") + remote_ip = LoggerJSON.Formatter.Plug.remote_ip(conn) + referer = LoggerJSON.Formatter.Plug.get_header(conn, "referer") json_map( protocol: Plug.Conn.get_http_protocol(conn), diff --git a/lib/logger_json/plug.ex b/lib/logger_json/plug.ex index dbfc76c..d7551b0 100644 --- a/lib/logger_json/plug.ex +++ b/lib/logger_json/plug.ex @@ -1,107 +1,109 @@ -defmodule LoggerJSON.Plug do - @moduledoc """ - A telemetry handler that logs request information in JSON format. +if Code.ensure_loaded?(Plug) do + defmodule LoggerJSON.Plug do + @moduledoc """ + A telemetry handler that logs request information in JSON format. - This module is not recommended to be used in production, as it can be - costly to log every single database query. - """ - require Logger + This module is not recommended to be used in production, as it can be + costly to log every single database query. + """ + require Logger - @doc """ - Attaches the telemetry handler to the given event. + @doc """ + Attaches the telemetry handler to the given event. - ### Available options + ### Available options - * `:level` - log level which is used to log requests. Defaults to `:info`. + * `:level` - log level which is used to log requests. Defaults to `:info`. - ### Dynamic log level + ### Dynamic log level - In some cases you may wish to set the log level dynamically - on a per-query basis. To do so, set the `:level` option to - a tuple, `{Mod, Fun, Args}`. The query and map of time measures - will be prepended to the provided list of arguments. + In some cases you may wish to set the log level dynamically + on a per-query basis. To do so, set the `:level` option to + a tuple, `{Mod, Fun, Args}`. The query and map of time measures + will be prepended to the provided list of arguments. - When invoked, your function must return a - [`Logger.level()`](`t:Logger.level()/0`) or `false` to - disable logging for the request. + When invoked, your function must return a + [`Logger.level()`](`t:Logger.level()/0`) or `false` to + disable logging for the request. - ### Examples + ### Examples - Attaching the telemetry handler to the `MyApp.Repo` events with the `:info` log level: + Attaching the telemetry handler to the `MyApp.Repo` events with the `:info` log level: - # in the endpoint - plug Plug.Telemetry, event_prefix: [:myapp, :plug] + # in the endpoint + plug Plug.Telemetry, event_prefix: [:myapp, :plug] - # in your application.ex - LoggerJSON.Plug.attach("logger-json-requests", [:myapp, :plug, :stop], :info) + # in your application.ex + LoggerJSON.Plug.attach("logger-json-requests", [:myapp, :plug, :stop], :info) - To make plug broadcast those events see [`Plug.Telemetry`](https://hexdocs.pm/plug/Plug.Telemetry.html) documentation. + To make plug broadcast those events see [`Plug.Telemetry`](https://hexdocs.pm/plug/Plug.Telemetry.html) documentation. - You can also attach to the `[:phoenix, :endpoint, :stop]` event to log request latency from Phoenix endpoints: + You can also attach to the `[:phoenix, :endpoint, :stop]` event to log request latency from Phoenix endpoints: - LoggerJSON.Plug.attach("logger-json-phoenix-requests", [:phoenix, :endpoint, :stop], :info) - """ - def attach(name, event, level) do - :telemetry.attach(name, event, &__MODULE__.telemetry_logging_handler/4, level) - end + LoggerJSON.Plug.attach("logger-json-phoenix-requests", [:phoenix, :endpoint, :stop], :info) + """ + def attach(name, event, level) do + :telemetry.attach(name, event, &__MODULE__.telemetry_logging_handler/4, level) + end - @doc """ - A telemetry handler that logs requests in a structured format. - """ - @spec telemetry_logging_handler( - event_name :: [atom()], - query_time :: %{duration: non_neg_integer()}, - metadata :: %{conn: Plug.Conn.t()}, - level :: Logger.level() | {module :: module(), function :: atom(), arguments :: [term()]} | false - ) :: :ok - def telemetry_logging_handler(_event_name, %{duration: duration}, %{conn: conn}, level) do - duration = System.convert_time_unit(duration, :native, :microsecond) - - if level = level(level, conn) do - Logger.log( - level, - fn -> - %{ - method: method, - request_path: request_path, - state: state, - status: status - } = conn - - [ - method, - ?\s, - request_path, - ?\s, - "[", - connection_type(state), - ?\s, - status(status), - "in ", - duration(duration), - "]" - ] - end, - conn: conn, - duration_μs: duration - ) + @doc """ + A telemetry handler that logs requests in a structured format. + """ + @spec telemetry_logging_handler( + event_name :: [atom()], + query_time :: %{duration: non_neg_integer()}, + metadata :: %{conn: Plug.Conn.t()}, + level :: Logger.level() | {module :: module(), function :: atom(), arguments :: [term()]} | false + ) :: :ok + def telemetry_logging_handler(_event_name, %{duration: duration}, %{conn: conn}, level) do + duration = System.convert_time_unit(duration, :native, :microsecond) + + if level = level(level, conn) do + Logger.log( + level, + fn -> + %{ + method: method, + request_path: request_path, + state: state, + status: status + } = conn + + [ + method, + ?\s, + request_path, + ?\s, + "[", + connection_type(state), + ?\s, + status(status), + "in ", + duration(duration), + "]" + ] + end, + conn: conn, + duration_μs: duration + ) + end end - end - defp connection_type(:set_chunked), do: "Chunked" - defp connection_type(_), do: "Sent" + defp connection_type(:set_chunked), do: "Chunked" + defp connection_type(_), do: "Sent" - defp status(nil), do: "" - defp status(status), do: [status |> Plug.Conn.Status.code() |> Integer.to_string(), ?\s] + defp status(nil), do: "" + defp status(status), do: [status |> Plug.Conn.Status.code() |> Integer.to_string(), ?\s] - def duration(duration) do - if duration > 1000 do - [duration |> div(1000) |> Integer.to_string(), "ms"] - else - [Integer.to_string(duration), "µs"] + def duration(duration) do + if duration > 1000 do + [duration |> div(1000) |> Integer.to_string(), "ms"] + else + [Integer.to_string(duration), "µs"] + end end - end - defp level({m, f, a}, conn), do: apply(m, f, [conn | a]) - defp level(level, _conn) when is_atom(level), do: level + defp level({m, f, a}, conn), do: apply(m, f, [conn | a]) + defp level(level, _conn) when is_atom(level), do: level + end end diff --git a/mix.exs b/mix.exs index 618b3b0..2f5c914 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule LoggerJSON.Mixfile do use Mix.Project @source_url "https://github.com/Nebo15/logger_json" - @version "6.0.0" + @version "6.0.1" def project do [