Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logstasher instrumentation #2559

Merged
merged 56 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
867f4c1
Instrumentation skeleton
hannahramadan Apr 15, 2024
82f1f43
Merge branch 'dev' into logstasher_instrumentation
hannahramadan Jun 11, 2024
ec7de88
Merge branch 'dev' into logstasher_instrumentation
hannahramadan Jun 13, 2024
5531741
log decorator: support hashes
fallwith Jun 21, 2024
2485d79
log decorator: return early for frozen hashes
fallwith Jun 21, 2024
b1f3f0b
Merge pull request #2729 from newrelic/mark_williams
hannahramadan Jun 21, 2024
b302925
Add instrumenting methods
hannahramadan Jun 21, 2024
36f0a05
Instrumentation updates
hannahramadan Jun 24, 2024
693a991
Rubocop: spacing
hannahramadan Jun 24, 2024
d0ad2d2
Add tests
hannahramadan Jun 25, 2024
5cc4d09
Rubocop
hannahramadan Jun 25, 2024
b0816c6
Update lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb
hannahramadan Jun 25, 2024
f00a319
Test updates
hannahramadan Jun 25, 2024
acda525
test and instrumentation udpates
hannahramadan Jun 25, 2024
4827fec
test updates
hannahramadan Jun 25, 2024
032e67d
Test updates
hannahramadan Jun 27, 2024
e0b714b
Merge branch 'dev' into logstasher_instrumentation
hannahramadan Jun 27, 2024
f8542b2
Rubocop
hannahramadan Jun 27, 2024
fce3e68
Envfile update
hannahramadan Jun 27, 2024
9ccd046
require ostruct
hannahramadan Jun 27, 2024
8b88ba6
Add changelog and version gate
hannahramadan Jun 27, 2024
6ad0e5f
Test updates
hannahramadan Jun 28, 2024
c3f3089
rubocop beep beep
hannahramadan Jun 28, 2024
ba44298
More tests
hannahramadan Jun 28, 2024
072e97c
metrics enabled test
hannahramadan Jun 28, 2024
20304b0
message change
hannahramadan Jun 28, 2024
df672a7
test updates
hannahramadan Jun 28, 2024
c8a7d04
test: record nil on kaboom
hannahramadan Jun 28, 2024
df447d6
beep beep rubocop
hannahramadan Jun 28, 2024
7462d5e
Apply suggestions from code review
hannahramadan Jun 28, 2024
de96a74
Apply suggestions from code review
hannahramadan Jul 1, 2024
4fabc5b
Create shared methods
hannahramadan Jul 1, 2024
229c7d1
Monitoring conditions?
hannahramadan Jul 1, 2024
278c972
Update pin
hannahramadan Jul 1, 2024
45e6bc4
Always capitalize severity level
hannahramadan Jul 1, 2024
f689fb7
Don't send empty strings
hannahramadan Jul 1, 2024
84c64b3
Spruce up attributes test
hannahramadan Jul 1, 2024
3546a3c
Upcase severity levels
hannahramadan Jul 2, 2024
6eef9a6
Fix name
hannahramadan Jul 2, 2024
32036e5
Update linking metadata tests
hannahramadan Jul 2, 2024
559ecd4
Small cleanups
hannahramadan Jul 2, 2024
02d09b3
Use initialize value
hannahramadan Jul 3, 2024
29a78c0
Use singletone class
hannahramadan Jul 3, 2024
18ca504
revert initialize changes
hannahramadan Jul 3, 2024
1ab9aa2
remove required file
hannahramadan Jul 3, 2024
1a70059
Use *args
hannahramadan Jul 3, 2024
3168617
All keys are strings
hannahramadan Jul 3, 2024
6dc33a4
revert to_s
hannahramadan Jul 3, 2024
fecf021
up ruby version
hannahramadan Jul 3, 2024
07353c7
Trigger ci run
hannahramadan Jul 9, 2024
752c1f2
Merge branch 'dev' into logstasher_instrumentation
hannahramadan Jul 9, 2024
c8e2d8e
Update CHANGELOG.md
hannahramadan Jul 9, 2024
9e2fa2d
Add comment
hannahramadan Jul 9, 2024
015d13b
Apply suggestions from code review
hannahramadan Jul 9, 2024
068acc3
add envfile comment
hannahramadan Jul 9, 2024
cf27f6c
Use less than 7.1
hannahramadan Jul 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 and improves instrumentation for the `redis-clustering` gem.

Version <dev> improves instrumentation for the `redis-clustering` gem.
- **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
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
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]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few blocks of code were shared by create_event and create_logstasher_event. This is an example of a block of code moved into a new method (increment_event_counters is the name for this)

@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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block of code lives in a new method, monitoring_conditions_met?

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?
fallwith marked this conversation as resolved.
Show resolved Hide resolved
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
19 changes: 19 additions & 0 deletions test/multiverse/suites/logstasher/Envfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 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

instrumentation_methods :chain, :prepend

logstasher_versions = [
[nil, 2.7]
]

# Lock down activesupport version due to a logstasher test incompatiability with 7.1.
def gem_list(logstasher_versions = nil)
<<~RB
gem 'logstasher'#{logstasher_versions}
gem 'activesupport', '< 7.1'
RB
end

create_gemfiles(logstasher_versions)
22 changes: 22 additions & 0 deletions test/multiverse/suites/logstasher/config/newrelic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
development:
error_collector:
enabled: true
apdex_t: 0.5
monitor_mode: true
license_key: bootstrap_newrelic_admin_license_key_000
instrumentation:
logstasher: <%= $instrumentation_method %>
app_name: test
log_level: debug
host: 127.0.0.1
api_host: 127.0.0.1
transaction_trace:
record_sql: obfuscated
enabled: true
stack_trace_threshold: 0.5
transaction_threshold: 1.0
capture_params: false
application_logging:
forwarding:
enabled: true
Loading
Loading