Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions lib/logger_json/formatters/datadog.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ defmodule LoggerJSON.Formatters.Datadog do
(like Kubernetes), you can set `hostname` to `:unset` to exclude it entirely. You'll then be relying on
[`dd-agent`](https://docs.datadoghq.com/agent/) to determine the hostname.

* `:reported_levels` (optional) - a list of log levels that should be reported as errors to Datadog.
Default: `[:emergency, :alert, :critical, :error]`.

For list of shared options see "Shared options" in `LoggerJSON`.

## Metadata
Expand Down Expand Up @@ -50,6 +53,8 @@ defmodule LoggerJSON.Formatters.Datadog do

@processed_metadata_keys ~w[pid file line mfa conn]a

@default_levels_reported_as_errors ~w[emergency alert critical error]a

@impl Formatter
def new(opts \\ []) do
{__MODULE__, config(opts)}
Expand All @@ -64,7 +69,15 @@ defmodule LoggerJSON.Formatters.Datadog do
hostname = Keyword.get(opts, :hostname, :system)
metadata_keys_or_selector = Keyword.get(opts, :metadata, [])
metadata_selector = update_metadata_selector(metadata_keys_or_selector, @processed_metadata_keys)
%{encoder_opts: encoder_opts, metadata: metadata_selector, redactors: redactors, hostname: hostname}
reported_levels = Keyword.get(opts, :reported_levels, @default_levels_reported_as_errors)

%{
encoder_opts: encoder_opts,
metadata: metadata_selector,
redactors: redactors,
hostname: hostname,
reported_levels: reported_levels
}
end

@impl Formatter
Expand All @@ -73,7 +86,8 @@ defmodule LoggerJSON.Formatters.Datadog do
encoder_opts: encoder_opts,
metadata: metadata_selector,
redactors: redactors,
hostname: hostname
hostname: hostname,
reported_levels: reported_levels
} = config(config_or_opts)

message =
Expand All @@ -94,6 +108,7 @@ defmodule LoggerJSON.Formatters.Datadog do
%{syslog: syslog(level, meta, hostname)}
|> maybe_put(:logger, format_logger(meta))
|> maybe_merge(format_http_request(meta))
|> maybe_merge(format_error(message, metadata, level, reported_levels))
|> maybe_merge(encode(metadata, redactors))
|> maybe_merge(encode(message, redactors))
|> @encoder.encode_to_iodata!(encoder_opts)
Expand Down Expand Up @@ -244,6 +259,29 @@ defmodule LoggerJSON.Formatters.Datadog do

defp format_http_request(_meta), do: nil

defp format_error(%{error: _error}, _metadata, _level, _reported_levels), do: nil

defp format_error(%{message: message}, metadata, level, reported_levels) when is_binary(message) do
if level in reported_levels do
error =
metadata[:error]
|> Kernel.||(%{})
|> Map.put(:kind, get_error_kind(metadata))
|> Map.put(:message, message)
|> maybe_put(:stack, get_error_stack(metadata))

%{error: error}
end
end

defp format_error(_msg, _metadata, _level, _reported_levels), do: nil

defp get_error_kind(%{error: %{kind: kind}}) when is_binary(kind), do: kind
defp get_error_kind(_metadata), do: "error"

defp get_error_stack(%{error: %{stack: stack}}) when is_binary(stack), do: stack
defp get_error_stack(_metadata), do: nil

if Code.ensure_loaded?(Plug.Conn) do
defp build_http_request_data(%Plug.Conn{} = conn, request_id) do
request_url = Plug.Conn.request_url(conn)
Expand Down
30 changes: 30 additions & 0 deletions test/logger_json/formatters/datadog_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,36 @@ defmodule LoggerJSON.Formatters.DatadogTest do
} = log_entry
end

test "logs error.{kind, message, stack} for error+ logs" do
for level <- [:error, :critical, :alert, :emergency] do
log =
capture_log(fn ->
Logger.log(level, "Something went wrong")
end)
|> decode_or_print_error()

assert log["error"]["kind"] == "error"
assert log["error"]["message"] == "Something went wrong"
end
end

test "logs error.* fields from logger metadata" do
for level <- [:error, :critical, :alert, :emergency] do
log =
capture_log(level, fn ->
Logger.log(level, "Something went wrong",
error: %{kind: "CustomError", module: "PaymentGateway", stack: "stacktrace"}
)
end)
|> decode_or_print_error()

assert log["error"]["kind"] == "CustomError"
assert log["error"]["message"] == "Something went wrong"
assert log["error"]["module"] == "PaymentGateway"
assert log["error"]["stack"] == "stacktrace"
end
end

if @encoder == Jason do
test "passing options to encoder" do
formatter = Datadog.new(encoder_opts: [pretty: true])
Expand Down