Skip to content

Commit

Permalink
Merge branch 'dev' into elasticsearch_try_cluster_once_per_instance
Browse files Browse the repository at this point in the history
  • Loading branch information
tannalynn authored Jul 11, 2024
2 parents 1e93a5f + 49c220a commit 0f80c06
Show file tree
Hide file tree
Showing 30 changed files with 798 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
"rails": "norails,rails42,rails52"
},
"3.3.4": {
"rails": "norails,rails61,rails71"
"rails": "norails,rails61,rails72"
}
}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci_cron.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,16 @@ jobs:
"rails": "norails,rails61,rails60,rails70,rails71"
},
"3.1.6": {
"rails": "norails,rails61,rails70,rails71"
"rails": "norails,rails61,rails70,rails71,rails72"
},
"3.2.4": {
"rails": "norails,rails61,rails70,rails71"
"rails": "norails,rails61,rails70,rails71,rails72"
},
"3.3.4": {
"rails": "norails,rails61,rails70,rails71"
"rails": "norails,rails61,rails70,rails71,rails72"
},
"3.4.0-preview1": {
"rails": "norails,rails61,rails70,rails71"
"rails": "norails,rails61,rails70,rails71,rails72"
}
}
Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# New Relic Ruby Agent Release Notes

## <dev>
Version <dev> introduces instrumentation for the LogStasher gem, improves instrumentation for the `redis-clustering` gem, and updates the elasticsearch instrumentation to only attempt to get the cluster name once per client, even if it fails.

Version <dev> improves instrumentation for the `redis-clustering` gem, and updates the elasticsearch instrumentation to only attempt to get the cluster name once per client, even if it fails.
- **Feature: Add instrumentation for LogStasher**

The agent will now record logs generated by [LogStasher](https://github.com/shadabahmed/logstasher). Versions 1.0.0 and above of the LogStasher gem are supported. [PR#2559](https://github.com/newrelic/newrelic-ruby-agent/pull/2559)

- **Feature: Add instrumentation for redis-clustering**

Expand Down
9 changes: 9 additions & 0 deletions lib/new_relic/agent/configuration/default_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1636,6 +1636,15 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil)
:allowed_from_server => false,
:description => 'Controls auto-instrumentation of Ruby standard library Logger at start-up. May be one of: `auto`, `prepend`, `chain`, `disabled`.'
},
:'instrumentation.logstasher' => {
:default => instrumentation_value_from_boolean(:'application_logging.enabled'),
:documentation_default => 'auto',
:public => true,
:type => String,
:dynamic_name => true,
:allowed_from_server => false,
:description => 'Controls auto-instrumentation of the LogStasher library at start-up. May be one of [auto|prepend|chain|disabled].'
},
:'instrumentation.memcache' => {
:default => 'auto',
:documentation_default => 'auto',
Expand Down
14 changes: 14 additions & 0 deletions lib/new_relic/agent/instrumentation/bunny/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ def publish_with_tracing(payload, opts = {})
correlation_id: opts[:correlation_id],
exchange_type: type
)
if segment
segment.add_agent_attribute('server.address', channel&.connection&.hostname)
segment.add_agent_attribute('server.port', channel&.connection&.port)
segment.add_agent_attribute('messaging.destination.name', destination) # for produce, this is exchange name
segment.add_agent_attribute('messaging.rabbitmq.destination.routing_key', opts[:routing_key])
end
rescue => e
NewRelic::Agent.logger.error('Error starting message broker segment in Bunny::Exchange#publish', e)
yield
Expand Down Expand Up @@ -94,6 +100,14 @@ def pop_with_tracing
queue_name: name,
start_time: t0
)
if segment
segment.add_agent_attribute('server.address', channel&.connection&.hostname)
segment.add_agent_attribute('server.port', channel&.connection&.port)
segment.add_agent_attribute('messaging.destination.name', name) # for consume, this is queue name
segment.add_agent_attribute('messaging.destination_publish.name', exch_name)
segment.add_agent_attribute('message.queueName', name)
segment.add_agent_attribute('messaging.rabbitmq.destination.routing_key', delivery_info&.routing_key)
end
rescue => e
NewRelic::Agent.logger.error('Error starting message broker segment in Bunny::Queue#pop', e)
else
Expand Down
27 changes: 27 additions & 0 deletions lib/new_relic/agent/instrumentation/logstasher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

require_relative 'logstasher/instrumentation'
require_relative 'logstasher/chain'
require_relative 'logstasher/prepend'

DependencyDetection.defer do
named :logstasher

depends_on do
defined?(LogStasher) &&
Gem::Version.new(LogStasher::VERSION) >= Gem::Version.new('1.0.0') &&
NewRelic::Agent.config[:'application_logging.enabled']
end

executes do
NewRelic::Agent.logger.info('Installing LogStasher instrumentation')

if use_prepend?
prepend_instrument LogStasher.singleton_class, NewRelic::Agent::Instrumentation::LogStasher::Prepend
else
chain_instrument NewRelic::Agent::Instrumentation::LogStasher::Chain
end
end
end
21 changes: 21 additions & 0 deletions lib/new_relic/agent/instrumentation/logstasher/chain.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

module NewRelic::Agent::Instrumentation
module LogStasher::Chain
def self.instrument!
::LogStasher.singleton_class.class_eval do
include NewRelic::Agent::Instrumentation::LogStasher

alias_method(:build_logstash_event_without_new_relic, :build_logstash_event)

def build_logstash_event(*args)
build_logstash_event_with_new_relic(*args) do
build_logstash_event_without_new_relic(*args)
end
end
end
end
end
end
24 changes: 24 additions & 0 deletions lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

module NewRelic::Agent::Instrumentation
module LogStasher
INSTRUMENTATION_NAME = NewRelic::Agent.base_name(name)

def self.enabled?
NewRelic::Agent.config[:'instrumentation.logstasher'] != 'disabled'
end

def build_logstash_event_with_new_relic(*args)
logstasher_event = yield
log = logstasher_event.instance_variable_get(:@data)

::NewRelic::Agent.record_instrumentation_invocation(INSTRUMENTATION_NAME)
::NewRelic::Agent.agent.log_event_aggregator.record_logstasher_event(log)
::NewRelic::Agent::LocalLogDecorator.decorate(log)

logstasher_event
end
end
end
13 changes: 13 additions & 0 deletions lib/new_relic/agent/instrumentation/logstasher/prepend.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

module NewRelic::Agent::Instrumentation
module LogStasher::Prepend
include NewRelic::Agent::Instrumentation::LogStasher

def build_logstash_event(*args)
build_logstash_event_with_new_relic(*args) { super }
end
end
end
9 changes: 8 additions & 1 deletion lib/new_relic/agent/local_log_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ def decorate(message)
return message unless decorating_enabled?

metadata = NewRelic::Agent.linking_metadata

if message.is_a?(Hash)
message.merge!(metadata) unless message.frozen?
return
end

formatted_metadata = " NR-LINKING|#{metadata[ENTITY_GUID_KEY]}|#{metadata[HOSTNAME_KEY]}|" \
"#{metadata[TRACE_ID_KEY]}|#{metadata[SPAN_ID_KEY]}|" \
"#{escape_entity_name(metadata[ENTITY_NAME_KEY])}|"
Expand All @@ -23,7 +29,8 @@ def decorate(message)

def decorating_enabled?
NewRelic::Agent.config[:'application_logging.enabled'] &&
NewRelic::Agent::Instrumentation::Logger.enabled? &&
(NewRelic::Agent::Instrumentation::Logger.enabled? ||
NewRelic::Agent::Instrumentation::LogStasher.enabled?) &&
NewRelic::Agent.config[:'application_logging.local_decorating.enabled']
end

Expand Down
117 changes: 91 additions & 26 deletions lib/new_relic/agent/log_event_aggregator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class LogEventAggregator < EventAggregator
DROPPED_METRIC = 'Logging/Forwarding/Dropped'.freeze
SEEN_METRIC = 'Supportability/Logging/Forwarding/Seen'.freeze
SENT_METRIC = 'Supportability/Logging/Forwarding/Sent'.freeze
OVERALL_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Ruby/Logger/%s'.freeze
LOGGER_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Ruby/Logger/%s'.freeze
LOGSTASHER_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Ruby/LogStasher/%s'.freeze
METRICS_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Metrics/Ruby/%s'.freeze
FORWARDING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Forwarding/Ruby/%s'.freeze
DECORATING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/LocalDecorating/Ruby/%s'.freeze
Expand Down Expand Up @@ -58,38 +59,71 @@ def capacity
end

def record(formatted_message, severity)
return unless enabled?
return unless logger_enabled?

severity = 'UNKNOWN' if severity.nil? || severity.empty?
increment_event_counters(severity)

return if formatted_message.nil? || formatted_message.empty?
return unless monitoring_conditions_met?(severity)

txn = NewRelic::Agent::Transaction.tl_current
priority = LogPriority.priority_for(txn)

if NewRelic::Agent.config[METRICS_ENABLED_KEY]
@counter_lock.synchronize do
@seen += 1
@seen_by_severity[severity] += 1
return txn.add_log_event(create_event(priority, formatted_message, severity)) if txn

@lock.synchronize do
@buffer.append(priority: priority) do
create_event(priority, formatted_message, severity)
end
end
rescue
nil
end

return if severity_too_low?(severity)
return if formatted_message.nil? || formatted_message.empty?
return unless NewRelic::Agent.config[FORWARDING_ENABLED_KEY]
return if @high_security
def record_logstasher_event(log)
return unless logstasher_enabled?

# LogStasher logs do not inherently include a message key, so most logs are recorded.
# But when the key exists, we should not record the log if the message value is nil or empty.
return if log.key?('message') && (log['message'].nil? || log['message'].empty?)

severity = determine_severity(log)
increment_event_counters(severity)

return unless monitoring_conditions_met?(severity)

txn = NewRelic::Agent::Transaction.tl_current
priority = LogPriority.priority_for(txn)

if txn
return txn.add_log_event(create_event(priority, formatted_message, severity))
else
return @lock.synchronize do
@buffer.append(priority: priority) do
create_event(priority, formatted_message, severity)
end
return txn.add_log_event(create_logstasher_event(priority, severity, log)) if txn

@lock.synchronize do
@buffer.append(priority: priority) do
create_logstasher_event(priority, severity, log)
end
end
rescue
nil
end

def monitoring_conditions_met?(severity)
!severity_too_low?(severity) && NewRelic::Agent.config[FORWARDING_ENABLED_KEY] && !@high_security
end

def determine_severity(log)
log['level'] ? log['level'].to_s.upcase : 'UNKNOWN'
end

def increment_event_counters(severity)
return unless NewRelic::Agent.config[METRICS_ENABLED_KEY]

@counter_lock.synchronize do
@seen += 1
@seen_by_severity[severity] += 1
end
end

def record_batch(txn, logs)
# Ensure we have the same shared priority
priority = LogPriority.priority_for(txn)
Expand All @@ -104,15 +138,17 @@ def record_batch(txn, logs)
end
end

def create_event(priority, formatted_message, severity)
formatted_message = truncate_message(formatted_message)

event = LinkingMetadata.append_trace_linking_metadata({
def add_event_metadata(formatted_message, severity)
metadata = {
LEVEL_KEY => severity,
MESSAGE_KEY => formatted_message,
TIMESTAMP_KEY => Process.clock_gettime(Process::CLOCK_REALTIME) * 1000
})
}
metadata[MESSAGE_KEY] = formatted_message unless formatted_message.nil?

LinkingMetadata.append_trace_linking_metadata(metadata)
end

def create_prioritized_event(priority, event)
[
{
PrioritySampledBuffer::PRIORITY_KEY => priority
Expand All @@ -121,6 +157,31 @@ def create_event(priority, formatted_message, severity)
]
end

def create_event(priority, formatted_message, severity)
formatted_message = truncate_message(formatted_message)
event = add_event_metadata(formatted_message, severity)

create_prioritized_event(priority, event)
end

def create_logstasher_event(priority, severity, log)
formatted_message = log['message'] ? truncate_message(log['message']) : nil
event = add_event_metadata(formatted_message, severity)
add_logstasher_event_attributes(event, log)

create_prioritized_event(priority, event)
end

def add_logstasher_event_attributes(event, log)
log_copy = log.dup
# Delete previously reported attributes
log_copy.delete('message')
log_copy.delete('level')
log_copy.delete('@timestamp')

event['attributes'] = log_copy
end

def add_custom_attributes(custom_attributes)
attributes.add_custom_attributes(custom_attributes)
end
Expand Down Expand Up @@ -166,19 +227,23 @@ def reset!
super
end

def enabled?
def logger_enabled?
@enabled && @instrumentation_logger_enabled
end

def logstasher_enabled?
@enabled && NewRelic::Agent::Instrumentation::LogStasher.enabled?
end

private

# We record once-per-connect metrics for enabled/disabled state at the
# point we consider the configuration stable (i.e. once we've gotten SSC)
def register_for_done_configuring(events)
events.subscribe(:server_source_configuration_added) do
@high_security = NewRelic::Agent.config[:high_security]

record_configuration_metric(OVERALL_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
record_configuration_metric(LOGGER_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
record_configuration_metric(LOGSTASHER_SUPPORTABILITY_FORMAT, OVERALL_ENABLED_KEY)
record_configuration_metric(METRICS_SUPPORTABILITY_FORMAT, METRICS_ENABLED_KEY)
record_configuration_metric(FORWARDING_SUPPORTABILITY_FORMAT, FORWARDING_ENABLED_KEY)
record_configuration_metric(DECORATING_SUPPORTABILITY_FORMAT, DECORATING_ENABLED_KEY)
Expand Down
Loading

0 comments on commit 0f80c06

Please sign in to comment.