changed CHANGELOG.md
 
@@ -13,6 13,8 @@ Tweaks
13
13
* Make `plug_cowboy` an optional dep. [#201](https://github.com/newrelic/elixir_agent/pull/201)
14
14
* Use Erlang/OTP 21 built-in SSL hostname verification. [#197](https://github.com/newrelic/elixir_agent/pull/197)
15
15
* Reduce contention on transaction storage. [#209](https://github.com/newrelic/elixir_agent/pull/209)
16
* Detect Ecto repo via telemetry event instead of an Erlang tracer. [#214](https://github.com/newrelic/elixir_agent/pull/214)
17
* Fix for Distributed Traces that start from a Browser Agent. [#215](https://github.com/newrelic/elixir_agent/pull/215)
16
18
17
19
------
changed VERSION
 
@@ -1 1 @@
1
- 1.18.0-rc.2
1
1.18.0-rc.4
changed hex_metadata.config
 
@@ -115,5 115,10 @@
115
115
{<<"name">>,<<"ecto_sql">>},
116
116
{<<"optional">>,true},
117
117
{<<"repository">>,<<"hexpm">>},
118
- {<<"requirement">>,<<"~> 3.3">>}]]}.
119
- {<<"version">>,<<"1.18.0-rc.2">>}.
118
{<<"requirement">>,<<">= 3.4.0">>}],
119
[{<<"app">>,<<"ecto">>},
120
{<<"name">>,<<"ecto">>},
121
{<<"optional">>,true},
122
{<<"repository">>,<<"hexpm">>},
123
{<<"requirement">>,<<">= 3.4.1">>}]]}.
124
{<<"version">>,<<"1.18.0-rc.4">>}.
changed lib/new_relic/distributed_trace.ex
 
@@ -85,7 85,7 @@ defmodule NewRelic.DistributedTrace do
85
85
86
86
def maybe_generate_sampling(context), do: context
87
87
88
- def maybe_generate_trace_id(%Context{parent_id: nil} = context) do
88
def maybe_generate_trace_id(%Context{trace_id: nil} = context) do
89
89
%{context | trace_id: generate_guid(16)}
90
90
end
91
91
 
@@ -112,7 112,10 @@ defmodule NewRelic.DistributedTrace do
112
112
end
113
113
114
114
def report_attributes(
115
- %Context{parent_id: nil} = context,
115
%Context{
116
parent_id: nil,
117
span_guid: nil
118
} = context,
116
119
transport_type: _type
117
120
) do
118
121
[
 
@@ -158,20 161,6 @@ defmodule NewRelic.DistributedTrace do
158
161
context
159
162
end
160
163
161
- def convert_to_outbound(%Context{parent_id: nil} = context) do
162
- %Context{
163
- source: context.source,
164
- account_id: AgentRun.account_id(),
165
- app_id: AgentRun.primary_application_id(),
166
- parent_id: nil,
167
- trust_key: context.trust_key,
168
- guid: context.guid,
169
- trace_id: context.trace_id,
170
- priority: context.priority,
171
- sampled: context.sampled
172
- }
173
- end
174
-
175
164
def convert_to_outbound(%Context{} = context) do
176
165
%Context{
177
166
source: context.source,
changed lib/new_relic/distributed_trace/new_relic_context.ex
 
@@ -73,24 73,21 @@ defmodule NewRelic.DistributedTrace.NewRelicContext do
73
73
"d" =>
74
74
%{
75
75
"ty" => context.type,
76
- "ac" => context.account_id,
77
- "ap" => context.app_id,
76
"ac" => context.account_id |> to_string,
77
"ap" => context.app_id |> to_string,
78
78
"tx" => context.guid,
79
79
"tr" => context.trace_id,
80
"id" => context.span_guid,
80
81
"pr" => context.priority,
81
82
"sa" => context.sampled,
82
83
"ti" => context.timestamp
83
84
}
84
- |> maybe_put(:span_guid, "id", context.sampled, context.span_guid)
85
85
|> maybe_put(:trust_key, "tk", context.account_id, context.trust_key)
86
86
}
87
87
|> Jason.encode!()
88
88
|> Base.encode64()
89
89
end
90
90
91
- def maybe_put(data, :span_guid, key, true = _sampled, guid), do: Map.put(data, key, guid)
92
- def maybe_put(data, :span_guid, _key, false = _sampled, _guid), do: data
93
-
94
91
def maybe_put(data, :trust_key, _key, account_id, account_id), do: data
95
92
def maybe_put(data, :trust_key, _key, _account_id, nil), do: data
96
93
def maybe_put(data, :trust_key, key, _account_id, trust_key), do: Map.put(data, key, trust_key)
changed lib/new_relic/logger.ex
 
@@ -44,6 44,10 @@ defmodule NewRelic.Logger do
44
44
{:reply, StringIO.flush(io_device), state}
45
45
end
46
46
47
def handle_call(:flush, _from, state) do
48
{:reply, "", state}
49
end
50
47
51
def handle_call({:logger, logger}, _from, old_state) do
48
52
{:ok, io_device} = device(logger)
49
53
{:reply, old_state, %{io_device: io_device, logger: logger}}
changed lib/new_relic/telemetry/ecto.ex
 
@@ -18,22 18,24 @@ defmodule NewRelic.Telemetry.Ecto do
18
18
SQL query collection via configuration. See `NewRelic.Config` for details.
19
19
"""
20
20
21
- def start_link(otp_app) do
22
- enabled = NewRelic.Config.feature?(:ecto_instrumentation)
23
- ecto_repos = Application.get_env(otp_app, :ecto_repos)
24
- config = extract_config(otp_app, ecto_repos)
21
def start_link(repo: repo, opts: opts) do
22
config = %{
23
enabled?: NewRelic.Config.feature?(:ecto_instrumentation),
24
collect_sql?: NewRelic.Config.feature?(:sql_collection),
25
handler_id: {:new_relic_ecto, repo},
26
event: opts[:telemetry_prefix] [:query],
27
opts: opts
28
}
25
29
26
- GenServer.start_link(__MODULE__, config: config, enabled: enabled)
30
GenServer.start_link(__MODULE__, config)
27
31
end
28
32
29
- def init(config: _, enabled: false), do: :ignore
33
def init(%{enabled?: false}), do: :ignore
30
34
31
- def init(config: config, enabled: true) do
32
- log(config)
33
-
34
- :telemetry.attach_many(
35
def init(%{enabled?: true} = config) do
36
:telemetry.attach(
35
37
config.handler_id,
36
- config.events,
38
config.event,
37
39
&NewRelic.Telemetry.Ecto.Handler.handle_event/4,
38
40
config
39
41
)
 
@@ -45,59 47,4 @@ defmodule NewRelic.Telemetry.Ecto do
45
47
def terminate(_reason, %{handler_id: handler_id}) do
46
48
:telemetry.detach(handler_id)
47
49
end
48
-
49
- defp extract_config(otp_app, ecto_repos) do
50
- %{
51
- otp_app: otp_app,
52
- events: extract_events(otp_app, ecto_repos),
53
- repo_configs: extract_repo_configs(otp_app, ecto_repos),
54
- collect_sql?: NewRelic.Config.feature?(:sql_collection),
55
- handler_id: {:new_relic_ecto, otp_app}
56
- }
57
- end
58
-
59
- defp extract_events(otp_app, ecto_repos) do
60
- Enum.map(ecto_repos, fn repo ->
61
- ecto_telemetry_prefix(otp_app, repo) [:query]
62
- end)
63
- end
64
-
65
- defp extract_repo_configs(otp_app, ecto_repos) do
66
- Enum.into(ecto_repos, %{}, fn repo ->
67
- {repo, extract_repo_config(otp_app, repo)}
68
- end)
69
- end
70
-
71
- defp extract_repo_config(otp_app, repo) do
72
- Application.get_env(otp_app, repo)
73
- |> Map.new()
74
- |> case do
75
- %{url: url} ->
76
- uri = URI.parse(url)
77
-
78
- %{
79
- hostname: uri.host,
80
- port: uri.port,
81
- database: uri.path |> String.trim_leading("/")
82
- }
83
-
84
- config ->
85
- config
86
- end
87
- end
88
-
89
- defp ecto_telemetry_prefix(otp_app, repo) do
90
- Application.get_env(otp_app, repo)
91
- |> Keyword.get_lazy(:telemetry_prefix, fn ->
92
- repo
93
- |> Module.split()
94
- |> Enum.map(&(&1 |> Macro.underscore() |> String.to_atom()))
95
- end)
96
- end
97
-
98
- defp log(%{repo_configs: repo_configs}) do
99
- for {repo, _config} <- repo_configs do
100
- NewRelic.log(:info, "Detected Ecto Repo `#{inspect(repo)}`")
101
- end
102
- end
103
50
end
changed lib/new_relic/telemetry/ecto/handler.ex
 
@@ -17,9 17,9 @@ defmodule NewRelic.Telemetry.Ecto.Handler do
17
17
queue_time_ms = measurements[:queue_time] |> to_ms
18
18
decode_time_ms = measurements[:decode_time] |> to_ms
19
19
20
- database = config.repo_configs[repo][:database] || "unknown"
21
- hostname = config.repo_configs[repo][:hostname] || "unknown"
22
- port = config.repo_configs[repo][:port] || "unknown"
20
database = config.opts[:database] || "unknown"
21
hostname = config.opts[:hostname] || "unknown"
22
port = config.opts[:port] || "unknown"
23
23
24
24
query = (config.collect_sql? && metadata.query) || ""
changed lib/new_relic/telemetry/ecto/supervisor.ex
 
@@ -3,15 3,29 @@ defmodule NewRelic.Telemetry.Ecto.Supervisor do
3
3
4
4
use DynamicSupervisor
5
5
6
@ecto_repo_init [:ecto, :repo, :init]
7
6
8
def start_link() do
7
9
DynamicSupervisor.start_link(__MODULE__, :ok, name: __MODULE__)
8
10
end
9
11
10
- def start_child(otp_app) do
11
- DynamicSupervisor.start_child(__MODULE__, {NewRelic.Telemetry.Ecto, otp_app})
12
- end
13
-
14
12
def init(:ok) do
13
:telemetry.attach(
14
{:new_relic_ecto, :supervisor},
15
@ecto_repo_init,
16
&__MODULE__.handle_event/4,
17
%{}
18
)
19
15
20
DynamicSupervisor.init(strategy: :one_for_one)
16
21
end
22
23
def handle_event(@ecto_repo_init, _, %{repo: repo, opts: opts}, _) do
24
NewRelic.log(:info, "Detected Ecto Repo `#{inspect(repo)}`")
25
26
DynamicSupervisor.start_child(
27
__MODULE__,
28
{NewRelic.Telemetry.Ecto, [repo: repo, opts: opts]}
29
)
30
end
17
31
end
changed lib/new_relic/transaction/monitor.ex
 
@@ -90,15 90,6 @@ defmodule NewRelic.Transaction.Monitor do
90
90
{:noreply, state}
91
91
end
92
92
93
- def handle_info(
94
- {:trace_ts, _pid, :call,
95
- {Ecto.Repo.Supervisor, :start_link, [_repo, otp_app, _adapter, _opts]}, _timestamp},
96
- state
97
- ) do
98
- NewRelic.Telemetry.Ecto.Supervisor.start_child(otp_app)
99
- {:noreply, state}
100
- end
101
-
102
93
def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do
103
94
Transaction.Reporter.ensure_purge(pid)
104
95
Transaction.Reporter.complete(pid, :async)
 
@@ -140,7 131,6 @@ defmodule NewRelic.Transaction.Monitor do
140
131
# http://erlang.org/doc/apps/erts/match_spec.html
141
132
trace_task_async_nolink()
142
133
trace_poolboy_checkout()
143
- trace_ecto_repo_discovery()
144
134
end
145
135
146
136
defp trace_task_async_nolink do
 
@@ -150,9 140,4 @@ defmodule NewRelic.Transaction.Monitor do
150
140
defp trace_poolboy_checkout do
151
141
:erlang.trace_pattern({:poolboy, :checkout, :_}, [{:_, [], [{:return_trace}]}], [])
152
142
end
153
-
154
- defp trace_ecto_repo_discovery() do
155
- Code.ensure_loaded?(Ecto.Repo.Supervisor) &&
156
- :erlang.trace_pattern({Ecto.Repo.Supervisor, :start_link, :_}, true, [:meta])
157
- end
158
143
end
changed lib/new_relic/util.ex
 
@@ -57,27 57,30 @@ defmodule NewRelic.Util do
57
57
end
58
58
59
59
def coerce_attributes(attrs) do
60
- Enum.map(attrs, fn
60
Enum.flat_map(attrs, fn
61
{_key, nil} ->
62
[]
63
61
64
{key, value} when is_number(value) when is_boolean(value) ->
62
- {key, value}
65
[{key, value}]
63
66
64
67
{key, value} when is_bitstring(value) ->
65
68
case String.valid?(value) do
66
- true -> {key, value}
67
- false -> bad_value(key, value)
69
true -> [{key, value}]
70
false -> [bad_value(key, value)]
68
71
end
69
72
70
73
{key, value} when is_reference(value) when is_pid(value) when is_port(value) ->
71
- {key, inspect(value)}
74
[{key, inspect(value)}]
72
75
73
76
{key, value} when is_atom(value) ->
74
- {key, to_string(value)}
77
[{key, to_string(value)}]
75
78
76
79
{key, %struct{} = value} when struct in [Date, DateTime, Time, NaiveDateTime] ->
77
- {key, struct.to_iso8601(value)}
80
[{key, struct.to_iso8601(value)}]
78
81
79
82
{key, value} ->
80
- bad_value(key, value)
83
[bad_value(key, value)]
81
84
end)
82
85
end
changed mix.exs
 
@@ -43,7 43,8 @@ defmodule NewRelic.Mixfile do
43
43
{:plug, "~> 1.0"},
44
44
{:plug_cowboy, "~> 2.0", optional: true},
45
45
# Optional Instrumentation:
46
- {:ecto_sql, "~> 3.3", optional: true}
46
{:ecto_sql, ">= 3.4.0", optional: true},
47
{:ecto, ">= 3.4.1", optional: true}
47
48
]
48
49
end