Skip to content

Commit 0a4215c

Browse files
committed
Fix PR #1749 review follow-ups — fetch/3 :unidentified collapse and log attribution
Two review-driven follow-ups on the preference dispatcher. Fix 1: fetch/3 now collapses {:error, :unidentified} to :error — matching the adapter behaviour's "treat as not found" semantics. No warning is logged for that case because it is the expected path for anonymous visitors / background jobs. Other {:error, reason} tuples still surface unchanged, so a genuine adapter failure remains distinguishable. Fix 2: Every dispatcher-originated Logger.warning now carries the adapter module in the message — "adapter \#{inspect(module)} returned error on get/3 ..." — so operators running multi-adapter routing (global.* → Session, resource.* → EctoAdapter) can tell which backend failed. The dispatch helpers now return {module, result} tuples so callers can attribute; identity-resolver warnings say "resolving identity via \#{resolver}" instead, since no adapter is involved.
1 parent d96adf1 commit 0a4215c

2 files changed

Lines changed: 81 additions & 49 deletions

File tree

lib/backpex/preferences.ex

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,16 @@ defmodule Backpex.Preferences do
8181
default = Keyword.get(opts, :default)
8282

8383
case dispatch_get(ctx_or_session, key, opts) do
84-
{:ok, :not_found} ->
84+
{_module, {:ok, :not_found}} ->
8585
default
8686

87-
{:ok, value} ->
87+
{_module, {:ok, value}} ->
8888
value
8989

90-
{:error, reason} ->
90+
{module, {:error, reason}} ->
9191
Logger.warning(
92-
"[Backpex.Preferences] adapter returned error on get/3 for key #{inspect(key)}: " <>
93-
inspect(reason) <> "; falling back to default"
92+
"Backpex.Preferences: adapter #{inspect(module)} returned error on get/3 for key " <>
93+
"#{inspect(key)}: #{inspect(reason)}; falling back to default"
9494
)
9595

9696
default
@@ -110,10 +110,13 @@ defmodule Backpex.Preferences do
110110
111111
* `{:ok, value}` — the adapter returned a stored value.
112112
* `:error` — the adapter successfully determined nothing is stored
113-
(`{:ok, :not_found}` from the adapter).
114-
* `{:error, reason}` — the adapter failed (e.g. `:unidentified`, a raised
115-
exception swallowed by the dispatcher, or any other adapter-level
116-
error). Also logs a warning — same behavior as `get/3`.
113+
(`{:ok, :not_found}` from the adapter), **or** the adapter returned
114+
`{:error, :unidentified}`. Per the
115+
`Backpex.Preferences.Adapter` behaviour, `:unidentified` on reads is
116+
defined as "treat as not found" — no warning is logged for that case
117+
because it is expected (anonymous visitors, background jobs, etc.).
118+
* `{:error, reason}` — any other adapter failure (e.g. a raised
119+
exception swallowed by the dispatcher). A warning is also logged.
117120
118121
## Examples
119122
@@ -128,16 +131,23 @@ defmodule Backpex.Preferences do
128131
{:ok, term()} | :error | {:error, term()}
129132
def fetch(ctx_or_session, key, opts \\ []) do
130133
case dispatch_get(ctx_or_session, key, opts) do
131-
{:ok, :not_found} ->
134+
{_module, {:ok, :not_found}} ->
132135
:error
133136

134-
{:ok, value} ->
137+
{_module, {:ok, value}} ->
135138
{:ok, value}
136139

137-
{:error, reason} = err ->
140+
{_module, {:error, :unidentified}} ->
141+
# Per the adapter behaviour, `:unidentified` on reads means "treat as
142+
# not found" — collapse to `:error` without logging. This matches the
143+
# expected path for anonymous visitors / background jobs that have no
144+
# resolved identity.
145+
:error
146+
147+
{module, {:error, reason} = err} ->
138148
Logger.warning(
139-
"[Backpex.Preferences] adapter returned error on fetch/3 for key #{inspect(key)}: " <>
140-
inspect(reason)
149+
"Backpex.Preferences: adapter #{inspect(module)} returned error on fetch/3 for key " <>
150+
"#{inspect(key)}: #{inspect(reason)}"
141151
)
142152

143153
err
@@ -179,8 +189,8 @@ defmodule Backpex.Preferences do
179189

180190
{:error, reason} ->
181191
Logger.warning(
182-
"[Backpex.Preferences] adapter returned error on get_map/3 for prefix " <>
183-
inspect(prefix) <> ": " <> inspect(reason) <> "; falling back to %{}"
192+
"Backpex.Preferences: adapter #{inspect(module)} returned error on get_map/3 for prefix " <>
193+
"#{inspect(prefix)}: #{inspect(reason)}; falling back to %{}"
184194
)
185195

186196
%{}
@@ -226,13 +236,13 @@ defmodule Backpex.Preferences do
226236
ctx = conn |> Context.from_conn() |> resolve_identity()
227237

228238
case dispatch_put(ctx, key, value, opts) do
229-
{:ok, effects} ->
239+
{_module, {:ok, effects}} ->
230240
{:ok, apply_effects_on_conn(conn, effects)}
231241

232-
{:error, reason} = err ->
242+
{module, {:error, reason} = err} ->
233243
Logger.warning(
234-
"[Backpex.Preferences] adapter refused put/4 for key #{inspect(key)} " <>
235-
"on conn origin: " <> inspect(reason)
244+
"Backpex.Preferences: adapter #{inspect(module)} refused put/4 for key " <>
245+
"#{inspect(key)} on conn origin: #{inspect(reason)}"
236246
)
237247

238248
err
@@ -246,16 +256,16 @@ defmodule Backpex.Preferences do
246256
|> resolve_identity()
247257

248258
case dispatch_put(ctx, key, value, opts) do
249-
{:ok, effects} ->
250-
{:ok, apply_effects_on_socket(socket, key, value, effects)}
259+
{module, {:ok, effects}} ->
260+
{:ok, apply_effects_on_socket(socket, module, key, value, effects)}
251261

252-
{:error, :requires_http} ->
262+
{_module, {:error, :requires_http}} ->
253263
{:ok, push_event_fallback(socket, key, value)}
254264

255-
{:error, reason} = err ->
265+
{module, {:error, reason} = err} ->
256266
Logger.warning(
257-
"[Backpex.Preferences] adapter refused put/4 for key #{inspect(key)} " <>
258-
"on socket origin: " <> inspect(reason)
267+
"Backpex.Preferences: adapter #{inspect(module)} refused put/4 for key " <>
268+
"#{inspect(key)} on socket origin: #{inspect(reason)}"
259269
)
260270

261271
err
@@ -368,28 +378,34 @@ defmodule Backpex.Preferences do
368378
defp dispatch_get(ctx_or_session, key, opts) do
369379
ctx = resolve_identity(Context.coerce(ctx_or_session))
370380
{module, adapter_opts} = Router.resolve(key)
371-
module.get(ctx, key, merge_opts(adapter_opts, opts))
372-
rescue
373-
reason ->
374-
Logger.warning(
375-
"[Backpex.Preferences] adapter raised in get/3 for key #{inspect(key)}: " <>
376-
Exception.format(:error, reason, __STACKTRACE__)
377-
)
378381

379-
{:error, {:exception, reason}}
382+
try do
383+
{module, module.get(ctx, key, merge_opts(adapter_opts, opts))}
384+
rescue
385+
reason ->
386+
Logger.warning(
387+
"Backpex.Preferences: adapter #{inspect(module)} raised in get/3 for key " <>
388+
"#{inspect(key)}: #{Exception.format(:error, reason, __STACKTRACE__)}"
389+
)
390+
391+
{module, {:error, {:exception, reason}}}
392+
end
380393
end
381394

382395
defp dispatch_put(%Context{} = ctx, key, value, opts) do
383396
{module, adapter_opts} = Router.resolve(key)
384-
module.put(ctx, key, value, merge_opts(adapter_opts, opts))
385-
rescue
386-
reason ->
387-
Logger.warning(
388-
"[Backpex.Preferences] adapter raised in put/4 for key #{inspect(key)}: " <>
389-
Exception.format(:error, reason, __STACKTRACE__)
390-
)
391397

392-
{:error, {:exception, reason}}
398+
try do
399+
{module, module.put(ctx, key, value, merge_opts(adapter_opts, opts))}
400+
rescue
401+
reason ->
402+
Logger.warning(
403+
"Backpex.Preferences: adapter #{inspect(module)} raised in put/4 for key " <>
404+
"#{inspect(key)}: #{Exception.format(:error, reason, __STACKTRACE__)}"
405+
)
406+
407+
{module, {:error, {:exception, reason}}}
408+
end
393409
end
394410

395411
# Socket-origin put accepted by the adapter: consume the returned side
@@ -400,15 +416,15 @@ defmodule Backpex.Preferences do
400416
# adapter could still emit it. Rather than silently dropping the write,
401417
# log a warning and fall back to `push_event/3` so the browser can retry
402418
# via the preferences controller.
403-
defp apply_effects_on_socket(socket, key, value, effects) do
419+
defp apply_effects_on_socket(socket, module, key, value, effects) do
404420
Enum.reduce(effects, socket, fn
405421
:noop, s ->
406422
s
407423

408424
{:put_session, _k, _v}, s ->
409425
Logger.warning(
410-
"[Backpex.Preferences] adapter emitted {:put_session, _, _} from a socket origin " <>
411-
"for key #{inspect(key)}; routing through push_event fallback. " <>
426+
"Backpex.Preferences: adapter #{inspect(module)} emitted {:put_session, _, _} from a " <>
427+
"socket origin for key #{inspect(key)}; routing through push_event fallback. " <>
412428
"Adapters should return :requires_http instead when called outside a controller."
413429
)
414430

@@ -439,16 +455,16 @@ defmodule Backpex.Preferences do
439455
rescue
440456
reason ->
441457
Logger.warning(
442-
"[Backpex.Preferences] identity resolver #{inspect(mod)}.#{fun}/#{length(args)} raised: " <>
443-
Exception.format(:error, reason, __STACKTRACE__) <> "; falling back to :unidentified"
458+
"Backpex.Preferences: resolving identity via #{inspect({mod, fun, length(args)})} raised: " <>
459+
"#{Exception.format(:error, reason, __STACKTRACE__)}; falling back to :unidentified"
444460
)
445461

446462
:unidentified
447463
catch
448464
kind, reason ->
449465
Logger.warning(
450-
"[Backpex.Preferences] identity resolver #{inspect(mod)}.#{fun}/#{length(args)} " <>
451-
"threw #{inspect(kind)}: " <> inspect(reason) <> "; falling back to :unidentified"
466+
"Backpex.Preferences: resolving identity via #{inspect({mod, fun, length(args)})} " <>
467+
"threw #{inspect(kind)}: #{inspect(reason)}; falling back to :unidentified"
452468
)
453469

454470
:unidentified

test/preferences_test.exs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,22 @@ defmodule Backpex.PreferencesTest do
228228
assert log =~ "fetch/3"
229229
end)
230230
end
231+
232+
test "collapses {:error, :unidentified} to :error without logging" do
233+
# Per Backpex.Preferences.Adapter, `:unidentified` on reads means
234+
# "treat as not found" — fetch/3 must return :error (matching the
235+
# stored-but-missing case) and must NOT log a warning, because the
236+
# condition is expected (anonymous visitors, background jobs, etc.).
237+
with_adapters([{:default, Backpex.PreferencesTest.UnidentifiedAdapter, []}], fn ->
238+
log =
239+
capture_log(fn ->
240+
assert Preferences.fetch(%{}, Keys.theme()) == :error
241+
end)
242+
243+
refute log =~ "Backpex.Preferences"
244+
refute log =~ ":unidentified"
245+
end)
246+
end
231247
end
232248

233249
describe "get/3 logs warnings on adapter error (m6 + M4)" do

0 commit comments

Comments
 (0)