defmodule Appsignal.IntegrationLogger do
require Appsignal.Utils
@io Appsignal.Utils.compile_env(:appsignal, :io, IO)
@file_module Appsignal.Utils.compile_env(:appsignal, :file, File)
@log_levels [:trace, :debug, :info, :warn, :error]
@type log_level :: :trace | :debug | :info | :warn | :error
@type device :: :stdio | :stderr | :file
@spec trace(String.t()) :: :ok
def trace(message) do
log(:trace, message, [])
end
@spec debug(String.t()) :: :ok
def debug(message) do
log(:debug, message, [])
end
@spec info(String.t()) :: :ok
def info(message) do
log(:info, message, [])
end
@spec warn(String.t()) :: :ok
def warn(message, options \\ []) do
log(:warn, message, options)
end
@spec error(String.t()) :: :ok
def error(message, options \\ []) do
log(:error, message, options)
end
@spec log(log_level(), String.t(), keyword()) :: :ok
defp log(level, message, options) do
threshold = Appsignal.Config.log_level()
if log_level?(level, threshold) do
case {device(), Keyword.get(options, :stderr, false)} do
{device, false} ->
do_log(device, level, message)
{:stdio, true} ->
do_log(:stderr, level, message)
{:file, true} ->
do_log(:file, level, message)
do_log(:stderr, level, message)
end
end
end
@spec device() :: device()
defp device do
case Application.fetch_env!(:appsignal, :config)[:log] do
"stdout" -> :stdio
_ -> :file
end
end
@spec log_level?(log_level(), log_level()) :: bool()
defp log_level?(level, threshold) do
Enum.find_index(@log_levels, &(&1 == level)) >=
Enum.find_index(@log_levels, &(&1 == threshold))
end
@spec do_log(device(), log_level(), String.t()) :: :ok
defp do_log(device, level, message) do
time = NaiveDateTime.from_erl!(:calendar.local_time())
pid = System.pid()
puts(device, format(device, time, pid, level, message))
end
@spec puts(device(), String.t()) :: :ok
defp puts(device, content)
defp puts(:file, content) do
@file_module.write(Appsignal.Config.log_file_path(), content <> "\n", [:append, :utf8])
:ok
end
defp puts(device, content) do
@io.puts(device, content)
:ok
end
@spec format(device(), NaiveDateTime.t(), binary(), log_level(), String.t()) :: String.t()
defp format(device, time, pid, level, message) do
time = format_time(time)
level = format_level(level)
case device do
:stdio -> "\n[#{time} (process) ##{pid}][appsignal][#{level}] #{message}"
:stderr -> "\n[appsignal][#{level}] #{message}"
:file -> "[#{time} (process) ##{pid}][#{level}] #{message}"
end
end
@spec format_level(log_level()) :: String.t()
defp format_level(level) do
case level do
:trace -> "TRACE"
:debug -> "DEBUG"
:info -> "INFO"
:warn -> "WARNING"
:error -> "ERROR"
end
end
@spec format_time(NaiveDateTime.t()) :: String.t()
defp format_time(time) do
time
|> NaiveDateTime.truncate(:second)
|> NaiveDateTime.to_iso8601()
end
end