Skip to content

Commit 4ff3cae

Browse files
Merge pull request #338 from getsentry/logger-metadata
Logger metadata
2 parents b9e8a4f + 619c31a commit 4ff3cae

File tree

6 files changed

+154
-12
lines changed

6 files changed

+154
-12
lines changed

.travis.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ elixir:
33
- 1.7
44
- 1.8
55
otp_release:
6-
- 20.2
7-
- 21.0
6+
- 20.3
7+
- 21.3
88
env:
99
- STRICT=true
1010
- STRICT=false
1111
matrix:
1212
exclude:
13-
- otp_release: 20.2
13+
- otp_release: 20.3
1414
env: STRICT=true
15-
- otp_release: 21.0
15+
- otp_release: 21.3
1616
env: STRICT=false
1717
notifications:
1818
email:

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## master
44

5+
* Enhancements
6+
* Option to include `Logger.metadata` in `Sentry.LoggerBackend` (#338)
7+
58
## 7.0.6 (2019-04-17)
69

710
* Enhancements

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ def start(_type, _opts) do
6868
end
6969
```
7070

71+
The backend can also be configured to capture Logger metadata, which is detailed [here](https://hexdocs.pm/sentry/Sentry.LoggerBackend.html).
72+
7173
### Capture Arbitrary Exceptions
7274

7375
Sometimes you want to capture specific exceptions. To do so, use `Sentry.capture_exception/2`.

lib/sentry/logger_backend.ex

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,67 @@
11
defmodule Sentry.LoggerBackend do
22
@moduledoc """
33
This module makes use of Elixir 1.7's new Logger metadata to report
4-
crashes processes. It replaces the previous `Sentry.Logger` sytem.
4+
crashed processes. It replaces the previous `Sentry.Logger` sytem.
5+
6+
To include the backend in your application, the backend can be added in your
7+
application file:
8+
9+
def start(_type, _opts) do
10+
children = [
11+
supervisor(MyApp.Repo, []),
12+
supervisor(MyAppWeb.Endpoint, [])
13+
]
14+
15+
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
16+
17+
{:ok, _} = Logger.add_backend(Sentry.LoggerBackend)
18+
19+
Supervisor.start_link(children, opts)
20+
end
21+
22+
If you are on OTP 21+ and would like to configure the backend to include metadata from
23+
`Logger.metadata/0` in reported events, it can be enabled:
24+
25+
{:ok, _} = Logger.add_backend(Sentry.LoggerBackend)
26+
Logger.configure_backend(Sentry.LoggerBackend, include_logger_metadata: true)
27+
28+
29+
It is important to be aware of whether this will include sensitive information
30+
in Sentry events before enabling it.
31+
32+
## Options
33+
34+
The supported options are:
35+
36+
* `:include_logger_metadata` - Enabling this option will read any key/value
37+
pairs with with binary, atom or number values from `Logger.metadata/0`
38+
and include that dictionary under the `:logger_metadata` key in an
39+
event's `:extra` metadata. This option defaults to `false`.
540
"""
641
@behaviour :gen_event
742

8-
defstruct level: nil
43+
defstruct level: nil, include_logger_metadata: false
944

1045
def init(__MODULE__) do
11-
config = Application.get_env(:logger, :sentry, [])
46+
config = Application.get_env(:logger, __MODULE__, [])
1247
{:ok, init(config, %__MODULE__{})}
1348
end
1449

1550
def init({__MODULE__, opts}) when is_list(opts) do
1651
config =
17-
Application.get_env(:logger, :sentry, [])
52+
Application.get_env(:logger, __MODULE__, [])
1853
|> Keyword.merge(opts)
1954

2055
{:ok, init(config, %__MODULE__{})}
2156
end
2257

23-
def handle_call({:configure, _options}, state) do
58+
def handle_call({:configure, options}, state) do
59+
config =
60+
Application.get_env(:logger, __MODULE__, [])
61+
|> Keyword.merge(options)
62+
63+
Application.put_env(:logger, __MODULE__, config)
64+
state = init(config, state)
2465
{:ok, :ok, state}
2566
end
2667

@@ -29,16 +70,30 @@ defmodule Sentry.LoggerBackend do
2970
end
3071

3172
def handle_event({_level, _gl, {Logger, _msg, _ts, meta}}, state) do
73+
%{include_logger_metadata: include_logger_metadata} = state
74+
75+
opts =
76+
if include_logger_metadata do
77+
[
78+
extra: %{
79+
logger_metadata: build_logger_metadata(meta)
80+
}
81+
]
82+
else
83+
[]
84+
end
85+
3286
case Keyword.get(meta, :crash_reason) do
3387
{reason, stacktrace} ->
3488
opts =
35-
Keyword.put([], :event_source, :logger)
89+
opts
90+
|> Keyword.put(:event_source, :logger)
3691
|> Keyword.put(:stacktrace, stacktrace)
3792

3893
Sentry.capture_exception(reason, opts)
3994

4095
reason when is_atom(reason) and not is_nil(reason) ->
41-
Sentry.capture_exception(reason, event_source: :logger)
96+
Sentry.capture_exception(reason, [{:event_source, :logger} | opts])
4297

4398
_ ->
4499
:ok
@@ -69,6 +124,15 @@ defmodule Sentry.LoggerBackend do
69124

70125
defp init(config, %__MODULE__{} = state) do
71126
level = Keyword.get(config, :level)
72-
%{state | level: level}
127+
include_logger_metadata = Keyword.get(config, :include_logger_metadata)
128+
%{state | level: level, include_logger_metadata: include_logger_metadata}
129+
end
130+
131+
defp build_logger_metadata(meta) do
132+
meta
133+
|> Enum.filter(fn {_key, value} ->
134+
is_binary(value) || is_atom(value) || is_number(value)
135+
end)
136+
|> Enum.into(%{})
73137
end
74138
end

test/logger_backend_test.exs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ defmodule Sentry.LoggerBackendTest do
77
{:ok, _} = Logger.add_backend(Sentry.LoggerBackend)
88

99
ExUnit.Callbacks.on_exit(fn ->
10+
Logger.configure_backend(Sentry.LoggerBackend, [])
1011
:ok = Logger.remove_backend(Sentry.LoggerBackend)
1112
end)
1213
end
@@ -147,4 +148,67 @@ defmodule Sentry.LoggerBackendTest do
147148
assert_receive "API called"
148149
end)
149150
end
151+
152+
if :erlang.system_info(:otp_release) >= '21' do
153+
test "includes Logger.metadata when enabled if the key and value are safely JSON-encodable" do
154+
Logger.configure_backend(Sentry.LoggerBackend, include_logger_metadata: true)
155+
bypass = Bypass.open()
156+
Process.flag(:trap_exit, true)
157+
pid = self()
158+
159+
Bypass.expect(bypass, fn conn ->
160+
{:ok, body, conn} = Plug.Conn.read_body(conn)
161+
json = Jason.decode!(body)
162+
assert get_in(json, ["extra", "logger_metadata", "string"]) == "string"
163+
assert get_in(json, ["extra", "logger_metadata", "atom"]) == "atom"
164+
assert get_in(json, ["extra", "logger_metadata", "number"]) == 43
165+
refute Map.has_key?(get_in(json, ["extra", "logger_metadata"]), "list")
166+
send(pid, "API called")
167+
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
168+
end)
169+
170+
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
171+
172+
capture_log(fn ->
173+
{:ok, pid} = Sentry.TestGenServer.start_link(pid)
174+
Sentry.TestGenServer.add_logger_metadata(pid, :string, "string")
175+
Sentry.TestGenServer.add_logger_metadata(pid, :atom, :atom)
176+
Sentry.TestGenServer.add_logger_metadata(pid, :number, 43)
177+
Sentry.TestGenServer.add_logger_metadata(pid, :list, [])
178+
Sentry.TestGenServer.invalid_function(pid)
179+
assert_receive "terminating"
180+
assert_receive "API called"
181+
end)
182+
end
183+
184+
test "does not include Logger.metadata when disabled" do
185+
Logger.configure_backend(Sentry.LoggerBackend, include_logger_metadata: false)
186+
bypass = Bypass.open()
187+
Process.flag(:trap_exit, true)
188+
pid = self()
189+
190+
Bypass.expect(bypass, fn conn ->
191+
{:ok, body, conn} = Plug.Conn.read_body(conn)
192+
json = Jason.decode!(body)
193+
refute get_in(json, ["extra", "logger_metadata", "string"]) == "string"
194+
refute get_in(json, ["extra", "logger_metadata", "atom"]) == "atom"
195+
refute get_in(json, ["extra", "logger_metadata", "number"]) == 43
196+
send(pid, "API called")
197+
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
198+
end)
199+
200+
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
201+
202+
capture_log(fn ->
203+
{:ok, pid} = Sentry.TestGenServer.start_link(pid)
204+
Sentry.TestGenServer.add_logger_metadata(pid, :string, "string")
205+
Sentry.TestGenServer.add_logger_metadata(pid, :atom, :atom)
206+
Sentry.TestGenServer.add_logger_metadata(pid, :number, 43)
207+
Sentry.TestGenServer.add_logger_metadata(pid, :list, [])
208+
Sentry.TestGenServer.invalid_function(pid)
209+
assert_receive "terminating"
210+
assert_receive "API called"
211+
end)
212+
end
213+
end
150214
end

test/support/test_gen_server.exs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ defmodule Sentry.TestGenServer do
1111
send(pid, :bad_exit)
1212
end
1313

14+
def add_logger_metadata(pid, key, value) do
15+
send(pid, {:logger_metadata, key, value})
16+
end
17+
1418
def invalid_function(pid) do
1519
send(pid, :invalid_function)
1620
end
@@ -32,6 +36,11 @@ defmodule Sentry.TestGenServer do
3236
{:stop, :bad_exit, state}
3337
end
3438

39+
def handle_info({:logger_metadata, key, value}, state) do
40+
Logger.metadata([{key, value}])
41+
{:noreply, state}
42+
end
43+
3544
def handle_info(:invalid_function, state) do
3645
cond do
3746
Version.match?(System.version(), ">= 1.5.0") ->

0 commit comments

Comments
 (0)