From 1cbee1a9fb12559af282488f660013258962b1db Mon Sep 17 00:00:00 2001 From: Ivor Paul Date: Mon, 7 Oct 2024 22:20:03 +0200 Subject: [PATCH] Allow passing the slicer module in as an option to the Tesla middleware --- lib/meta_logger/slicer.ex | 76 ++++++++++++---------- lib/tesla/middleware/meta_logger.ex | 4 +- test/meta_logger/slicer_test.exs | 2 +- test/tesla/middleware/meta_logger_test.exs | 21 ++++++ 4 files changed, 65 insertions(+), 38 deletions(-) diff --git a/lib/meta_logger/slicer.ex b/lib/meta_logger/slicer.ex index fb27e59..58e16f7 100644 --- a/lib/meta_logger/slicer.ex +++ b/lib/meta_logger/slicer.ex @@ -5,42 +5,46 @@ defmodule MetaLogger.Slicer do @typedoc "Max length in bytes or `:infinity` if the entry should not be sliced." @type max_entry_length :: non_neg_integer() | :infinity - - @doc """ - Returns sliced log entries according to the given max entry length. - - If the entry is smaller than given max length, or if `:infinity ` is given - as option, a list with one entry is returned. Otherwise a list with multiple - entries is returned. - - ## Examples - - iex> #{inspect(__MODULE__)}.slice("1234567890", 10) - ["1234567890"] - - iex> #{inspect(__MODULE__)}.slice("1234567890", :infinity) - ["1234567890"] - - iex> #{inspect(__MODULE__)}.slice("1234567890", 5) - ["12345", "67890"] - - """ - @spec slice(String.t(), max_entry_length()) :: [String.t()] - def slice(entry, max_entry_length) - when max_entry_length == :infinity - when byte_size(entry) <= max_entry_length, - do: [entry] - - def slice(entry, max_entry_length) do - entry_length = byte_size(entry) - rem = rem(entry_length, max_entry_length) - sliced_entries = for <>, do: slice - - if rem > 0 do - remainder_entry = binary_part(entry, entry_length, rem * -1) - sliced_entries ++ [remainder_entry] - else - sliced_entries + @callback slice(String.t(), max_entry_length()) :: [String.t()] + + defmodule Default do + @behaviour MetaLogger.Slicer + @doc """ + Returns sliced log entries according to the given max entry length. + + If the entry is smaller than given max length, or if `:infinity ` is given + as option, a list with one entry is returned. Otherwise a list with multiple + entries is returned. + + ## Examples + + iex> #{inspect(__MODULE__)}.slice("1234567890", 10) + ["1234567890"] + + iex> #{inspect(__MODULE__)}.slice("1234567890", :infinity) + ["1234567890"] + + iex> #{inspect(__MODULE__)}.slice("1234567890", 5) + ["12345", "67890"] + + """ + @impl MetaLogger.Slicer + def slice(entry, max_entry_length) + when max_entry_length == :infinity + when byte_size(entry) <= max_entry_length, + do: [entry] + + def slice(entry, max_entry_length) do + entry_length = byte_size(entry) + rem = rem(entry_length, max_entry_length) + sliced_entries = for <>, do: slice + + if rem > 0 do + remainder_entry = binary_part(entry, entry_length, rem * -1) + sliced_entries ++ [remainder_entry] + else + sliced_entries + end end end end diff --git a/lib/tesla/middleware/meta_logger.ex b/lib/tesla/middleware/meta_logger.ex index 949d9b1..a09d359 100644 --- a/lib/tesla/middleware/meta_logger.ex +++ b/lib/tesla/middleware/meta_logger.ex @@ -71,6 +71,7 @@ if Code.ensure_loaded?(Tesla) do |> maybe_put_default_value(:log_level, :info) |> maybe_put_default_value(:log_tag, __MODULE__) |> maybe_put_default_value(:max_entry_length, :infinity) + |> maybe_put_default_value(:slicer, MetaLogger.Slicer.Default) end @spec maybe_put_default_values(Env.opts(), [atom()], any()) :: Env.opts() @@ -182,9 +183,10 @@ if Code.ensure_loaded?(Tesla) do defp log(message, level, options) when is_binary(message) do max_entry_length = Keyword.get(options, :max_entry_length) + slicer = Keyword.get(options, :slicer) message - |> Slicer.slice(max_entry_length) + |> slicer.slice(max_entry_length) |> Enum.map(&prepend_tag(&1, options)) |> Enum.each(&MetaLogger.log(level, &1)) end diff --git a/test/meta_logger/slicer_test.exs b/test/meta_logger/slicer_test.exs index 783a7b9..a6c389e 100644 --- a/test/meta_logger/slicer_test.exs +++ b/test/meta_logger/slicer_test.exs @@ -1,7 +1,7 @@ defmodule MetaLogger.SlicerTest do use ExUnit.Case, async: true - alias MetaLogger.Slicer, as: Subject + alias MetaLogger.Slicer.Default, as: Subject doctest Subject diff --git a/test/tesla/middleware/meta_logger_test.exs b/test/tesla/middleware/meta_logger_test.exs index 8db95d5..701b080 100644 --- a/test/tesla/middleware/meta_logger_test.exs +++ b/test/tesla/middleware/meta_logger_test.exs @@ -5,6 +5,15 @@ defmodule Tesla.Middleware.MetaLoggerTest do alias Tesla.Middleware.MetaLogger, as: Subject + defmodule AlternativeSlicer do + @behaviour MetaLogger.Slicer + + @impl MetaLogger.Slicer + def slice(entry, _max_entry_length) do + ["slice1", "slice2"] + end + end + defmodule FakeClient do use Tesla @@ -251,6 +260,18 @@ defmodule Tesla.Middleware.MetaLoggerTest do assert logs =~ "[warning] [#{inspect(Subject)}] response body moved" end + test "when a slicer module is given, uses it to slice the message" do + logs = + capture_log(fn -> + FakeClient.post("/huge-response", "1234567890", + opts: [slicer: __MODULE__.AlternativeSlicer] + ) + end) + + assert logs =~ "[debug] [Tesla.Middleware.MetaLogger] slice1" + assert logs =~ "[debug] [Tesla.Middleware.MetaLogger] slice2" + end + test "when the request fails to connect, logs the error" do logs = capture_log(fn -> FakeClient.get("/connection-error") end)