Current section
Files
Jump to
Current section
Files
lib/og.ex
defmodule Og do
@moduledoc ~S"""
Óg is a small collection of debugging helper functions. Og is a debugging tool primarily
intendend for development, the use of ordinary `Logger` is preferred for production.
## Summary
- `log/2` - logs the data transformed by the inspector function
and returns `:ok`
- `log_r/2` - logs the data transformed by the inspector function
and returns the original data.
- Inspection of the data before logging it can be helpful in a debugging context for
example for
- Avoiding the `Protocol.UndefinedError` when logging tuples.
- Not needing to require Logger
- Formatting with `Kernel.inspect` on the data carries an overhead so
`Og.log/2` and `Og.log_r/2` should be probably be reserved for debugging code in `:dev`
environments.
## Configuration
config :logger, :og,
kernel_opts: [width: 70],
apex_opts: [numbers: :false, color: :false],
sanitize_by_default: :false,
default_inspector: :kernel
## Configuration options
- `kernel_opts` - corresponds to elixir inspect opts for `IO.inspect/2`
and `Kernel.inspect/2`, refer to `https://hexdocs.pm/elixir/Inspect.Opts.html`
- `apex_opts` - corresponds to options for the `Apex.Format.format/2` function,
refer to `https://github.com/BjRo/apex/blob/master/lib/apex/format.ex`
- `sanitize_by_default` - defaults to `:false`, when set to `:true`, an attempt will
be made to apply the `SecureLogFormatter.sanitize/1` function on the data
before applying the inspection function. For this function to take any
effect, the settings for `SecureLogFormatter` must also be placed in
`config.exs`. See
[secure_log_formatter](https://github.com/localvore-today/secure_log_formatter/blob/master/lib/secure_log_formatter.ex)
for more details.
- `default_inspector` - corresponds to the default inspector which will apply
to the data passed to the log function. This can be overriden in the options
of the log function. The options are `:kernel` or `:apex`, the default is `:kernel`.
Example configuration for secure_log_formatter, as referenced at
the following [secure_log_formatter url](https://github.com/localvore-today/secure_log_formatter/blob/master/lib/secure_log_formatter.ex).
config :logger,
secure_log_formatter:
[
# Map and Keyword List keys who's value should be hidden
fields: ["password", "credit_card", ~r/.*_token/],
# Patterns which if found, should be hidden
patterns: [~r/4[0-9]{15}/] # Simple credit card example
# defaults to "[REDACTED]"
replacement: "[PRIVATE]"
]
"""
require Logger
defp kernel_opts(), do: Application.get_env(:logger, :og, [])
|> Keyword.get(:kernel_opts, [])
defp apex_opts(), do: Application.get_env(:logger, :og, [])
|> Keyword.get(:apex_opts, [color: :false, numbers: :false])
defp default_inspector(), do: Application.get_env(:logger, :og, [])
|> Keyword.get(:default_inspector, :kernel)
defp sanitize_by_default?(), do: Application.get_env(:logger, :og, [])
|> Keyword.get(:sanitize_by_default, :false)
# Public
@doc """
Formats the data using an inspector function, logs it and returns the
atom `:ok`.
## Notes:
There is an overhead in converting the data to
other formats such as a binary representation. Hence,
`Og.log/2` and `Og.log_r/2` are preferred for
development debugging purposes only.
## opts
- ***level***: defaults to `:debug`.
- ***env***: defaults to `:nil`.
- ***inspector***: defaults to `:default_inspector` in the application config.exs and if not
set falls back to `Kernel.inspect/2`.
The inspector function determines what function transforms the data prior to logging.
Currently the options are `:kernel` or `:apex` which use
the functions `&Kernel.inspect/2` and `&Apex.Format.format/2` respectively.
- ***sanitize***: defaults to `:sanitize_by_default` in the application config.exs and if not
set falls back to `:false`.
When set to `:true` the function `SecureLogFormatter.sanitize/1` will be applied to
the data prior to logging the data. See
[secure_log_formatter](https://github.com/localvore-today/secure_log_formatter/blob/master/lib/secure_log_formatter.ex)
for more details.
## Examples:
Og.log(%{test: "test"})
Og.log(%{test: "test"}, level: :info)
Og.log(%{test: "test"}, env: __ENV__)
Og.log(%{test: "test"}, inspector: :apex)
Og.log(%{credit_card: "4111111111111111"}, sanitize: :true)
"""
@spec log(any, Keyword.t) :: :ok
def log(data, opts \\ [])
def log(data, opts) when is_list(opts) do
inspector = Keyword.get(opts, :inspector, default_inspector())
sanitize? = Keyword.get(opts, :sanitize, sanitize_by_default?())
inspector_opts =
case inspector do
:kernel -> kernel_opts()
:apex -> apex_opts()
_ -> kernel_opts()
end
inspector =
case inspector do
:kernel -> &Kernel.inspect/2
:apex -> &Apex.Format.format/2
_ -> &Kernel.inspect/2
end
data =
case sanitize? do
:true -> SecureLogFormatter.sanitize(data)
:false -> data
end
env = Keyword.get(opts, :env, :nil)
level = Keyword.get(opts, :level, :debug)
(base_details(env) <> inspector.(data, inspector_opts))
|> log_data(level)
:ok
end
@spec log(data :: any, env :: Macro.Env.t) :: :ok
def log(data, env_or_level)
@spec log(data :: any, level :: atom) :: :ok
def log(data, :error), do: Og.log(data, level: :error)
def log(data, :warn), do: Og.log(data, level: :warn)
def log(data, :info), do: Og.log(data, level: :info)
def log(data, :debug), do: Og.log(data, level: :debug)
@spec log(data :: any, env :: Macro.Env.t, level :: atom) :: :ok
def log(data, %Macro.Env{} = arg, :error), do: Og.log(data, env: arg, level: :error)
def log(data, %Macro.Env{} = arg, :warn), do: Og.log(data, env: arg, level: :warn)
def log(data, %Macro.Env{} = arg, :info), do: Og.log(data, env: arg, level: :info)
def log(data, %Macro.Env{} = arg, :debug), do: Og.log(data, env: arg, level: :debug)
@spec log(data :: any, level :: atom, env :: Macro.Env.t) :: :ok
def log(data, :error, %Macro.Env{} = arg), do: Og.log(data, env: arg, level: :error)
def log(data, :warn, %Macro.Env{} = arg), do: Og.log(data, env: arg, level: :warn)
def log(data, :info, %Macro.Env{} = arg), do: Og.log(data, env: arg, level: :info)
def log(data, :debug, %Macro.Env{} = arg), do: Og.log(data, env: arg, level: :debug)
@doc """
Formats the data using an inspector function, logs it and returns the
original data.
## Notes:
There is an overhead in converting the data to
other formats such as a binary representation. Hence,
`Og.log/2` and `Og.log_r/2` are preferred for
development debugging purposes only.
## opts
- ***level***: defaults to `:debug`.
- ***env***: defaults to `:nil`.
- ***inspector***: defaults to `:default_inspector` in the application config.exs.
The inspector function determines what function transforms the data prior to logging.
Currently the options are `:kernel` or `:apex` which use
the functions `&Kernel.inspect/2` and `&Apex.Format.format/2` respectively.
- ***sanitize***: defaults to `:sanitize_by_default` in the application config.exs.
When set to `:true` the function `SecureLogFormatter.sanitize/1` will be applied to
the data prior to logging the data. See
[secure_log_formatter](https://github.com/localvore-today/secure_log_formatter/blob/master/lib/secure_log_formatter.ex)
for more details.
## Examples:
Og.log(%{test: "test"})
Og.log(%{test: "test"}, level: :info)
Og.log(%{test: "test"}, env: __ENV__)
Og.log(%{test: "test"}, inspector: :apex)
Og.log(%{credit_card: "4111111111111111"}, sanitize: :true)
"""
@spec log_r(any, Keyword.t) :: any
def log_r(data, opts \\ [])
def log_r(data, opts) when is_list(opts) do
log(data, opts)
data
end
@spec log_r(data :: any, env :: Macro.Env.t) :: any
def log_r(data, %Macro.Env{} = env), do: Og.log_r(data, env: env)
@spec log_r(data :: any, level :: atom) :: any
def log_r(data, :error), do: Og.log_r(data, level: :error)
def log_r(data, :warn), do: Og.log_r(data, level: :warn)
def log_r(data, :info), do: Og.log_r(data, level: :info)
def log_r(data, :debug), do: Og.log_r(data, level: :debug)
@spec log_r(data :: any, env :: Macro.Env.t, level :: atom) :: any
def log_r(data, %Macro.Env{} = arg, :error), do: Og.log_r(data, env: arg, level: :error)
def log_r(data, %Macro.Env{} = arg, :warn), do: Og.log_r(data, env: arg, level: :warn)
def log_r(data, %Macro.Env{} = arg, :info), do: Og.log_r(data, env: arg, level: :info)
def log_r(data, %Macro.Env{} = arg, :debug), do: Og.log_r(data, env: arg, level: :debug)
@spec log_r(data :: any, level :: atom, env :: Macro.Env.t) :: any
def log_r(data, :error, %Macro.Env{} = arg), do: Og.log_r(data, env: arg, level: :error)
def log_r(data, :warn, %Macro.Env{} = arg), do: Og.log_r(data, env: arg, level: :warn)
def log_r(data, :info, %Macro.Env{} = arg), do: Og.log_r(data, env: arg, level: :info)
def log_r(data, :debug, %Macro.Env{} = arg), do: Og.log_r(data, env: arg, level: :debug)
# Private
defp base_details(:nil), do: ""
defp base_details(env) do
put_into_string = fn(elem, name) ->
case elem do
:nil -> :nil
_ -> "#{name}: #{elem}, "
end
end
function =
case env.function do
:nil -> :nil
{function, arity} -> "#{function}/#{arity}"
end
application = Application.get_env(:logger, :compile_time_application) |> put_into_string.("application")
module = put_into_string.(env.module, "module")
function = put_into_string.(function, "function")
line = put_into_string.(env.line, "line") |> String.trim_trailing(" ") |> String.trim_trailing(",")
"#{application}#{module}#{function}#{line}:\n"
end
defp log_data(formatted_data, level) do
cond do
level == :error -> Logger.error(formatted_data)
level == :warn -> Logger.warn(formatted_data)
level == :info -> Logger.info(formatted_data)
level == :debug -> Logger.debug(formatted_data)
:true -> raise("unexpected log level")
end
end
end