Current section

Files

Jump to
new_relic_agent lib new_relic logs_in_context.ex
Raw

lib/new_relic/logs_in_context.ex

defmodule NewRelic.LogsInContext do
@moduledoc false
alias NewRelic.Harvest.Collector.AgentRun
alias NewRelic.Harvest.TelemetrySdk
@elixir_version_requirement ">= 1.10.0"
def elixir_version_supported?(mode) do
case Version.match?(System.version(), @elixir_version_requirement) do
true ->
true
false ->
mode in [:direct, :forwarder] &&
NewRelic.log(:error, ":logs_in_context requires Elixir 1.10 or greater")
false
end
end
def configure(:direct) do
:logger.add_primary_filter(:nr_logs_in_context, {&primary_filter/2, %{mode: :direct}})
end
def configure(:forwarder) do
:logger.add_primary_filter(:nr_logs_in_context, {&primary_filter/2, %{mode: :forwarder}})
Logger.configure_backend(:console, format: {NewRelic.LogsInContext, :format})
end
def configure(:disabled) do
:skip
end
def configure(unknown) do
NewRelic.log(:error, "Unknown :logs_in_context mode: #{inspect(unknown)}")
:skip
end
def primary_filter(%{msg: {:string, _msg}} = log, %{mode: :direct}) do
log
|> prepare_log()
|> TelemetrySdk.Logs.Harvester.report_log()
log
end
def primary_filter(%{msg: {:string, _msg}} = log, %{mode: :forwarder}) do
message =
log
|> prepare_log()
|> Map.merge(linking_metadata())
|> Jason.encode!()
%{log | msg: {:string, message}}
end
def primary_filter(_log, _config) do
:ignore
end
defp prepare_log(%{msg: {:string, msg}} = log) do
%{
message: msg,
timestamp: System.convert_time_unit(log.meta.time, :microsecond, :millisecond),
"log.level": log.level
}
|> Map.merge(log_metadata(log))
|> Map.merge(logger_metadata())
|> Map.merge(tracing_metadata())
end
def format(_level, message, _timestamp, _metadata) when is_binary(message) do
message <> "\n"
end
# Fallback to default formatter for future compatibility with Elixir structured logging
def format(level, message, timestamp, metadata) do
config = Logger.Formatter.compile(nil)
Logger.Formatter.format(config, level, message, timestamp, metadata)
end
defp log_metadata(log) do
{module, function, arity} = log.meta.mfa
%{
"line.number": log.meta.line,
"file.name": log.meta.file |> to_string,
"module.name": inspect(module),
"function.name": "#{function}/#{arity}",
"process.pid": inspect(log.meta.pid)
}
end
defp logger_metadata() do
case :logger.get_process_metadata() do
:undefined ->
%{}
metadata ->
[metadata: metadata]
|> NewRelic.Util.deep_flatten()
|> NewRelic.Util.coerce_attributes()
|> Map.new()
|> Map.delete("metadata.size")
end
end
def linking_metadata() do
AgentRun.entity_metadata()
end
def tracing_metadata() do
context = NewRelic.DistributedTrace.get_tracing_context() || %{}
%{
"trace.id": Map.get(context, :trace_id),
"span.id": Map.get(context, :guid)
}
end
end