Skip to content

Commit

Permalink
Merge branch 'master' into vince/instrument-finch
Browse files Browse the repository at this point in the history
  • Loading branch information
tpitale authored Jan 13, 2025
2 parents 7b60d4a + 4ad638c commit c1e4f72
Show file tree
Hide file tree
Showing 36 changed files with 289 additions and 287 deletions.
19 changes: 1 addition & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,6 @@ config :new_relic_agent,
httpc_request_options: [connect_timeout: 5000]
```

#### For Elixir 1.15 and higher

Due to changes in the Elixir 1.15 Logger, additional logger configuration is needed for NewRelic to capture all errors. Update your logger configuration by setting `handle_sasl_reports` to `true` and adding `NewRelic.ErrorLogger` to your logger backends.

```elixir
config :logger,
handle_sasl_reports: true,
backends: [:console, NewRelic.ErrorLogger]
```

## Telemetry-based Instrumentation

Some common Elixir packages are auto-instrumented via [`telemetry`](https://github.com/beam-telemetry/telemetry)
Expand Down Expand Up @@ -157,7 +147,7 @@ defmodule MyExternalService do
end
```

#### Pre-Instrumented Modules
#### Mix Tasks

`NewRelic.Instrumented.Mix.Task` To enable the agent and record an Other Transaction during a `Mix.Task`, simply `use NewRelic.Instrumented.Mix.Task`. This will ensure the agent is properly started, records a Transaction, and is shut down.

Expand All @@ -172,13 +162,6 @@ defmodule Mix.Tasks.Example do
end
```

`NewRelic.Instrumented.HTTPoison` Automatically wraps HTTP calls in a span, and adds an outbound header to track the request as part of a Distributed Trace.

```elixir
alias NewRelic.Instrumented.HTTPoison
HTTPoison.get("http://www.example.com")
```

#### Other Transactions

You may start an "Other" Transaction for non-HTTP related work. This could used be while consuming from a message queue, for example.
Expand Down
4 changes: 0 additions & 4 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import Config

config :logger,
handle_sasl_reports: true,
backends: [NewRelic.ErrorLogger]

if Mix.env() == :test, do: import_config("test.exs")
if File.exists?("config/secret.exs"), do: import_config("secret.exs")
2 changes: 2 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ config :logger, level: :warning

config :new_relic_agent,
app_name: "ElixirAgentTest",
license_key: "license_key",
bypass_collector: true,
automatic_attributes: [test_attribute: "test_value"],
ignore_paths: ["/ignore/this", ~r(ignore/these/*.)],
log: "Logger"
4 changes: 3 additions & 1 deletion examples/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ config :logger, level: :debug

config :new_relic_agent,
app_name: "ExampleApps",
trusted_account_key: "trusted_account_key"
trusted_account_key: "trusted_account_key",
license_key: "license_key",
bypass_collector: true

for config <- "../apps/*/config/config.exs" |> Path.expand(__DIR__) |> Path.wildcard() do
import_config config
Expand Down
4 changes: 2 additions & 2 deletions lib/new_relic.ex
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ defmodule NewRelic do
```elixir
NewRelic.set_span(:generic, some: "attribute")
NewRelic.set_span(:http, url: "https://elixir-lang.org", method: "GET", component: "HTTPoison")
NewRelic.set_span(:http, url: "https://elixir-lang.org", method: "GET", component: "HttpClient")
NewRelic.set_span(:datastore, statement: statement, instance: instance, address: address,
hostname: hostname, component: component)
Expand All @@ -197,7 +197,7 @@ defmodule NewRelic do
of an incoming Distributed Trace, but outgoing requests need an extra header:
```elixir
HTTPoison.get(url, ["x-api-key": "secret"] ++ NewRelic.distributed_trace_headers(:http))
Req.get(url, headers: ["x-api-key": "secret"] ++ NewRelic.distributed_trace_headers(:http))
```
## Notes
Expand Down
8 changes: 7 additions & 1 deletion lib/new_relic/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,10 @@ defmodule NewRelic.Config do
* Controls all Oban instrumentation
* `:finch_instrumentation_enabled` (default `true`)
* Controls all Finch instrumentation
* `:request_queuing_metrics_enabled`
* `:request_queuing_metrics_enabled` (default `true`)
* Controls collection of request queuing metrics
* `:extended_attributes` (default `true`)
* Controls reporting extended per-source attributes for datastore, external and function traces
### Configuration
Expand Down Expand Up @@ -215,6 +217,10 @@ defmodule NewRelic.Config do
get(:features, :request_queuing_metrics)
end

def feature?(:extended_attributes) do
get(:features, :extended_attributes)
end

@doc """
Some Agent features can be controlled via configuration.
Expand Down
3 changes: 2 additions & 1 deletion lib/new_relic/distributed_trace/backoff_sampler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ defmodule NewRelic.DistributedTrace.BackoffSampler do
:persistent_term.put({__MODULE__, :counter}, new(@size, []))
put(@sampling_target, AgentRun.lookup(:sampling_target) || 10)

trigger_next_cycle()
{:ok, %{}}
end

Expand Down Expand Up @@ -94,7 +95,7 @@ defmodule NewRelic.DistributedTrace.BackoffSampler do

def trigger_next_cycle() do
cycle_period = AgentRun.lookup(:sampling_target_period) || 60_000
Process.send_after(self(), :cycle, cycle_period)
Process.send_after(__MODULE__, :cycle, cycle_period)
end

def update_state(false = _sampled?) do
Expand Down
2 changes: 2 additions & 0 deletions lib/new_relic/enabled_supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ defmodule NewRelic.EnabledSupervisor do
NewRelic.Aggregate.Supervisor
]

NewRelic.OsMon.start()

Supervisor.init(children, strategy: :one_for_one)
end
end
62 changes: 62 additions & 0 deletions lib/new_relic/error/logger_filter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule NewRelic.Error.LoggerFilter do
@moduledoc false

# Track errors by attaching a `:logger` primary filter
# Always returns `:ignore` so we don't actually filter anything

def add_filter() do
:logger.add_primary_filter(__MODULE__, {&__MODULE__.filter/2, []})
end

def remove_filter() do
:logger.remove_primary_filter(__MODULE__)
end

def filter(
%{
meta: %{error_logger: %{type: :crash_report}},
msg: {:report, %{report: [report | _]}}
},
_opts
) do
if NewRelic.Transaction.Sidecar.tracking?() do
NewRelic.Error.Reporter.CrashReport.report_error(:transaction, report)
else
NewRelic.Error.Reporter.CrashReport.report_error(:process, report)
end

:ignore
end

if NewRelic.Util.ConditionalCompile.match?("< 1.15.0") do
def filter(
%{
meta: %{error_logger: %{tag: :error_msg}},
msg: {:report, %{label: {_, :terminating}}}
},
_opts
) do
:ignore
end
end

def filter(
%{
meta: %{error_logger: %{tag: :error_msg}},
msg: {:report, %{report: %{} = report}}
},
_opts
) do
if NewRelic.Transaction.Sidecar.tracking?() do
NewRelic.Error.Reporter.ErrorMsg.report_error(:transaction, report)
else
NewRelic.Error.Reporter.ErrorMsg.report_error(:process, report)
end

:ignore
end

def filter(_log, _opts) do
:ignore
end
end
33 changes: 0 additions & 33 deletions lib/new_relic/error/logger_handler.ex

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule NewRelic.Error.Reporter do
defmodule NewRelic.Error.Reporter.CrashReport do
@moduledoc false

alias NewRelic.Util
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
defmodule NewRelic.Error.MetadataReporter do
defmodule NewRelic.Error.Reporter.ErrorMsg do
@moduledoc false

alias NewRelic.Util
alias NewRelic.Harvest.Collector

# Before elixir 1.15, ignore terminating errors so they don't get reported twice
if NewRelic.Util.ConditionalCompile.match?("< 1.15.0") do
def report_error(_, {{_, :terminating}, _}), do: nil
end

def report_error(:transaction, {_cause, metadata}) do
kind = :error
{exception, stacktrace} = metadata.reason
process_name = parse_process_name(metadata[:registered_name], stacktrace)
def report_error(:transaction, report) do
{exception, stacktrace} = report.reason
process_name = parse_process_name(report[:registered_name], stacktrace)

NewRelic.add_attributes(process: process_name)

NewRelic.Transaction.Reporter.error(%{
kind: kind,
kind: :error,
reason: exception,
stack: stacktrace
})
end

def report_error(:process, {_cause, metadata}) do
{exception_type, reason, stacktrace, expected} = parse_reason(metadata.reason)
def report_error(:process, report) do
{exception_type, reason, stacktrace, expected} = parse_reason(report.reason)

process_name = parse_process_name(metadata[:registered_name], stacktrace)
process_name = parse_process_name(report[:registered_name], stacktrace)
automatic_attributes = NewRelic.Config.automatic_attributes()
formatted_stacktrace = Util.Error.format_stacktrace(stacktrace, nil)

Expand Down
10 changes: 5 additions & 5 deletions lib/new_relic/error/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ defmodule NewRelic.Error.Supervisor do
]

if NewRelic.Config.feature?(:error_collector) do
add_handler()
add_filter()
end

Supervisor.init(children, strategy: :one_for_one)
end

def add_handler(),
do: NewRelic.Error.LoggerHandler.add_handler()
def add_filter(),
do: NewRelic.Error.LoggerFilter.add_filter()

def remove_handler(),
do: NewRelic.Error.LoggerHandler.remove_handler()
def remove_filter(),
do: NewRelic.Error.LoggerFilter.remove_filter()
end
40 changes: 6 additions & 34 deletions lib/new_relic/error_logger.ex
Original file line number Diff line number Diff line change
@@ -1,44 +1,16 @@
defmodule NewRelic.ErrorLogger do
@moduledoc """
Handle error reporting in elixir >= 1.15
"""
@moduledoc false
require Logger
@behaviour :gen_event

if NewRelic.Util.ConditionalCompile.match?(">= 1.15.0") do
def init(opts) do
Logger.add_translator({__MODULE__, :translator})
{:ok, opts}
end
else
def init(opts) do
{:ok, opts}
end
def init(_) do
Logger.warning("`NewRelic.ErrorLogger` no longer needed, please remove it from :logger configuration")
{:ok, nil}
end

def handle_call(_opts, state), do: {:ok, :ok, state}

def handle_event(_opts, state), do: {:ok, state}

def handle_info(_opts, state), do: {:ok, state}

def code_change(_old_vsn, state, _extra), do: {:ok, state}

if NewRelic.Util.ConditionalCompile.match?(">= 1.15.0") do
def terminate(_reason, _state) do
Logger.remove_translator({__MODULE__, :translator})
:ok
end
else
def terminate(_reason, _state) do
:ok
end
end

# Don't log SASL progress reports
def translator(_level, _message, _timestamp, {{caller, :progress}, _})
when caller in [:supervisor, :application_controller] do
:skip
end

def translator(_level, _message, _timestamp, _metadata), do: :none
def terminate(_reason, _state), do: :ok
end
2 changes: 1 addition & 1 deletion lib/new_relic/graceful_shutdown.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule NewRelic.GracefulShutdown do

def terminate(_reason, _state) do
NewRelic.log(:info, "Attempting graceful shutdown")
NewRelic.Error.Supervisor.remove_handler()
NewRelic.Error.Supervisor.remove_filter()
NewRelic.Harvest.Supervisor.manual_shutdown()
end
end
14 changes: 13 additions & 1 deletion lib/new_relic/harvest/collector/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,17 @@ defmodule NewRelic.Harvest.Collector.Protocol do
do:
params
|> collector_method_url
|> NewRelic.Util.HTTP.post(payload, collector_headers())
|> post(payload)
|> parse_http_response(params)

defp post(url, payload) do
if Application.get_env(:new_relic_agent, :bypass_collector, false) do
{:error, :bypass_collector}
else
NewRelic.Util.HTTP.post(url, payload, collector_headers())
end
end

defp retry_call({:ok, response}, _params, _payload), do: {:ok, response}

@retryable [408, 429, 500, 503]
Expand Down Expand Up @@ -120,6 +128,10 @@ defmodule NewRelic.Harvest.Collector.Protocol do
{:error, status}
end

defp parse_http_response({:error, :bypass_collector}, _params) do
{:error, :bypass_collector}
end

defp parse_http_response({:error, reason}, params) do
log_error(:failed_request, reason, params)
{:error, reason}
Expand Down
Loading

0 comments on commit c1e4f72

Please sign in to comment.