forked from sneako/finch
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add GoogleErrorReporter to format errors compatible with Google Error…
… Reporting (sneako#58) * Add GoogleErrorReporter to format errors compatible with Google Error Reporting * :undef error contains an extra line of context * mix format * Add optional metadata * Automatically format errors for google when google_error_reporter config is present * Revert "Automatically format errors for google when google_error_reporter config is present" This reverts commit 472e16ca0f755e7fa3cc75b8617e0b74289e285b. * Add google_error_reporter config * Handle case where stack trace includes arg data * Reformat Elixir's stacktrace format Use regular expressions to reformat the lines, instead of building the stacktraces manually. This ensures that the stacktrace includes all of the original information. The previous approach could throw away lines it didn't understand. * Format the message name Google skips over this with Elixir's default format * Specify the reason This allows for pass-through from try/catch * Revert "Format the message name" This reverts commit 99ffdce64d81528ab15815cc7158f6fae92fe19a. * Add a Context section after the stacktrace Google Error Reporter names the error message after last non-file line in the message appearing before the first file line. This leads to errors being named "Foo.Bar(123, 456)" rather than "(UndefinedFunctionError) function Foo.bar/2 is undefined" Putting the context at the end keeps the original error message name, and still includes the contextual information.
- Loading branch information
Showing
2 changed files
with
153 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
defmodule LoggerJSON.Formatters.GoogleErrorReporter do | ||
require Logger | ||
@googleErrorType "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent" | ||
|
||
def report(kind, reason, stacktrace, metadata \\ []) do | ||
[format_banner(kind, reason, stacktrace) | format_stacktrace(stacktrace)] | ||
|> Enum.join("\n") | ||
|> Logger.error(Keyword.merge(build_metadata(), metadata)) | ||
end | ||
|
||
defp format_banner(kind, reason, stacktrace) do | ||
Exception.format_banner(kind, reason, stacktrace) | ||
end | ||
|
||
defp format_stacktrace(stacktrace) do | ||
lines = | ||
Exception.format_stacktrace(stacktrace) | ||
|> String.trim_trailing() | ||
|> String.split("\n") | ||
|> Enum.map(&format_line/1) | ||
|> Enum.group_by(fn {kind, _line} -> kind end) | ||
|
||
format_lines(:trace, lines[:trace]) ++ format_lines(:context, lines[:context]) ++ [""] | ||
end | ||
|
||
defp format_line(line) do | ||
case Regex.run(~r/(.+)\:(\d+)\: (.*)/, line) do | ||
[_, file, line, function] -> {:trace, "#{file}:#{line}:in `#{function}'"} | ||
_ -> {:context, line} | ||
end | ||
end | ||
|
||
defp format_lines(_kind, nil) do | ||
[] | ||
end | ||
|
||
defp format_lines(:trace, lines) do | ||
Enum.map(lines, fn {:trace, line} -> line end) | ||
end | ||
|
||
defp format_lines(:context, lines) do | ||
["Context:" | Enum.map(lines, fn {:context, line} -> line end)] | ||
end | ||
|
||
defp build_metadata() do | ||
["@type": @googleErrorType] | ||
|> with_service_context() | ||
end | ||
|
||
defp with_service_context(metadata) do | ||
if service_context = config()[:service_context] do | ||
Keyword.merge(metadata, serviceContext: service_context) | ||
else | ||
metadata | ||
end | ||
end | ||
|
||
defp config do | ||
Application.get_env(:logger_json, :google_error_reporter, []) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
defmodule LoggerJSONGoogleErrorReporterTest do | ||
use Logger.Case, async: false | ||
alias LoggerJSON.Formatters.GoogleCloudLogger | ||
alias LoggerJSON.Formatters.GoogleErrorReporter | ||
|
||
setup do | ||
:ok = | ||
Logger.configure_backend( | ||
LoggerJSON, | ||
device: :user, | ||
level: nil, | ||
metadata: :all, | ||
json_encoder: Jason, | ||
on_init: :disabled, | ||
formatter: GoogleCloudLogger | ||
) | ||
|
||
:ok = Logger.reset_metadata([]) | ||
end | ||
|
||
test "metadata" do | ||
log = | ||
capture_log(fn -> GoogleErrorReporter.report(:error, %RuntimeError{message: "oops"}, []) end) | ||
|> Jason.decode!() | ||
|
||
assert log["severity"] == "ERROR" | ||
assert log["@type"] == "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent" | ||
end | ||
|
||
test "google_error_reporter metadata" do | ||
:ok = Application.put_env(:logger_json, :google_error_reporter, service_context: [service: "myapp", version: "abc123"]) | ||
log = | ||
capture_log(fn -> GoogleErrorReporter.report(:error, %RuntimeError{message: "oops"}, []) end) | ||
|> Jason.decode!() | ||
|
||
assert log["serviceContext"]["service"] == "myapp" | ||
assert log["serviceContext"]["version"] == "abc123" | ||
after | ||
Application.delete_env(:logger_json, :google_error_reporter) | ||
end | ||
|
||
test "optional metadata" do | ||
log = | ||
capture_log(fn -> GoogleErrorReporter.report(:error, %RuntimeError{message: "oops"}, [], foo: "bar") end) | ||
|> Jason.decode!() | ||
|
||
assert log["foo"] == "bar" | ||
end | ||
|
||
test "logs elixir error" do | ||
error = %RuntimeError{message: "oops"} | ||
|
||
stacktrace = [ | ||
{Foo, :bar, 0, [file: 'foo/bar.ex', line: 123]}, | ||
{Foo.Bar, :baz, 1, [file: 'foo/bar/baz.ex', line: 456]} | ||
] | ||
|
||
log = | ||
capture_log(fn -> GoogleErrorReporter.report(:error, error, stacktrace) end) | ||
|> Jason.decode!() | ||
|
||
assert log["message"] == | ||
""" | ||
** (RuntimeError) oops | ||
foo/bar.ex:123:in `Foo.bar/0' | ||
foo/bar/baz.ex:456:in `Foo.Bar.baz/1' | ||
""" | ||
end | ||
|
||
test "logs erlang error" do | ||
error = :undef | ||
|
||
stacktrace = [ | ||
{Foo, :bar, [123, 456], []}, | ||
{Foo, :bar, 2, [file: 'foo/bar.ex', line: 123]}, | ||
{Foo.Bar, :baz, 1, [file: 'foo/bar/baz.ex', line: 456]} | ||
] | ||
|
||
log = | ||
capture_log(fn -> GoogleErrorReporter.report(:error, error, stacktrace) end) | ||
|> Jason.decode!() | ||
|
||
assert log["message"] == | ||
""" | ||
** (UndefinedFunctionError) function Foo.bar/2 is undefined (module Foo is not available) | ||
foo/bar.ex:123:in `Foo.bar/2' | ||
foo/bar/baz.ex:456:in `Foo.Bar.baz/1' | ||
Context: | ||
Foo.bar(123, 456) | ||
""" | ||
end | ||
end |