README.md

# ring_logger

[![CircleCI](https://circleci.com/gh/nerves-project/ring_logger.svg?style=svg)](https://circleci.com/gh/nerves-project/ring_logger)
[![Hex version](https://img.shields.io/hexpm/v/ring_logger.svg "Hex version")](https://hex.pm/packages/ring_logger)

This is an in-memory ring buffer backend for the [Elixir
Logger](https://hexdocs.pm/logger/Logger.html) with convenience methods for
accessing the logs from the IEx prompt.

Use cases:

* Get log messages in real-time over remote IEx sessions
* Grep and tail through log messages without setting up anything else
* Keep logs in limited resource environments
* Capture recent log events for error reports

As a bonus, `ring_logger` is nice to your IEx prompt. If you attach to the log
and are receiving messages as they're sent, they won't stomp what you're typing.

## Configuration

Add `ring_logger` to your projects dependencies in your `mix.exs`:

```elixir
  def deps do
    [{:ring_logger, "~> 0.6"}]
  end
```

Then configure the logger in your `config/config.exs`:

```elixir
use Mix.Config

# Add the RingLogger backend. This removes the default :console backend.
config :logger, backends: [RingLogger]

# Periodically save logs to a file, and load logs on GenServer start from this file
config :logger, RingLogger, persist_path: "./myapp.log", persist_seconds: 300

# Save messages to one circular buffer that holds 1024 entries.
config :logger, RingLogger, max_size: 1024

# Separate out `:error` and `:warning` messages to their own circular buffer.
# All other log messages are stored in the default circular buffer.
config :logger, RingLogger, buffers: %{
  errors: %{
    levels: [:error, :warning],
    max_size: 1024
  }
}

# Specify circular buffers for all log levels. The default circular buffer won't
# be used in this example configuration.
config :logger, RingLogger, buffers: %{
  low_priority: %{
    levels: [:warning, :notice, :info, :debug],
    max_size: 1024
  },
  high_priority: %{
    levels: [:emergency, :alert, :critical, :error],
    max_size: 1024
  }
}

# You can also configure `RingLogger.Client` options to be used
# with every client by default
config :logger, RingLogger,
  application_levels: %{my_app: :error},
  colors: [debug: :yellow],
  level: :debug
```

Or you can start the backend manually by running the following:

```elixir
Logger.add_backend(RingLogger)
Logger.configure_backend(RingLogger, max_size: 1024)
```

## IEx session usage

See the example project for a hands-on walk-through of using the logger. Read on
for the highlights.

For the purpose of the example, when you're in IEx, log messages shouldn't be
printed to the console by default. They'll be coming from the console logger, so
turn them off:

```elixir
iex> Logger.remove_backend(:console)
:ok
```

To see log messages as they come in with RingLogger, call `RingLogger.attach()`
and then to make the log messages stop, call `RingLogger.detach()`. The `attach`
method takes options if you want to limit the log level, change the formatting,
etc.

Here's an example:

```elixir
iex> Logger.add_backend(RingLogger)
{:ok, #PID<0.199.0>}
iex> RingLogger.attach
:ok
iex> require Logger
iex> Logger.info("hello")
:ok

14:04:52.516 [info]  hello
```

If you prefer polling for log messages rather than having them print when they
show up. If you're still attached, then `detach` and `next`:

```elixir
iex> RingLogger.detach
:ok
iex> Logger.info("Hello logger, how are you?")
:ok
iex> Logger.info("It's a nice day. Wouldn't you say?")
:ok
iex> RingLogger.next

14:04:52.516 [info]  hello

14:11:54.397 [info]  Hello logger, how are you?

14:12:09.180 [info]  It's a nice day. Wouldn't you say?
:ok
iex> RingLogger.next
:ok
```

If you only want to see the most recent entries, run `tail`:

```elixir
iex> RingLogger.tail

14:04:52.516 [info]  hello

14:11:54.397 [info]  Hello logger, how are you?

14:12:09.180 [info]  It's a nice day. Wouldn't you say?
:ok
```

You can also `grep`:

```elixir
iex> RingLogger.grep(~r/[Nn]eedle/)

16:55:41.614 [info]  Needle in a haystack
```

## RingLogger TUI

RingLogger provides a simple text UI that lets you access log viewing features
in a friendly way.

```elixir
iex> RingLogger.viewer()
```

Type `h` and then enter for help.

## Module and Application Level Filtering

If you want to filter a module or modules at a particular level you pass a map
where the key is the module name and value in the level into the
`:module_levels` option to `RingLogger.attach/1`.

For example:

```elixir
iex> RingLogger.attach(module_levels: %{MyModule => :info})
```

This will ignore all the `:debug` messages from `MyModule`.

Also, it allows for filtering the whole project on a higher level, but a
particular module, or a subset of modules, to log at a lower level like so:

```elixir
iex> RingLogger.attach(module_levels: %{MyModule => :debug}, level: :warn)
```

In the example above log messages at the `:debug` level will be logged, but
every other module will be logging at the `:warn` level. You can also turn off a
module's logging completely by specifying `:none`.

Additionally, you can specify the same options at the application level to
disable logging for all its modules using the `:application_levels` option
with OTP application names as the key:

```elixir
iex> RingLogger.attach(application_levels: %{my_app: :info})
```

`module_levels` takes precedence in the case of including both module and
application level filtering:

```elixir
iex> RingLogger.attach(application_levels: %{my_app: :info}, module_levels: %{MyApp.Important => :debug})
```

In the above example, all modules of `:my_app` with have a level of `:info` except
for `MyApp.Important`, which will have a level of `:debug`.

As a note if the Elixir `Logger` level is set too low you will miss some log
messages.

## Saving the log

By design, `RingLogger` doesn't save logs. It can be convenient to share the
current log buffer for later analysis:

```elixir
iex> RingLogger.save("/tmp/log.txt")
:ok
```

Log messages are formatted the same way as the `RingLogger` functions that
output to the console.

## Formatting

If you want to use a specific string format with the built in Elixir
Logger.Formatter, you can pass that as the `:format` option to
`RingLogger.attach/1`.

If you want to use a custom formatter function, you can pass it through the
`:format` option to `RingLogger.attach/1` instead.

For example, to print the file and line number of each log message, you could
define a function as follows:

```elixir
defmodule CustomFormatter do
  def format(_level, message, _timestamp, metadata) do
    "#{message} #{metadata[:file]}:#{metadata[:line]}\n"
  rescue
    _ -> message
  end
end
```

and attach to the RingLogger with:

```elixir
iex> RingLogger.attach(format: {CustomFormatter, :format}, metadata: [:file, :line])
:ok
iex> require Logger
Logger
iex> Logger.info("Important message!")
:ok
Important message! iex:4
```

Within an application, the `iex:4` would be the source file path and line number.

See [Logger custom formatting](https://hexdocs.pm/logger/Logger.html#module-custom-formatting)
for more information.

## Programmatic usage

It can be useful to get a snapshot of the log when an unexpected event occurs.
The commandline functions demonstrated above are available, but you can also get
the raw log entries by calling `RingLogger.get/0`:

```elixir
iex> RingLogger.get
[
  debug: {Logger, "8", {{2018, 2, 5}, {17, 44, 7, 675}},
   [
     pid: #PID<0.139.0>,
     application: :example,
     module: Example,
     function: "log/1",
     file: "ring_logger/example/lib/example.ex",
     line: 11
   ]},
  debug: {Logger, "9", {{2018, 2, 5}, {17, 44, 8, 676}},
   [
     pid: #PID<0.139.0>,
     application: :example,
     module: Example,
     function: "log/1",
     file: "ring_logger/example/lib/example.ex",
     line: 11
   ]},
  debug: {Logger, "10", {{2018, 2, 5}, {17, 44, 9, 677}},
   [
     pid: #PID<0.139.0>,
     application: :example,
     module: Example,
     function: "log/1",
     file: "ring_logger/example/lib/example.ex",
     line: 11
   ]}
]
```