Skip to content

Commit 7b0b2de

Browse files
committed
Add option for specifying a pager
This removes the hardcoded printout and allows users to pass a custom pager.
1 parent 50d7d7e commit 7b0b2de

File tree

4 files changed

+133
-31
lines changed

4 files changed

+133
-31
lines changed

lib/ring_logger.ex

+27-6
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@ defmodule RingLogger do
3737
@typedoc "Option values used by the ring logger"
3838
@type server_option :: {:max_size, pos_integer()}
3939

40+
@typedoc "Callback function for printing/paging tail, grep, and next output"
41+
@type pager_fun :: (IO.device(), iodata() -> :ok | {:error, term()})
42+
4043
@typedoc "Option values used by client-side functions like `attach` and `tail`"
4144
@type client_option ::
4245
{:io, term}
46+
| {:pager, pager_fun()}
4347
| {:color, term}
4448
| {:metadata, Logger.metadata()}
4549
| {:format, String.t() | custom_formatter}
@@ -60,12 +64,14 @@ defmodule RingLogger do
6064
Attach the current IEx session to the logger. It will start printing log messages.
6165
6266
Options include:
63-
* `:io` - Defaults to `:stdio`
64-
* `:colors` -
65-
* `:metadata` - A KV list of additional metadata
66-
* `:format` - A custom format string
67-
* `:level` - The minimum log level to report.
68-
* `:module_levels` - A map of module to log level for module level logging. For example,
67+
68+
* `:io` - output location when printing. Defaults to `:stdio`
69+
* `:colors` - a keyword list of coloring options
70+
* `:metadata` - a keyword list of additional metadata
71+
* `:format` - the format message used to print logs
72+
* `:level` - the minimum log level to report by this backend. Note that the `:logger`
73+
application's `:level` setting filters log messages prior to `RingLogger`.
74+
* `:module_levels` - a map of log level overrides per module. For example,
6975
%{MyModule => :error, MyOtherModule => :none}
7076
"""
7177
@spec attach([client_option]) :: :ok
@@ -79,12 +85,22 @@ defmodule RingLogger do
7985

8086
@doc """
8187
Print the next messages in the log.
88+
89+
Options include:
90+
91+
* Options from `attach/1`
92+
* `:pager` - a function for printing log messages to the console. Defaults to `IO.binwrite/2`.
8293
"""
8394
@spec next([client_option]) :: :ok | {:error, term()}
8495
defdelegate next(opts \\ []), to: Autoclient
8596

8697
@doc """
8798
Print the last n messages in the log.
99+
100+
Options include:
101+
102+
* Options from `attach/1`
103+
* `:pager` - a function for printing log messages to the console. Defaults to `IO.binwrite/2`.
88104
"""
89105
@spec tail(non_neg_integer(), [client_option]) :: :ok | {:error, term()}
90106
def tail(), do: Autoclient.tail(10, [])
@@ -105,6 +121,11 @@ defmodule RingLogger do
105121
106122
iex> RingLogger.grep(~r/something/)
107123
:ok
124+
125+
Options include:
126+
127+
* Options from `attach/1`
128+
* `:pager` - a function for printing log messages to the console. Defaults to `IO.binwrite/2`.
108129
"""
109130
@spec grep(Regex.t(), [client_option]) :: :ok | {:error, term()}
110131
defdelegate grep(regex, opts \\ []), to: Autoclient

lib/ring_logger/autoclient.ex

+17-17
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ defmodule RingLogger.Autoclient do
1212
@doc """
1313
Attach to the logger and print messages as they come in.
1414
"""
15-
def attach(config \\ []) do
15+
def attach(opts \\ []) do
1616
with :ok <- check_server_started(),
17-
pid <- maybe_create_client(config),
17+
pid <- maybe_create_client(opts),
1818
do: Client.attach(pid)
1919
end
2020

@@ -42,37 +42,37 @@ defmodule RingLogger.Autoclient do
4242
@doc """
4343
Print the log messages since the previous time this was called.
4444
"""
45-
def next(config \\ []) do
45+
def next(opts \\ []) do
4646
with :ok <- check_server_started(),
47-
pid <- maybe_create_client(config),
48-
do: Client.next(pid)
47+
pid <- maybe_create_client(opts),
48+
do: Client.next(pid, opts)
4949
end
5050

5151
@doc """
5252
Print the most recent log messages.
5353
"""
54-
def tail(n, config) do
54+
def tail(n, opts) do
5555
with :ok <- check_server_started(),
56-
pid <- maybe_create_client(config),
57-
do: Client.tail(pid, n)
56+
pid <- maybe_create_client(opts),
57+
do: Client.tail(pid, n, opts)
5858
end
5959

6060
@doc """
6161
Run a regular expression on each entry in the log and print out the matchers.
6262
"""
63-
def grep(regex, config \\ []) do
63+
def grep(regex, opts \\ []) do
6464
with :ok <- check_server_started(),
65-
pid <- maybe_create_client(config),
66-
do: Client.grep(pid, regex)
65+
pid <- maybe_create_client(opts),
66+
do: Client.grep(pid, regex, opts)
6767
end
6868

6969
@doc """
7070
Reset the index used to keep track of the position in the log for `tail/1` so
7171
that the next call to `tail/1` starts back at the oldest entry.
7272
"""
73-
def reset(config \\ []) do
73+
def reset(opts \\ []) do
7474
with :ok <- check_server_started(),
75-
pid <- maybe_create_client(config),
75+
pid <- maybe_create_client(opts),
7676
do: Client.reset(pid)
7777
end
7878

@@ -81,7 +81,7 @@ defmodule RingLogger.Autoclient do
8181
"""
8282
def format(message) do
8383
with :ok <- check_server_started(),
84-
pid <- maybe_create_client(),
84+
pid <- maybe_create_client([]),
8585
do: Client.format(pid, message)
8686
end
8787

@@ -123,16 +123,16 @@ defmodule RingLogger.Autoclient do
123123
end
124124
end
125125

126-
defp maybe_create_client(config \\ []) do
126+
defp maybe_create_client(opts) do
127127
case get_client_pid() do
128128
nil ->
129-
{:ok, pid} = Client.start_link(config)
129+
{:ok, pid} = Client.start_link(opts)
130130
Process.put(:ring_logger_client, pid)
131131
pid
132132

133133
pid ->
134134
# Update the configuration if the user changed something
135-
Client.configure(pid, config)
135+
Client.configure(pid, opts)
136136
pid
137137
end
138138
end

lib/ring_logger/client.ex

+26-8
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,32 @@ defmodule RingLogger.Client do
6767

6868
@doc """
6969
Get the last n messages.
70+
71+
Supported options:
72+
73+
* `:pager` - an optional 2-arity function that takes an IO device and what to print
7074
"""
7175
@spec tail(GenServer.server(), non_neg_integer()) :: :ok | {:error, term()}
72-
def tail(client_pid, n) do
76+
def tail(client_pid, n, opts \\ []) do
7377
{io, to_print} = GenServer.call(client_pid, {:tail, n})
74-
IO.binwrite(io, to_print)
78+
79+
pager = Keyword.get(opts, :pager, &IO.binwrite/2)
80+
pager.(io, to_print)
7581
end
7682

7783
@doc """
7884
Get the next set of the messages in the log.
85+
86+
Supported options:
87+
88+
* `:pager` - an optional 2-arity function that takes an IO device and what to print
7989
"""
80-
@spec next(GenServer.server()) :: :ok | {:error, term()}
81-
def next(client_pid) do
90+
@spec next(GenServer.server(), keyword()) :: :ok | {:error, term()}
91+
def next(client_pid, opts \\ []) do
8292
{io, to_print} = GenServer.call(client_pid, :next)
83-
IO.binwrite(io, to_print)
93+
94+
pager = Keyword.get(opts, :pager, &IO.binwrite/2)
95+
pager.(io, to_print)
8496
end
8597

8698
@doc """
@@ -102,11 +114,17 @@ defmodule RingLogger.Client do
102114

103115
@doc """
104116
Run a regular expression on each entry in the log and print out the matchers.
117+
118+
Supported options:
119+
120+
* `:pager` - an optional 2-arity function that takes an IO device and what to print
105121
"""
106-
@spec grep(GenServer.server(), Regex.t()) :: :ok | {:error, term()}
107-
def grep(client_pid, regex) do
122+
@spec grep(GenServer.server(), Regex.t(), keyword()) :: :ok | {:error, term()}
123+
def grep(client_pid, regex, opts \\ []) do
108124
{io, to_print} = GenServer.call(client_pid, {:grep, regex})
109-
IO.binwrite(io, to_print)
125+
126+
pager = Keyword.get(opts, :pager, &IO.binwrite/2)
127+
pager.(io, to_print)
110128
end
111129

112130
def init(config) do

test/ring_logger_test.exs

+63
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,69 @@ defmodule RingLoggerTest do
233233
assert [{:debug, {Logger, "Bar", _, _}}, {:debug, {Logger, "Baz", _, _}}] = buffer
234234
end
235235

236+
test "next supports passing a custom pager", %{io: io} do
237+
:ok = RingLogger.attach(io: io)
238+
239+
io
240+
|> handshake_log(:info, "Hello")
241+
|> handshake_log(:debug, "Foo")
242+
|> handshake_log(:debug, "Bar")
243+
244+
# Even thought the intention for a custom pager is to "page" the output to the user,
245+
# just print out the number of characters as a check that the custom function is
246+
# actually run.
247+
:ok =
248+
RingLogger.next(
249+
pager: fn device, msg ->
250+
IO.write(device, "Got #{String.length(IO.iodata_to_binary(msg))} characters")
251+
end
252+
)
253+
254+
assert_receive {:io, messages}
255+
256+
assert messages =~ "Got 107 characters"
257+
end
258+
259+
test "tail supports passing a custom pager", %{io: io} do
260+
:ok = RingLogger.attach(io: io)
261+
262+
io
263+
|> handshake_log(:info, "Hello")
264+
|> handshake_log(:debug, "Foo")
265+
|> handshake_log(:debug, "Bar")
266+
267+
:ok =
268+
RingLogger.tail(2,
269+
pager: fn device, msg ->
270+
IO.write(device, "Got #{String.length(IO.iodata_to_binary(msg))} characters")
271+
end
272+
)
273+
274+
assert_receive {:io, messages}
275+
276+
assert messages =~ "Got 70 characters"
277+
end
278+
279+
test "grep supports passing a custom pager", %{io: io} do
280+
:ok = RingLogger.attach(io: io)
281+
282+
io
283+
|> handshake_log(:info, "Hello")
284+
|> handshake_log(:debug, "Foo")
285+
|> handshake_log(:debug, "Bar")
286+
287+
:ok =
288+
RingLogger.grep(~r/debug/,
289+
pager: fn device, msg ->
290+
IO.write(device, "Got #{String.length(IO.iodata_to_binary(msg))} characters")
291+
end
292+
)
293+
294+
assert_receive {:io, messages}
295+
296+
assert messages =~ "Got 70 characters"
297+
end
298+
236299
test "buffer start index is less then buffer_start_index", %{io: io} do
237300
Logger.configure_backend(RingLogger, max_size: 1)
238301

0 commit comments

Comments
 (0)