# 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.
Log messages aren't printed to the console by default. If you're seeing them,
they may be coming from Elixir's default `:console` logger.
To see log messages as they come in, 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> Logger.remove_backend(:console)
:ok
iex> RingLogger.attach
:ok
iex> require Logger
iex> Logger.info("hello")
:ok
14:04:52.516 [info] hello
```
This probably isn't too exciting until you see that it works on remote shells as
well (the `:console` logger doesn't do this).
Say you prefer polling for log messages rather than having them print over your
console at random times. 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
```
## 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
]}
]
```