changed CHANGELOG.md
 
@@ -1,6 1,13 @@
1
1
## CHANGELOG
2
2
3
3
4
### `v1.13.0`
5
6
Features
7
8
* `OTP 21 ` has been required for a while, and now the agent will enforce this version requirement [#151](https://github.com/newrelic/elixir_agent/pull/151)
9
* Truncate super large nested attribute structure [#153](https://github.com/newrelic/elixir_agent/pull/153)
10
4
11
### `v1.12.0`
5
12
6
13
Features
changed README.md
 
@@ -19,6 19,10 @@ We'd love to get your contributions to improve the elixir agent! Keep in mind wh
19
19
20
20
Install the [Hex package](https://hex.pm/packages/new_relic_agent)
21
21
22
Requirements:
23
* Erlang/OTP 21
24
* Elixir 1.8
25
22
26
```elixir
23
27
defp deps do
24
28
[
 
@@ -72,9 76,10 @@ defmodule MyPlug do
72
76
end
73
77
```
74
78
75
- #### Function Tracing
79
#### Tracing
80
81
* `NewRelic.Tracer` enables detailed Function tracing. Annotate a function and it'll show up as a span in Transaction Traces / Distributed Traces, and we'll collect aggregate stats about it. Install it by adding `use NewRelic.Tracer` to any module, and annotating any function with `@trace` module attribute
76
82
77
- * `NewRelic.Tracer` enables detailed Function Tracing. Annotate a function and it'll show up as a span in Transaction Traces / Distributed Traces, and we'll collect aggregate stats about it. Install it by adding `use NewRelic.Tracer` to any module, and annotating any function with `@trace` module attribute
78
83
79
84
```elixir
80
85
defmodule MyModule do
 
@@ -87,6 92,21 @@ defmodule MyModule do
87
92
end
88
93
```
89
94
95
* `NewRelic.Tracer` also enables detailed External request tracing. A little more instrumentation is required to pass the trace context forward with Distributed Tracing.
96
97
```elixir
98
defmodule MyExternalService do
99
use NewRelic.Tracer
100
101
@trace {:request, category: :external}
102
def request(method, url, headers) do
103
NewRelic.set_span(:http, url: url, method: method, component: "HttpClient")
104
headers NewRelic.create_distributed_trace_payload(:http)
105
HttpClient.request(method, url, headers)
106
end
107
end
108
```
109
90
110
#### `Mix.Task`
91
111
92
112
To enable the agent during a `Mix.Task`, you simply need to start and stop it.
changed VERSION
 
@@ -1 1 @@
1
- 1.12.0
1
1.13.0
changed hex_metadata.config
 
@@ -1,7 1,7 @@
1
1
{<<"app">>,<<"new_relic_agent">>}.
2
2
{<<"build_tools">>,[<<"mix">>]}.
3
3
{<<"description">>,<<"New Relic's Open-Source Elixir Agent">>}.
4
- {<<"elixir">>,<<"~> 1.7">>}.
4
{<<"elixir">>,<<"~> 1.8">>}.
5
5
{<<"files">>,
6
6
[<<"lib">>,<<"lib/new_relic">>,<<"lib/new_relic/transaction.ex">>,
7
7
<<"lib/new_relic/tracer">>,<<"lib/new_relic/tracer/macro.ex">>,
 
@@ -32,7 32,8 @@
32
32
<<"lib/new_relic/aggregate/supervisor.ex">>,
33
33
<<"lib/new_relic/aggregate/reporter.ex">>,
34
34
<<"lib/new_relic/aggregate/aggregate.ex">>,<<"lib/new_relic/config.ex">>,
35
- <<"lib/new_relic/distributed_trace.ex">>,<<"lib/new_relic/harvest">>,
35
<<"lib/new_relic/distributed_trace.ex">>,
36
<<"lib/new_relic/always_on_supervisor.ex">>,<<"lib/new_relic/harvest">>,
36
37
<<"lib/new_relic/harvest/supervisor.ex">>,
37
38
<<"lib/new_relic/harvest/collector">>,
38
39
<<"lib/new_relic/harvest/collector/supervisor.ex">>,
 
@@ -72,9 73,10 @@
72
73
<<"lib/new_relic/error/reporter.ex">>,
73
74
<<"lib/new_relic/error/logger_handler.ex">>,
74
75
<<"lib/new_relic/error/event.ex">>,<<"lib/new_relic/error/trace.ex">>,
75
- <<"lib/new_relic/error/error_logger_handler.ex">>,<<"lib/new_relic.ex">>,
76
- <<"priv">>,<<"priv/cacert.pem">>,<<"mix.exs">>,<<"README.md">>,
77
- <<"CHANGELOG.md">>,<<"VERSION">>]}.
76
<<"lib/new_relic/error/error_logger_handler.ex">>,
77
<<"lib/new_relic/enabled_supervisor.ex">>,<<"lib/new_relic.ex">>,<<"priv">>,
78
<<"priv/cacert.pem">>,<<"mix.exs">>,<<"README.md">>,<<"CHANGELOG.md">>,
79
<<"VERSION">>]}.
78
80
{<<"licenses">>,[<<"Apache 2.0">>]}.
79
81
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/newrelic/elixir_agent">>}]}.
80
82
{<<"name">>,<<"new_relic_agent">>}.
 
@@ -94,4 96,4 @@
94
96
{<<"optional">>,false},
95
97
{<<"repository">>,<<"hexpm">>},
96
98
{<<"requirement">>,<<"~> 1.1">>}]]}.
97
- {<<"version">>,<<"1.12.0">>}.
99
{<<"version">>,<<"1.13.0">>}.
changed lib/new_relic.ex
 
@@ -9,6 9,9 @@ defmodule NewRelic do
9
9
The first segment will be treated as the Transaction namespace,
10
10
and commonly contains the name of the framework.
11
11
12
**Notes:**
13
* At least 2 segments are required to light up the Transactions UI in APM
14
12
15
In the following example, you will see `/custom/transaction/name`
13
16
in the Transaction list.
14
17
 
@@ -19,11 22,30 @@ defmodule NewRelic do
19
22
defdelegate set_transaction_name(name), to: NewRelic.Transaction.Reporter
20
23
21
24
@doc """
22
- Report a custom attribute on the current transaction
25
Report custom attributes on the current Transaction
26
27
Reporting nested data structures is supported by auto-flattening them
28
into a list of key-value pairs.
23
29
24
30
```elixir
25
31
NewRelic.add_attributes(foo: "bar")
32
# "foo" => "bar"
33
34
NewRelic.add_attributes(map: %{foo: "bar", baz: "qux"})
35
# "map.foo" => "bar"
36
# "map.baz" => "qux"
37
# "map.size" => 2
38
39
NewRelic.add_attributes(list: ["a", "b", "c"])
40
# "list.0" => "a"
41
# "list.1" => "b"
42
# "list.2" => "c"
43
# "list.length" => 3
26
44
```
45
46
**Notes:**
47
* Nested Lists and Maps are truncated at 10 items since there are a limited number
48
of attributes that can be reported on Transaction events
27
49
"""
28
50
defdelegate add_attributes(custom_attributes), to: NewRelic.Transaction.Reporter
added lib/new_relic/always_on_supervisor.ex
 
@@ -0,0 1,20 @@
1
defmodule NewRelic.AlwaysOnSupervisor do
2
use Supervisor
3
4
@moduledoc false
5
6
def start_link do
7
Supervisor.start_link(__MODULE__, [])
8
end
9
10
def init(_) do
11
children = [
12
worker(NewRelic.Harvest.Collector.AgentRun, []),
13
worker(NewRelic.Harvest.Collector.HarvesterStore, []),
14
supervisor(NewRelic.DistributedTrace.Supervisor, []),
15
supervisor(NewRelic.Transaction.Supervisor, [])
16
]
17
18
supervise(children, strategy: :one_for_one)
19
end
20
end
changed lib/new_relic/application.ex
 
@@ -10,12 10,8 @@ defmodule NewRelic.Application do
10
10
11
11
children = [
12
12
worker(NewRelic.Logger, []),
13
- supervisor(NewRelic.Harvest.Supervisor, []),
14
- supervisor(NewRelic.Sampler.Supervisor, []),
15
- supervisor(NewRelic.Error.Supervisor, []),
16
- supervisor(NewRelic.Transaction.Supervisor, []),
17
- supervisor(NewRelic.DistributedTrace.Supervisor, []),
18
- supervisor(NewRelic.Aggregate.Supervisor, []),
13
supervisor(NewRelic.AlwaysOnSupervisor, []),
14
supervisor(NewRelic.EnabledSupervisor, [[enabled: NewRelic.Config.enabled?()]]),
19
15
worker(NewRelic.GracefulShutdown, [], shutdown: 30_000)
20
16
]
added lib/new_relic/enabled_supervisor.ex
 
@@ -0,0 1,27 @@
1
defmodule NewRelic.EnabledSupervisor do
2
use Supervisor
3
4
# This Supervisor starts processes that we
5
# only start if the agent is enabled
6
7
@moduledoc false
8
9
def start_link(enabled: enabled) do
10
Supervisor.start_link(__MODULE__, enabled: enabled)
11
end
12
13
def init(enabled: false) do
14
:ignore
15
end
16
17
def init(enabled: true) do
18
children = [
19
supervisor(NewRelic.Harvest.Supervisor, []),
20
supervisor(NewRelic.Sampler.Supervisor, []),
21
supervisor(NewRelic.Error.Supervisor, []),
22
supervisor(NewRelic.Aggregate.Supervisor, [])
23
]
24
25
supervise(children, strategy: :one_for_one)
26
end
27
end
changed lib/new_relic/harvest/collector/agent_run.ex
 
@@ -38,9 38,6 @@ defmodule NewRelic.Harvest.Collector.AgentRun do
38
38
39
39
{:error, _reason} ->
40
40
{:noreply, %{status: :error_during_preconnect}}
41
-
42
- {:failed_connect, _reason} ->
43
- {:noreply, %{status: :failed_to_preconnect}}
44
41
end
45
42
end
changed lib/new_relic/harvest/collector/metric_data.ex
 
@@ -136,6 136,38 @@ defmodule NewRelic.Harvest.Collector.MetricData do
136
136
}
137
137
]
138
138
139
def transform({:external, url, component, method}, duration_s: duration_s) do
140
host = URI.parse(url).host
141
method = method |> to_string() |> String.upcase()
142
143
[
144
%Metric{
145
name: :"External/all",
146
call_count: 1,
147
total_call_time: duration_s,
148
total_exclusive_time: duration_s,
149
min_call_time: duration_s,
150
max_call_time: duration_s
151
},
152
%Metric{
153
name: join(["External", host, "all"]),
154
call_count: 1,
155
total_call_time: duration_s,
156
total_exclusive_time: duration_s,
157
min_call_time: duration_s,
158
max_call_time: duration_s
159
},
160
%Metric{
161
name: join(["External", host, component, method]),
162
call_count: 1,
163
total_call_time: duration_s,
164
total_exclusive_time: duration_s,
165
min_call_time: duration_s,
166
max_call_time: duration_s
167
}
168
]
169
end
170
139
171
def transform({:external, name}, duration_s: duration_s),
140
172
do: [
141
173
%Metric{
changed lib/new_relic/harvest/collector/protocol.ex
 
@@ -79,7 79,8 @@ defmodule NewRelic.Harvest.Collector.Protocol do
79
79
{:error, reason}
80
80
end
81
81
82
- defp parse_collector_response({:error, code}), do: code
82
defp parse_collector_response({:error, code}) when is_integer(code), do: code
83
defp parse_collector_response({:error, reason}), do: {:error, reason}
83
84
defp parse_collector_response({:ok, %{"return_value" => return_value}}), do: return_value
84
85
85
86
defp parse_collector_response({:ok, %{"exception" => exception_value}}),
changed lib/new_relic/harvest/collector/supervisor.ex
 
@@ -11,7 11,6 @@ defmodule NewRelic.Harvest.Collector.Supervisor do
11
11
12
12
def init(_) do
13
13
children = [
14
- worker(Collector.AgentRun, []),
15
14
data_supervisor(Collector.Metric, :data_report_period),
16
15
data_supervisor(Collector.TransactionTrace, :data_report_period),
17
16
data_supervisor(Collector.ErrorTrace, :data_report_period),
changed lib/new_relic/harvest/supervisor.ex
 
@@ -20,7 20,6 @@ defmodule NewRelic.Harvest.Supervisor do
20
20
21
21
def init(_) do
22
22
children = [
23
- worker(Collector.HarvesterStore, []),
24
23
supervisor(Task.Supervisor, [[name: Collector.TaskSupervisor]]),
25
24
supervisor(Collector.Supervisor, [])
26
25
]
changed lib/new_relic/init.ex
 
@@ -2,9 2,19 @@ defmodule NewRelic.Init do
2
2
@moduledoc false
3
3
4
4
def run() do
5
verify_erlang_otp_version()
5
6
init_collector_host()
6
7
end
7
8
9
@erlang_version_requirement ">= 21.0.0"
10
def verify_erlang_otp_version() do
11
if Version.match?(System.otp_release() <> ".0.0", @erlang_version_requirement) do
12
:ok
13
else
14
raise "Erlang/OTP 21 required"
15
end
16
end
17
8
18
def init_collector_host() do
9
19
Application.put_env(:new_relic_agent, :collector_host, determine_collector_host())
10
20
end
changed lib/new_relic/tracer.ex
 
@@ -29,15 29,19 @@ defmodule NewRelic.Tracer do
29
29
30
30
#### Categories
31
31
32
- To categorize External Service calls, use this syntax:
32
To categorize External Service calls you must give the trace annotation a category.
33
34
You may also call `NewRelic.set_span` to provide better naming for metrics & spans, and additonally annotate the outgoing HTTP headers with the Distributed Tracing context to track calls across services.
33
35
34
36
```elixir
35
37
defmodule MyExternalService do
36
38
use NewRelic.Tracer
37
39
38
- @trace {:query, category: :external}
39
- def query(args) do
40
- # Make the call
40
@trace {:request, category: :external}
41
def request(method, url, headers) do
42
NewRelic.set_span(:http, url: url, method: method, component: "HttpClient")
43
headers NewRelic.create_distributed_trace_payload(:http)
44
HttpClient.request(method, url, headers)
41
45
end
42
46
end
43
47
```
 
@@ -47,14 51,14 @@ defmodule NewRelic.Tracer do
47
51
* Add custom attributes to Transaction events:
48
52
- `external_call_count`
49
53
- `external_duration_ms`
50
- - `external.MyExternalService.query/0.call_count`
51
- - `external.MyExternalService.query/0.duration_ms`
54
- `external.MyExternalService.query.call_count`
55
- `external.MyExternalService.query.duration_ms`
52
56
53
57
Transactions that call the traced `ExternalService` functions will contain `external_call_count` attribute
54
58
55
59
```elixir
56
60
get "/endpoint" do
57
- ExternalService.query(2)
61
ExternalService.request(:get, url, headers)
58
62
send_resp(conn, 200, "ok")
59
63
end
60
64
```
changed lib/new_relic/tracer/report.ex
 
@@ -78,6 78,10 @@ defmodule NewRelic.Tracer.Report do
78
78
duration_s = duration_ms / 1000
79
79
arity = length(arguments)
80
80
args = inspect_args(arguments)
81
span_attrs = NewRelic.DistributedTrace.get_span_attrs()
82
83
function_name = function_name({module, function}, name)
84
function_arity_name = function_name({module, function, arity}, name)
81
85
82
86
Transaction.Reporter.add_trace_segment(%{
83
87
module: module,
 
@@ -96,23 100,23 @@ defmodule NewRelic.Tracer.Report do
96
100
NewRelic.report_span(
97
101
timestamp_ms: System.convert_time_unit(start_time, :native, :millisecond),
98
102
duration_s: duration_s,
99
- name: function_name({module, function, arity}, name),
103
name: function_arity_name,
100
104
edge: [span: id, parent: parent_id],
101
105
category: "http",
102
- attributes: Map.put(NewRelic.DistributedTrace.get_span_attrs(), :args, args)
106
attributes: Map.put(span_attrs, :args, args)
103
107
)
104
108
105
109
NewRelic.incr_attributes(
106
110
external_call_count: 1,
107
111
external_duration_ms: duration_ms,
108
- "external.#{function_name({module, function}, name)}.call_count": 1,
109
- "external.#{function_name({module, function}, name)}.duration_ms": duration_ms
112
"external.#{function_name}.call_count": 1,
113
"external.#{function_name}.duration_ms": duration_ms
110
114
)
111
115
112
116
NewRelic.report_aggregate(
113
117
%{
114
118
name: :FunctionTrace,
115
- mfa: function_name({module, function, arity}, name),
119
mfa: function_arity_name,
116
120
metric_category: :external
117
121
},
118
122
%{duration_ms: duration_ms, call_count: 1}
 
@@ -120,10 124,13 @@ defmodule NewRelic.Tracer.Report do
120
124
121
125
Transaction.Reporter.track_metric({:external, duration_s})
122
126
123
- NewRelic.report_metric(
124
- {:external, function_name({module, function}, name)},
125
- duration_s: duration_s
126
- )
127
case span_attrs do
128
%{url: url, component: component, method: method} ->
129
NewRelic.report_metric({:external, url, component, method}, duration_s: duration_s)
130
131
_ ->
132
NewRelic.report_metric({:external, function_name}, duration_s: duration_s)
133
end
127
134
end
128
135
129
136
def call(
 
@@ -168,11 175,11 @@ defmodule NewRelic.Tracer.Report do
168
175
)
169
176
end
170
177
171
- def inspect_args(arguments) do
178
defp inspect_args(arguments) do
172
179
inspect(arguments, charlists: :as_lists, limit: 20, printable_limit: 100)
173
180
end
174
181
175
- def duration_ms(start_time_mono, end_time_mono),
182
defp duration_ms(start_time_mono, end_time_mono),
176
183
do: System.convert_time_unit(end_time_mono - start_time_mono, :native, :millisecond)
177
184
178
185
defp function_name({m, f}, f), do: "#{inspect(m)}.#{f}"
changed lib/new_relic/util.ex
 
@@ -33,13 33,19 @@ defmodule NewRelic.Util do
33
33
Enum.flat_map(attrs, &deep_flatten/1)
34
34
end
35
35
36
- def deep_flatten({key, value}) when is_list(value) do
37
- Enum.with_index(value)
36
def deep_flatten({key, list_value}) when is_list(list_value) do
37
list_value
38
|> Enum.slice(0..9)
39
|> Enum.with_index()
38
40
|> Enum.flat_map(fn {v, index} -> deep_flatten({"#{key}.#{index}", v}) end)
41
|> Enum.concat([{"#{key}.length", length(list_value)}])
39
42
end
40
43
41
- def deep_flatten({key, value}) when is_map(value) do
42
- Enum.flat_map(value, fn {k, v} -> deep_flatten({"#{key}.#{k}", v}) end)
44
def deep_flatten({key, map_value}) when is_map(map_value) do
45
map_value
46
|> Enum.slice(0..9)
47
|> Enum.flat_map(fn {k, v} -> deep_flatten({"#{key}.#{k}", v}) end)
48
|> Enum.concat([{"#{key}.size", map_size(map_value)}])
43
49
end
44
50
45
51
def deep_flatten({key, value}) do
changed lib/new_relic/util/http.ex
 
@@ -9,7 9,7 @@ defmodule NewRelic.Util.HTTP do
9
9
%{host: host} = URI.parse(url)
10
10
11
11
with {:ok, {{_, status_code, _}, _headers, body}} <-
12
- :httpc.request(:post, request, ssl_options(host), []) do
12
:httpc.request(:post, request, http_options(host), []) do
13
13
{:ok, %{status_code: status_code, body: to_string(body)}}
14
14
end
15
15
end
 
@@ -21,8 21,9 @@ defmodule NewRelic.Util.HTTP do
21
21
Certs are pulled from Mozilla exactly as Hex does:
22
22
https://github.com/hexpm/hex/blob/master/README.md#bundled-ca-certs
23
23
"""
24
- def ssl_options(host) do
24
def http_options(host) do
25
25
[
26
connect_timeout: 1000,
26
27
ssl: [
27
28
verify: :verify_peer,
28
29
cacertfile: Application.app_dir(:new_relic_agent, "priv/cacert.pem"),
changed mix.exs
 
@@ -6,7 6,7 @@ defmodule NewRelic.Mixfile do
6
6
app: :new_relic_agent,
7
7
description: "New Relic's Open-Source Elixir Agent",
8
8
version: agent_version(),
9
- elixir: "~> 1.7",
9
elixir: "~> 1.8",
10
10
build_embedded: Mix.env() == :prod,
11
11
start_permanent: Mix.env() == :prod,
12
12
name: "New Relic Elixir Agent",