-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathappsignal_plug.ex
138 lines (109 loc) · 3.73 KB
/
appsignal_plug.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
defmodule Appsignal.Plug do
@span Application.compile_env(:appsignal, :appsignal_span, Appsignal.Span)
@moduledoc """
AppSignal's Plug instrumentation instruments calls to Plug applications to
gain performance insights and error reporting.
## Installation
To install `Appsignal.Plug` into your Plug application, `use Appsignal.Plug`
in your application's router module:
defmodule AppsignalPlugExample do
use Plug.Router
use Appsignal.Plug
plug(:match)
plug(:dispatch)
get "/" do
send_resp(conn, 200, "Welcome")
end
end
"""
@doc false
defmacro __using__(_) do
quote do
require Logger
Appsignal.IntegrationLogger.debug("AppSignal.Plug attached to #{__MODULE__}")
@tracer Application.compile_env(:appsignal, :appsignal_tracer, Appsignal.Tracer)
@span Application.compile_env(:appsignal, :appsignal_span, Appsignal.Span)
use Plug.ErrorHandler
def call(%Plug.Conn{private: %{appsignal_plug_instrumented: true}} = conn, opts) do
Logger.warning(
"Appsignal.Plug was included twice, disabling Appsignal.Plug. Please only `use Appsignal.Plug` once."
)
super(conn, opts)
end
def call(conn, opts) do
span = @tracer.create_span("http_request", @tracer.current_span)
try do
super(conn, opts)
catch
kind, reason ->
stack = __STACKTRACE__
_ =
span
|> Appsignal.Plug.handle_error(kind, reason, stack, conn)
|> @tracer.close_span()
@tracer.ignore()
:erlang.raise(kind, reason, stack)
else
conn ->
@tracer.close_span(span)
_ = Appsignal.Plug.set_conn_data(span, conn)
Plug.Conn.put_private(conn, :appsignal_plug_instrumented, true)
end
end
defoverridable call: 2
end
end
@doc """
Adds an `:appsignal_name` to the `Plug.Conn`, to overwrite the root
`Appsignal.Span`'s name.
## Examples
iex> Appsignal.Plug.put_name(%Plug.Conn{}, "AppsignalPlugExample#index")
%Plug.Conn{private: %{appsignal_name: "AppsignalPlugExample#index"}}
In a Plug app, call `Appsignal.Plug.put_name/2` on the returned `Plug.Conn`
struct:
defmodule AppsignalPlugExample do
use Plug.Router
use Appsignal.Plug
plug(:match)
plug(:dispatch)
get "/" do
conn
|> Appsignal.Plug.put_name("AppsignalPlugExample#index")
|> send_resp(200, "Welcome")
end
end
"""
def put_name(%Plug.Conn{} = conn, name) do
Plug.Conn.put_private(conn, :appsignal_name, name)
end
@doc false
def set_conn_data(span, conn) do
span
|> @span.set_name(Appsignal.Metadata.name(conn))
|> @span.set_attribute("appsignal:category", Appsignal.Metadata.category(conn))
|> @span.set_sample_data_if_nil("params", Appsignal.Metadata.params(conn))
|> @span.set_sample_data_if_nil("environment", Appsignal.Metadata.metadata(conn))
|> @span.set_sample_data_if_nil("session_data", Appsignal.Metadata.session(conn))
end
@doc false
def handle_error(
span,
:error,
%Plug.Conn.WrapperError{conn: conn, reason: wrapped_reason, stack: stack},
_stack,
_conn
) do
handle_error(span, :error, wrapped_reason, stack, conn)
end
@doc false
def handle_error(span, _kind, %{plug_status: status}, _stack, _conn) when status < 500 do
span
end
@doc false
def handle_error(span, kind, reason, stack, conn) do
conn_with_status = Plug.Conn.put_status(conn, Plug.Exception.status(reason))
span
|> @span.add_error(kind, reason, stack)
|> set_conn_data(conn_with_status)
end
end