New Relic Elixir Agent - Comprehensive Feature Analysis
View SourceVersion: Based on agent codebase analysis Purpose: Technical reference for building a local-first observability solution Generated: 2025-10-17
Table of Contents
- Core Features
- Telemetry Handlers & Auto-Instrumentation
- Public API Reference
- Data Structures
- Supported Frameworks & Libraries
- Manual Instrumentation
- Configuration & Feature Toggles
- Data Collection & Reporting
1. Core Features
1.1 Transaction Tracking
New Relic tracks two types of transactions:
Web Transactions:
- Auto-detected via telemetry events from web servers (Cowboy, Bandit)
- Tracks HTTP request/response lifecycle
- Captures request metadata, headers, timing, memory, reductions
Other Transactions:
- Background jobs, message queue consumers, scheduled tasks
- Manually started/stopped via API
- Examples: Oban jobs, GenStage consumers, custom workers
Key Capabilities:
- Transaction naming and grouping
- Custom attributes (key-value pairs)
- Automatic and manual error tracking
- Process monitoring (memory, reductions, message queue)
- Distributed tracing integration
1.2 Distributed Tracing
Protocol Support:
- W3C Trace Context (traceparent, tracestate headers)
- New Relic proprietary format
- Automatic context propagation across service boundaries
Features:
- Trace ID generation and propagation
- Parent-child span relationships
- Sampling with priority
- Cross-process span tracking
- Transport duration calculation
Implementation Details:
# From distributed_trace.ex
defstruct source: nil,
account_id: nil,
app_id: nil,
parent_id: nil,
span_guid: nil,
trace_id: nil,
priority: nil,
sampled: nil,
timestamp: nil,
trust_key: nil1.3 Span Events
Spans represent individual units of work within a transaction:
Span Categories:
:generic- Default category for any work:http- External HTTP calls:datastore- Database/cache operations:error- Failed operations
Span Attributes:
# From span/event.ex
defstruct type: "Span",
trace_id: nil,
guid: nil,
parent_id: nil,
transaction_id: nil,
sampled: nil,
priority: nil,
timestamp: nil,
duration: nil,
name: nil,
category: nil,
entry_point: false,
category_attributes: %{}HTTP Span Attributes:
http.url,http.method,component,span.kind
Datastore Span Attributes:
db.statement,db.instance,peer.address,peer.hostname,component
1.4 Error Tracking
Error Collection:
- Automatic exception capture in transactions
- Manual error reporting via
notice_error/2 - Stack trace capture
- Error context (transaction name, timestamp)
Error Structure:
# From error/trace.ex
defstruct timestamp: nil,
transaction_name: "",
message: nil,
expected: false,
error_type: nil,
cat_guid: "",
stack_trace: nil,
agent_attributes: %{},
user_attributes: %{}1.5 Metrics
Metric Types:
- Transaction metrics (duration, throughput)
- Datastore metrics (query performance)
- External metrics (HTTP call performance)
- Custom metrics
- Dimensional metrics (count, gauge, summary)
Metric Structure:
# From metric/metric.ex
defstruct name: "",
scope: "",
call_count: 0,
total_call_time: 0,
total_exclusive_time: 0,
min_call_time: 0,
max_call_time: 0,
sum_of_squares: 02. Telemetry Handlers & Auto-Instrumentation
2.1 Plug Instrumentation
File: lib/new_relic/telemetry/plug.ex
Events Monitored:
[:cowboy, :request, :start]
[:cowboy, :request, :stop]
[:cowboy, :request, :exception]
[:bandit, :request, :start]
[:bandit, :request, :stop]
[:bandit, :request, :exception]
[:plug, :router_dispatch, :start]Data Collected:
- HTTP method, path, host
- Request/response headers
- Status code
- Duration (total, request body, response body)
- Remote IP
- User agent, referer, content type
- Memory usage, reductions
- Request queue time (via
x-request-startheader)
Key Code:
defp add_start_attrs(meta, meas, headers, :cowboy) do
[
pid: inspect(self()),
"http.server": "cowboy",
start_time: meas[:system_time],
host: meta.req.host,
path: meta.req.path,
remote_ip: meta.req.peer |> elem(0) |> :inet_parse.ntoa() |> to_string(),
referer: headers["referer"],
user_agent: headers["user-agent"],
content_type: headers["content-type"],
request_method: meta.req.method
]
|> NewRelic.add_attributes()
end2.2 Phoenix Instrumentation
File: lib/new_relic/telemetry/phoenix.ex
Events Monitored:
[:phoenix, :router_dispatch, :start]
[:phoenix, :controller, :render, :start]
[:phoenix, :controller, :render, :stop]
[:phoenix, :error_rendered]Data Collected:
- Controller and action names
- Phoenix endpoint, router
- Template, view, format
- Render duration
- Transaction naming based on controller/action
Transaction Naming:
defp phoenix_name(%{plug: controller, plug_opts: action}) when is_atom(action) do
"/Phoenix/#{inspect(controller)}/#{action}"
end2.3 Phoenix LiveView Instrumentation
File: lib/new_relic/telemetry/phoenix_live_view.ex
Events Monitored:
[:phoenix, :live_view, :mount, :start/stop/exception]
[:phoenix, :live_view, :handle_params, :start/stop/exception]
[:phoenix, :live_view, :handle_event, :start/stop/exception]
[:phoenix, :live_view, :render, :start/stop/exception]Data Collected:
- LiveView module name
- Mount/handle_params/handle_event parameters
- Socket ID, action
- Router, endpoint
- Lifecycle event durations
- Render changed/forced flags
Special Handling:
# Creates transaction for WebSocket process events
if meta.socket.transport_pid do
with :collect <- Transaction.Reporter.start_transaction(:web, path) do
NewRelic.DistributedTrace.start(:other)
# ... collect transaction data
end
end2.4 Ecto Instrumentation
File: lib/new_relic/telemetry/ecto/handler.ex
Events Monitored:
# Dynamically attached based on Ecto repos
[repo, :query] # e.g., [:my_app, :repo, :query]Data Collected:
- SQL query text (optional, controlled by config)
- Database name, hostname, port
- Query duration (total, query, queue, decode)
- Table name, operation (SELECT, INSERT, UPDATE, DELETE)
- Repo name
Query Parsing:
# From ecto/metadata.ex
def parse(%{result: {:ok, %{command: command} = result}}) do
datastore = "Postgres" # or MySQL, etc.
operation = command |> to_string() |> String.upcase()
table = extract_table(result)
{datastore, {operation, table}}
endMetrics Reported:
metric_name = "Datastore/statement/#{datastore}/#{table}/#{operation}"
NewRelic.incr_attributes(
databaseCallCount: 1,
databaseDuration: duration_s,
datastore_call_count: 1,
datastore_duration_ms: duration_ms
)2.5 Redix Instrumentation
File: lib/new_relic/telemetry/redix.ex
Events Monitored:
[:redix, :connection]
[:redix, :pipeline, :stop]Data Collected:
- Redis command or pipeline
- Connection name/address
- Operation duration
- Host, port
- Query text (optional)
Command Parsing:
defp parse_command([[operation | _args] = command], collect: true) do
query = Enum.join(command, " ")
{operation, query}
end
defp parse_command(pipeline, collect: true) do
query = pipeline |> Enum.map(&Enum.join(&1, " ")) |> Enum.join("; ")
{"PIPELINE", query}
end2.6 Oban Instrumentation
File: lib/new_relic/telemetry/oban.ex
Events Monitored:
[:oban, :job, :start]
[:oban, :job, :stop]
[:oban, :job, :exception]Data Collected:
- Worker module, queue name
- Job arguments (inspected)
- Job tags, attempt number, priority
- Queue time, execution duration
- Job result/state
- Memory usage, reductions
Transaction Naming:
other_transaction_name: "Oban/#{meta.queue}/#{meta.worker}/perform"2.7 Finch Instrumentation
File: lib/new_relic/telemetry/finch.ex
Events Monitored:
[:finch, :request, :start]
[:finch, :request, :stop]
[:finch, :request, :exception]Data Collected:
- HTTP method, URL (scheme, host, path)
- Request duration
- Response status
- Finch pool name
- Error information
External Metrics:
metric_name = "External/#{request.host}/Finch/#{request.method}"
NewRelic.incr_attributes(
"external.#{request.host}.call_count": 1,
"external.#{request.host}.duration_ms": duration_ms
)2.8 Absinthe (GraphQL) Instrumentation
File: lib/new_relic/telemetry/absinthe.ex
Events Monitored:
[:absinthe, :execute, :operation, :start]
[:absinthe, :execute, :operation, :stop]
[:absinthe, :resolve, :field, :start]
[:absinthe, :resolve, :field, :stop]Data Collected:
- GraphQL query text (optional)
- Operation type (query, mutation, subscription)
- Operation name
- Schema module
- Field resolution details (path, type, arguments)
- Resolver function info
Transaction Naming:
defp transaction_name(schema, operation) do
deepest_path = operation |> collect_deepest_path() |> Enum.join(".")
"Absinthe/#{inspect(schema)}/#{operation.type}/#{deepest_path}"
end3. Public API Reference
3.1 Transaction Control
set_transaction_name/1
@spec set_transaction_name(String.t()) :: any()
NewRelic.set_transaction_name("/Plug/custom/transaction/name")Sets the transaction name. First segment is the namespace (e.g., "Plug", "Phoenix").
start_transaction/2 and start_transaction/3
@spec start_transaction(String.t(), String.t()) :: any()
@spec start_transaction(String.t(), String.t(), headers :: map) :: any()
NewRelic.start_transaction("GenStage", "MyConsumer/EventType")
NewRelic.start_transaction("Task", "TaskName")
NewRelic.start_transaction("WebSocket", "Handler", %{"newrelic" => "..."})Starts an "Other" (non-web) transaction with optional distributed trace headers.
stop_transaction/0
@spec stop_transaction() :: any()
NewRelic.stop_transaction()Stops the current "Other" transaction.
other_transaction/3 (macro)
NewRelic.other_transaction("Worker", "ProcessMessages") do
# Work happens here
endWraps a block of code in a transaction (start + execute + stop).
ignore_transaction/0
@spec ignore_transaction() :: any()
def health_check(conn, _) do
NewRelic.ignore_transaction()
send_resp(conn, 200, "OK")
endPrevents current transaction from being reported.
3.2 Attributes
add_attributes/1
@spec add_attributes(attributes :: Keyword.t()) :: any()
NewRelic.add_attributes(user_id: "abc123", tier: "premium")
NewRelic.add_attributes(map: %{foo: "bar", baz: "qux"})
NewRelic.add_attributes(list: ["a", "b", "c"])Adds custom attributes to the current transaction. Supports nested maps/lists (auto-flattened).
Flattening behavior:
%{foo: "bar"}→"map.foo" => "bar","map.size" => 1["a", "b"]→"list.0" => "a","list.1" => "b","list.length" => 2
3.3 Distributed Tracing
distributed_trace_headers/1
@spec distributed_trace_headers(:http) :: [{key :: String.t(), value :: String.t()}]
@spec distributed_trace_headers(:other) :: map()
headers = NewRelic.distributed_trace_headers(:http)
# Returns W3C traceparent, tracestate, and New Relic headers
Req.get(url, headers: headers)Generates distributed trace headers for outgoing requests. Must be called immediately before making the request.
3.4 Span Management
set_span/2
@spec set_span(:generic, attributes :: Keyword.t()) :: any()
@spec set_span(:http, url: String.t(), method: String.t(), component: String.t()) :: any()
@spec set_span(:datastore,
statement: String.t(),
instance: String.t(),
address: String.t(),
hostname: String.t(),
component: String.t()
) :: any()
NewRelic.set_span(:http,
url: "https://api.example.com",
method: "GET",
component: "HTTPoison"
)Sets the current span's category and attributes.
add_span_attributes/1
@spec add_span_attributes(attributes :: Keyword.t()) :: any()
NewRelic.add_span_attributes(user_role: "admin", cache_hit: true)Adds attributes to the current span (not transaction).
span/2 (macro)
NewRelic.span("do.some_work", user_id: "abc123") do
# Work happens here
endWraps a block in a span for instrumentation.
3.5 Error Reporting
notice_error/2
@spec notice_error(Exception.t(), Exception.stacktrace()) :: any()
try do
raise RuntimeError, "Something went wrong"
rescue
exception ->
NewRelic.notice_error(exception, __STACKTRACE__)
# Continue handling...
endManually reports an exception that was rescued.
3.6 Process Management
exclude_from_transaction/0
@spec exclude_from_transaction() :: any()
Task.async(fn ->
NewRelic.exclude_from_transaction()
Work.wont_be_included()
end)Excludes current process from parent transaction.
get_transaction/0 and connect_to_transaction/1
@spec get_transaction() :: tx_ref
@spec connect_to_transaction(tx_ref) :: any()
tx = NewRelic.get_transaction()
spawn(fn ->
NewRelic.connect_to_transaction(tx)
# This work is now part of the transaction
end)Advanced: Manual transaction connection across processes.
disconnect_from_transaction/0
@spec disconnect_from_transaction() :: any()
NewRelic.disconnect_from_transaction()Manually disconnects from current transaction.
3.7 Custom Events & Metrics
report_custom_event/2
@spec report_custom_event(type :: String.t(), event :: map()) :: any()
NewRelic.report_custom_event("Purchase", %{
"amount" => 99.99,
"currency" => "USD",
"user_id" => "123"
})Reports a custom event to NRDB.
report_custom_metric/2
@spec report_custom_metric(name :: String.t(), value :: number()) :: any()
NewRelic.report_custom_metric("Custom/MyMetric/ResponseTime", 150)Reports a custom metric value.
increment_custom_metric/2
@spec increment_custom_metric(name :: String.t(), count :: integer()) :: any()
NewRelic.increment_custom_metric("Custom/Cache/Hits")
NewRelic.increment_custom_metric("Custom/Queue/Size", 5)Increments a counter metric.
report_dimensional_metric/4
@spec report_dimensional_metric(
type :: :count | :gauge | :summary,
name :: String.t(),
value :: number,
attributes :: map()
) :: any()
NewRelic.report_dimensional_metric(:count, "my_metric_name", 1, %{
region: "us-west",
environment: "production"
})Reports dimensional (tagged) metrics.
3.8 Process Sampling
sample_process/0
@spec sample_process() :: any()
defmodule ImportantProcess do
use GenServer
def init(:ok) do
NewRelic.sample_process()
{:ok, %{}}
end
endEnables process monitoring (message queue, reductions, memory).
4. Data Structures
4.1 Transaction Event
Structure:
defstruct type: "Transaction",
web_duration: nil,
database_duration: nil,
timestamp: nil,
name: nil,
duration: nil,
total_time: nil,
user_attributes: %{}Format for reporting:
[
%{
webDuration: 0.5,
totalTime: 0.6,
databaseDuration: 0.2,
timestamp: 1697500000000,
name: "/Phoenix/UserController/show",
duration: 0.5,
type: "Transaction"
},
%{
# User attributes
user_id: "123",
plan: "premium"
}
]4.2 Span Event
Structure:
defstruct type: "Span",
trace_id: nil,
guid: nil,
parent_id: nil,
transaction_id: nil,
sampled: nil,
priority: nil,
timestamp: nil,
duration: nil,
name: nil,
category: nil,
entry_point: false,
category_attributes: %{}HTTP Span Format:
%{
type: "Span",
traceId: "abc123",
guid: "span456",
parentId: "parent789",
"http.url": "https://api.example.com/users",
"http.method": "GET",
component: "Finch",
"span.kind": "client"
}Datastore Span Format:
%{
type: "Span",
"db.statement": "SELECT * FROM users WHERE id = $1",
"db.instance": "myapp_prod",
"peer.address": "localhost:5432",
"peer.hostname": "localhost",
component: "Postgres",
"span.kind": "client"
}4.3 Error Trace
Structure:
defstruct timestamp: nil,
transaction_name: "",
message: nil,
expected: false,
error_type: nil,
cat_guid: "",
stack_trace: nil,
agent_attributes: %{},
user_attributes: %{}Format for reporting:
[
timestamp,
transaction_name,
message,
error_type,
%{
stack_trace: [...],
agentAttributes: %{request_uri: "/users/123"},
userAttributes: %{user_id: "123"},
intrinsics: %{"error.expected": false, traceId: "abc", guid: "xyz"}
},
cat_guid
]4.4 Metric
Structure:
defstruct name: "",
scope: "",
call_count: 0,
total_call_time: 0,
total_exclusive_time: 0,
min_call_time: 0,
max_call_time: 0,
sum_of_squares: 0Metric Types:
Datastore/statement/{datastore}/{table}/{operation}External/{host}/{component}/{method}WebTransaction/Phoenix/{controller}/{action}OtherTransaction/{category}/{name}Custom/{metric_name}
4.5 Distributed Trace Context
Structure:
defstruct source: nil,
account_id: nil,
app_id: nil,
parent_id: nil,
span_guid: nil,
trace_id: nil,
priority: nil,
sampled: nil,
timestamp: nil,
trust_key: nil,
type: nil5. Supported Frameworks & Libraries
5.1 Web Frameworks
| Framework | Support Level | Events Monitored | Data Collected |
|---|---|---|---|
| Plug | Full | Request lifecycle | Request/response metadata, timing, errors |
| Phoenix | Full | Router dispatch, controller render | Controller/action, templates, views |
| Phoenix LiveView | Full | Mount, params, events, render | Lifecycle events, params, WebSocket transactions |
HTTP Servers:
- Cowboy (via telemetry)
- Bandit (via telemetry)
5.2 Databases
| Database | Library | Support Level | Events Monitored | Data Collected |
|---|---|---|---|---|
| PostgreSQL | Ecto | Full | Query execution | SQL, timing, connection info |
| MySQL | Ecto | Full | Query execution | SQL, timing, connection info |
| MSSQL | Ecto | Full | Query execution | SQL, timing, connection info |
| Redis | Redix | Full | Pipeline/commands | Commands, timing, connection |
5.3 Background Jobs
| Library | Support Level | Events Monitored | Data Collected |
|---|---|---|---|
| Oban | Full | Job lifecycle | Worker, queue, args, timing, results |
5.4 HTTP Clients
| Library | Support Level | Events Monitored | Data Collected |
|---|---|---|---|
| Finch | Full | Request lifecycle | URL, method, status, timing |
5.5 GraphQL
| Library | Support Level | Events Monitored | Data Collected |
|---|---|---|---|
| Absinthe | Full | Operations, field resolution | Queries, operations, resolvers |
5.6 Automatic Instrumentation Summary
Zero-configuration instrumentation available for:
- Plug pipelines (Cowboy, Bandit)
- Phoenix controllers and templates
- Phoenix LiveView lifecycle
- Ecto database queries
- Redix Redis operations
- Oban background jobs
- Finch HTTP requests
- Absinthe GraphQL operations
All instrumentation can be disabled via configuration flags.
6. Manual Instrumentation
6.1 Function Tracing with @trace
File: lib/new_relic/tracer.ex
Basic Usage:
defmodule MyModule do
use NewRelic.Tracer
@trace :my_function
def my_function do
# Will report as "MyModule.my_function/0"
end
@trace :custom_name
def another_function(arg1, arg2) do
# Will report as "MyModule.another_function:custom_name/2"
end
endDisable Argument Collection:
@trace {:login, args: false}
def login(username, password) do
# Arguments won't be collected
endExternal Service Tracing:
@trace {:http_request, category: :external}
def make_request(url) do
NewRelic.set_span(:http,
url: url,
method: "GET",
component: "HTTPClient"
)
headers = NewRelic.distributed_trace_headers(:http)
HTTPClient.get(url, headers)
endHow it works:
# From tracer/macro.ex
# Tracing wraps function with:
# 1. Span creation
# 2. Timing measurement
# 3. Error catching
# 4. Metric reporting
# 5. Span event generationLimitations:
- Cannot trace recursive functions (not tail-call optimized)
- Cannot trace functions with multiple clause syntaxes (rescue, catch, etc.)
- Cannot trace function heads
6.2 Direct Span API
File: lib/new_relic/tracer/direct.ex
id = make_ref()
NewRelic.Tracer.Direct.start_span(id, "MySpan",
attributes: [user_id: "123"]
)
# Do work...
NewRelic.Tracer.Direct.stop_span(id)With timing:
NewRelic.Tracer.Direct.start_span(id, "MySpan",
system_time: System.system_time(),
attributes: [...]
)
NewRelic.Tracer.Direct.stop_span(id,
duration: duration_in_native_time
)7. Configuration & Feature Toggles
7.1 Required Configuration
# config.exs
config :new_relic_agent,
app_name: "MyApp",
license_key: "abc123"Environment Variables:
NEW_RELIC_APP_NAME=MyApp
NEW_RELIC_LICENSE_KEY=abc123
7.2 Feature Flags
All features default to enabled but can be toggled:
Security/Privacy:
config :new_relic_agent,
error_collector_enabled: false, # Disable error collection
query_collection_enabled: false, # Disable SQL/query collection
function_argument_collection_enabled: false # Disable arg collectionInstrumentation (opt-out):
config :new_relic_agent,
plug_instrumentation_enabled: false,
phoenix_instrumentation_enabled: false,
phoenix_live_view_instrumentation_enabled: false,
ecto_instrumentation_enabled: false,
redix_instrumentation_enabled: false,
oban_instrumentation_enabled: false,
finch_instrumentation_enabled: false,
absinthe_instrumentation_enabled: falsePerformance:
config :new_relic_agent,
distributed_tracing_enabled: true, # DT protocol support
request_queuing_metrics_enabled: true, # Request queue time
extended_attributes_enabled: true # Per-source attributes7.3 Advanced Features
Logs in Context:
config :new_relic_agent,
logs_in_context: :forwarder # or :direct or :disabledInfinite Tracing:
config :new_relic_agent,
infinite_tracing_trace_observer_host: "trace-observer.host"Automatic Attributes:
config :new_relic_agent,
automatic_attributes: [
environment: {:system, "APP_ENV"},
node_name: {Node, :self, []},
team_name: "MyTeam"
]Ignore Paths:
config :new_relic_agent,
ignore_paths: [
"/health",
~r/longpoll/
]Harvest Limits:
config :new_relic_agent,
analytic_event_per_minute: 1000,
custom_event_per_minute: 1000,
error_event_per_minute: 100,
span_event_per_minute: 10008. Data Collection & Reporting
8.1 Data Types Collected
Transaction Data:
- Transaction Events - Aggregated transaction information
- Transaction Traces - Detailed segment breakdowns
- Transaction Metrics - Time-series metric data
Distributed Trace Data:
- Span Events - Individual span information with parent/child relationships
- Trace Context - W3C trace propagation data
Error Data:
- Error Traces - Full error details with stack traces
- Error Events - Error occurrence metadata
Metric Data:
- Datastore Metrics - Database operation performance
- External Metrics - HTTP call performance
- Custom Metrics - User-defined metrics
- Dimensional Metrics - Tagged metric data
Sample Data:
- Process Samples - Memory, reductions, message queue
- BEAM Samples - VM-level metrics
- ETS Samples - ETS table statistics
8.2 Harvesting & Reporting
Harvest Cycle:
- Default: 60 seconds
- Configurable per data type
- Rate-limited based on configuration
Data Endpoints:
# Collector API endpoints
/agent_listener/invoke_raw_method
- Transaction events
- Transaction traces
- Error traces
- Metrics
- Span events
# Telemetry SDK endpoints
/trace/v1 - Infinite Tracing spans
/log/v1 - Log forwarding
/metric/v1 - Dimensional metrics8.3 Attribute Flattening
Nested Maps:
%{user: %{name: "Alice", age: 30}}
# Becomes:
%{
"user.name" => "Alice",
"user.age" => 30,
"user.size" => 2
}Lists:
["a", "b", "c"]
# Becomes:
%{
"list.0" => "a",
"list.1" => "b",
"list.2" => "c",
"list.length" => 3
}Truncation:
- Maps/lists truncated at 10 items
- Prevents attribute explosion
8.4 Process Tracking
Transaction Sidecar:
- Each transaction tracked via separate sidecar process
- Stores attributes, metrics, spans
- Survives process crashes within transaction
- Completed on transaction end
Span Hierarchy:
- Process dictionary stores current span
- Parent-child relationships via references
- Duration accounting (exclusive vs total time)
Code Reference:
# From distributed_trace.ex
def set_current_span(label: label, ref: ref) do
current = {label, ref}
previous_span = Process.delete(:nr_current_span)
previous_span_attrs = Process.delete(:nr_current_span_attrs)
Process.put(:nr_current_span, current)
{current, previous_span, previous_span_attrs}
end9. Key Implementation Patterns
9.1 Telemetry Event Handling
Pattern:
# 1. GenServer with handler attachment
def init(%{enabled?: true} = config) do
:telemetry.attach_many(
config.handler_id,
@events,
&__MODULE__.handle_event/4,
config
)
{:ok, config}
end
# 2. Event handling
def handle_event([:my, :event, :start], measurements, metadata, config) do
# Start tracking
end
def handle_event([:my, :event, :stop], measurements, metadata, config) do
# Report data
end9.2 Distributed Trace Propagation
Incoming:
# Extract from headers
w3c_headers(headers) || newrelic_header(headers) || :no_payload
# Set context
determine_context(headers)
|> track_transaction(transport_type: "HTTP")Outgoing:
# Generate headers
context = get_tracing_context()
nr_header = NewRelicContext.generate(context)
{traceparent, tracestate} = W3CTraceContext.generate(context)
[
{"newrelic", nr_header},
{"traceparent", traceparent},
{"tracestate", tracestate}
]9.3 Error Handling in Traces
Pattern:
try do
do_work()
rescue
exception ->
message = NewRelic.Util.Error.format_reason(:error, exception)
NewRelic.DistributedTrace.set_span(:error, message: message)
reraise exception, __STACKTRACE__
catch
:exit, value ->
message = NewRelic.Util.Error.format_reason(:exit, value)
NewRelic.DistributedTrace.set_span(:error, message: "(EXIT) #{message}")
exit(value)
end10. Architecture Insights
10.1 Supervision Tree
NewRelic.Application
├── NewRelic.Harvest.Supervisor
│ ├── Collector.Supervisor (APM data)
│ └── TelemetrySdk.Supervisor (Spans, Logs, Metrics)
├── NewRelic.Telemetry.Supervisor
│ ├── Plug
│ ├── Phoenix
│ ├── PhoenixLiveView
│ ├── Ecto.Supervisor
│ ├── Redix
│ ├── Oban
│ ├── Finch
│ └── Absinthe
├── NewRelic.Transaction.Supervisor
└── NewRelic.Sampler.Supervisor10.2 Data Flow
Application Code
↓
Telemetry Events / @trace / Public API
↓
Transaction.Reporter / DistributedTrace
↓
Transaction.Sidecar (process storage)
↓
Transaction.Complete (on end)
↓
Harvesters (buffering)
↓
HTTP API (New Relic backend)10.3 Key Design Decisions
- Telemetry-based: Non-invasive instrumentation via telemetry events
- Sidecar pattern: Transaction data stored in separate process for crash safety
- Process dictionary: Current span tracking via process dictionary (fast, local)
- Persistent term: Configuration stored in persistent_term (fast reads)
- Macro-based tracing: Compile-time function wrapping for low overhead
- Backoff sampling: Adaptive sampling to prevent data overload
11. Local-First Implementation Considerations
11.1 Data to Capture
Essential:
- Transaction events with attributes
- Span events with parent/child relationships
- Error traces with stack traces
- Metric aggregations
Optional but valuable:
- Transaction traces (detailed breakdown)
- Process samples
- Custom events
- Dimensional metrics
11.2 Storage Strategy
Time-series database:
- Transaction metrics (duration, throughput, error rate)
- Datastore metrics (query performance)
- External metrics (HTTP call performance)
Document/Event store:
- Span events (with trace/parent/child IDs)
- Error traces
- Transaction events
- Custom events
Relationships:
- Trace ID → Spans (one-to-many)
- Transaction → Spans (one-to-many)
- Span → Parent Span (tree structure)
11.3 UI/Visualization Needs
Transaction view:
- List of transactions by name
- Throughput, avg duration, error rate
- Drill-down to individual traces
Trace detail:
- Waterfall/Gantt chart of spans
- Span details (category, attributes)
- Error highlighting
Service map:
- External service calls
- Database dependencies
- GraphQL operations
Metrics dashboard:
- Time-series graphs
- Percentiles (p50, p95, p99)
- Error rate trends
11.4 Required Adaptations
Remove New Relic backend dependencies:
- Replace harvest/reporting with local storage
- Remove license key requirements
- Disable agent communication
Keep telemetry handlers:
- All instrumentation works without backend
- Only change where data is sent
Local storage harvesters:
- Replace HTTP POST with database writes
- Maintain same data structures
- Respect same sampling/limits
Query API:
- Build query layer for stored data
- Support filtering by attributes
- Time-range queries
- Aggregations
12. Code Examples from Source
12.1 Transaction Lifecycle
# From telemetry/plug.ex
def handle_event([server, :request, :start], measurements, meta, _config) do
with :collect <- Transaction.Reporter.start_transaction(:web, path(meta, server)) do
headers = get_headers(meta, server)
DistributedTrace.start(:http, headers)
add_start_attrs(meta, measurements, headers, server)
maybe_report_queueing(headers)
end
end
def handle_event([server, :request, :stop], meas, meta, _config) do
add_stop_attrs(meas, meta, server)
Transaction.Reporter.stop_transaction(:web)
end12.2 Span Creation
# From tracer/direct.ex
def start_span(id, name, opts \\ []) do
attributes = Keyword.get(opts, :attributes, [])
system_time = Keyword.get(opts, :system_time, System.system_time())
{span, previous_span, previous_span_attrs} =
NewRelic.DistributedTrace.set_current_span(label: name, ref: id)
Process.put({:nr_span_start, id}, {span, system_time, previous_span, previous_span_attrs})
attributes
|> Map.new()
|> NewRelic.DistributedTrace.add_span_attributes()
end12.3 Metric Reporting
# From telemetry/ecto/handler.ex
NewRelic.report_metric(
{:datastore, datastore, table, operation},
duration_s: duration_s
)
NewRelic.Transaction.Reporter.track_metric({
{:datastore, datastore, table, operation},
duration_s: duration_s
})
NewRelic.incr_attributes(
databaseCallCount: 1,
databaseDuration: duration_s
)12.4 Error Capture
# From telemetry/plug.ex
def handle_event([server, :request, :exception], meas, meta, _config) do
add_stop_attrs(meas, meta, server)
if NewRelic.Config.feature?(:error_collector) do
{reason, stacktrace} = reason_and_stacktrace(meta)
Transaction.Reporter.error(%{
kind: meta.kind,
reason: reason,
stack: stacktrace
})
end
Transaction.Reporter.stop_transaction(:web)
endSummary
The New Relic Elixir Agent is a comprehensive observability solution with:
Strengths:
- Zero-config auto-instrumentation for major Elixir libraries
- Deep integration with BEAM/OTP telemetry
- Distributed tracing with W3C standard support
- Rich attribute collection and custom events
- Minimal performance overhead
Architecture:
- Telemetry event-based (non-invasive)
- Sidecar pattern for transaction safety
- Compile-time function tracing
- Configurable sampling and limits
For Local-First Implementation:
- All data structures are well-defined and serializable
- Instrumentation code is independent of backend
- Clear separation between collection and reporting
- Rich metadata makes local querying powerful
Key Files to Study:
/lib/new_relic.ex- Public API surface/lib/new_relic/telemetry/*.ex- Auto-instrumentation handlers/lib/new_relic/distributed_trace.ex- Trace context management/lib/new_relic/transaction/reporter.ex- Transaction lifecycle/lib/new_relic/span/event.ex- Span data structure/lib/new_relic/error/trace.ex- Error data structure
This analysis provides a complete reference for understanding how New Relic instruments Elixir applications and what data it collects, enabling the design of a compatible local-first alternative.