Skip to content

Commit

Permalink
Add option for specifying a pager
Browse files Browse the repository at this point in the history
This removes the hardcoded printout and allows users to pass a custom
pager.
  • Loading branch information
fhunleth committed Oct 22, 2018
1 parent 50d7d7e commit 7b0b2de
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 31 deletions.
33 changes: 27 additions & 6 deletions lib/ring_logger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ defmodule RingLogger do
@typedoc "Option values used by the ring logger"
@type server_option :: {:max_size, pos_integer()}

@typedoc "Callback function for printing/paging tail, grep, and next output"
@type pager_fun :: (IO.device(), iodata() -> :ok | {:error, term()})

@typedoc "Option values used by client-side functions like `attach` and `tail`"
@type client_option ::
{:io, term}
| {:pager, pager_fun()}
| {:color, term}
| {:metadata, Logger.metadata()}
| {:format, String.t() | custom_formatter}
Expand All @@ -60,12 +64,14 @@ defmodule RingLogger do
Attach the current IEx session to the logger. It will start printing log messages.
Options include:
* `:io` - Defaults to `:stdio`
* `:colors` -
* `:metadata` - A KV list of additional metadata
* `:format` - A custom format string
* `:level` - The minimum log level to report.
* `:module_levels` - A map of module to log level for module level logging. For example,
* `:io` - output location when printing. Defaults to `:stdio`
* `:colors` - a keyword list of coloring options
* `:metadata` - a keyword list of additional metadata
* `:format` - the format message used to print logs
* `:level` - the minimum log level to report by this backend. Note that the `:logger`
application's `:level` setting filters log messages prior to `RingLogger`.
* `:module_levels` - a map of log level overrides per module. For example,
%{MyModule => :error, MyOtherModule => :none}
"""
@spec attach([client_option]) :: :ok
Expand All @@ -79,12 +85,22 @@ defmodule RingLogger do

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

@doc """
Print the last n messages in the log.
Options include:
* Options from `attach/1`
* `:pager` - a function for printing log messages to the console. Defaults to `IO.binwrite/2`.
"""
@spec tail(non_neg_integer(), [client_option]) :: :ok | {:error, term()}
def tail(), do: Autoclient.tail(10, [])
Expand All @@ -105,6 +121,11 @@ defmodule RingLogger do
iex> RingLogger.grep(~r/something/)
:ok
Options include:
* Options from `attach/1`
* `:pager` - a function for printing log messages to the console. Defaults to `IO.binwrite/2`.
"""
@spec grep(Regex.t(), [client_option]) :: :ok | {:error, term()}
defdelegate grep(regex, opts \\ []), to: Autoclient
Expand Down
34 changes: 17 additions & 17 deletions lib/ring_logger/autoclient.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ defmodule RingLogger.Autoclient do
@doc """
Attach to the logger and print messages as they come in.
"""
def attach(config \\ []) do
def attach(opts \\ []) do
with :ok <- check_server_started(),
pid <- maybe_create_client(config),
pid <- maybe_create_client(opts),
do: Client.attach(pid)
end

Expand Down Expand Up @@ -42,37 +42,37 @@ defmodule RingLogger.Autoclient do
@doc """
Print the log messages since the previous time this was called.
"""
def next(config \\ []) do
def next(opts \\ []) do
with :ok <- check_server_started(),
pid <- maybe_create_client(config),
do: Client.next(pid)
pid <- maybe_create_client(opts),
do: Client.next(pid, opts)
end

@doc """
Print the most recent log messages.
"""
def tail(n, config) do
def tail(n, opts) do
with :ok <- check_server_started(),
pid <- maybe_create_client(config),
do: Client.tail(pid, n)
pid <- maybe_create_client(opts),
do: Client.tail(pid, n, opts)
end

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

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

Expand All @@ -81,7 +81,7 @@ defmodule RingLogger.Autoclient do
"""
def format(message) do
with :ok <- check_server_started(),
pid <- maybe_create_client(),
pid <- maybe_create_client([]),
do: Client.format(pid, message)
end

Expand Down Expand Up @@ -123,16 +123,16 @@ defmodule RingLogger.Autoclient do
end
end

defp maybe_create_client(config \\ []) do
defp maybe_create_client(opts) do
case get_client_pid() do
nil ->
{:ok, pid} = Client.start_link(config)
{:ok, pid} = Client.start_link(opts)
Process.put(:ring_logger_client, pid)
pid

pid ->
# Update the configuration if the user changed something
Client.configure(pid, config)
Client.configure(pid, opts)
pid
end
end
Expand Down
34 changes: 26 additions & 8 deletions lib/ring_logger/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,32 @@ defmodule RingLogger.Client do

@doc """
Get the last n messages.
Supported options:
* `:pager` - an optional 2-arity function that takes an IO device and what to print
"""
@spec tail(GenServer.server(), non_neg_integer()) :: :ok | {:error, term()}
def tail(client_pid, n) do
def tail(client_pid, n, opts \\ []) do
{io, to_print} = GenServer.call(client_pid, {:tail, n})
IO.binwrite(io, to_print)

pager = Keyword.get(opts, :pager, &IO.binwrite/2)
pager.(io, to_print)
end

@doc """
Get the next set of the messages in the log.
Supported options:
* `:pager` - an optional 2-arity function that takes an IO device and what to print
"""
@spec next(GenServer.server()) :: :ok | {:error, term()}
def next(client_pid) do
@spec next(GenServer.server(), keyword()) :: :ok | {:error, term()}
def next(client_pid, opts \\ []) do
{io, to_print} = GenServer.call(client_pid, :next)
IO.binwrite(io, to_print)

pager = Keyword.get(opts, :pager, &IO.binwrite/2)
pager.(io, to_print)
end

@doc """
Expand All @@ -102,11 +114,17 @@ defmodule RingLogger.Client do

@doc """
Run a regular expression on each entry in the log and print out the matchers.
Supported options:
* `:pager` - an optional 2-arity function that takes an IO device and what to print
"""
@spec grep(GenServer.server(), Regex.t()) :: :ok | {:error, term()}
def grep(client_pid, regex) do
@spec grep(GenServer.server(), Regex.t(), keyword()) :: :ok | {:error, term()}
def grep(client_pid, regex, opts \\ []) do
{io, to_print} = GenServer.call(client_pid, {:grep, regex})
IO.binwrite(io, to_print)

pager = Keyword.get(opts, :pager, &IO.binwrite/2)
pager.(io, to_print)
end

def init(config) do
Expand Down
63 changes: 63 additions & 0 deletions test/ring_logger_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,69 @@ defmodule RingLoggerTest do
assert [{:debug, {Logger, "Bar", _, _}}, {:debug, {Logger, "Baz", _, _}}] = buffer
end

test "next supports passing a custom pager", %{io: io} do
:ok = RingLogger.attach(io: io)

io
|> handshake_log(:info, "Hello")
|> handshake_log(:debug, "Foo")
|> handshake_log(:debug, "Bar")

# Even thought the intention for a custom pager is to "page" the output to the user,
# just print out the number of characters as a check that the custom function is
# actually run.
:ok =
RingLogger.next(
pager: fn device, msg ->
IO.write(device, "Got #{String.length(IO.iodata_to_binary(msg))} characters")
end
)

assert_receive {:io, messages}

assert messages =~ "Got 107 characters"
end

test "tail supports passing a custom pager", %{io: io} do
:ok = RingLogger.attach(io: io)

io
|> handshake_log(:info, "Hello")
|> handshake_log(:debug, "Foo")
|> handshake_log(:debug, "Bar")

:ok =
RingLogger.tail(2,
pager: fn device, msg ->
IO.write(device, "Got #{String.length(IO.iodata_to_binary(msg))} characters")
end
)

assert_receive {:io, messages}

assert messages =~ "Got 70 characters"
end

test "grep supports passing a custom pager", %{io: io} do
:ok = RingLogger.attach(io: io)

io
|> handshake_log(:info, "Hello")
|> handshake_log(:debug, "Foo")
|> handshake_log(:debug, "Bar")

:ok =
RingLogger.grep(~r/debug/,
pager: fn device, msg ->
IO.write(device, "Got #{String.length(IO.iodata_to_binary(msg))} characters")
end
)

assert_receive {:io, messages}

assert messages =~ "Got 70 characters"
end

test "buffer start index is less then buffer_start_index", %{io: io} do
Logger.configure_backend(RingLogger, max_size: 1)

Expand Down

0 comments on commit 7b0b2de

Please sign in to comment.