Class: NewRelic::Agent::Instrumentation::NotificationsSubscriber

Inherits:
Object
  • Object
show all
Defined in:
lib/new_relic/agent/instrumentation/notifications_subscriber.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeNotificationsSubscriber

Returns a new instance of NotificationsSubscriber.



9
10
11
12
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 9

def initialize
  @queue_key = ['NewRelic', self.class.name, object_id].join('-')
  define_exception_method
end

Class Method Details

.find_all_subscribersObject



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 18

def self.find_all_subscribers
  # TODO: need to talk to Rails core about an API for this,
  # rather than digging through Listener ivars
  instance_variable_names = [:@subscribers, :@string_subscribers, :@other_subscribers]
  all_subscribers = []

  notifier = ActiveSupport::Notifications.notifier

  instance_variable_names.each do |name|
    if notifier.instance_variable_defined?(name)
      subscribers = notifier.instance_variable_get(name)
      if subscribers.is_a?(Array)
        # Rails 5 @subscribers, and Rails 6 @other_subscribers is a
        # plain array of subscriber objects
        all_subscribers += subscribers
      elsif subscribers.is_a?(Hash)
        # Rails 6 @string_subscribers is a Hash mapping the pattern
        # string of a subscriber to an array of subscriber objects
        subscribers.values.each { |array| all_subscribers += array }
      end
    end
  end

  all_subscribers
end

.subscribe(pattern) ⇒ Object



44
45
46
47
48
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 44

def self.subscribe(pattern)
  if !subscribed?
    ActiveSupport::Notifications.subscribe(pattern, new)
  end
end

.subscribed?Boolean

Returns:

  • (Boolean)


14
15
16
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 14

def self.subscribed?
  find_all_subscribers.find { |s| s.instance_variable_get(:@delegate).class == self }
end

Instance Method Details

#add_segment_params(segment, payload) ⇒ Object

for subclasses



86
87
88
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 86

def add_segment_params(segment, payload)
  # no op
end

#define_exception_methodObject



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 119

def define_exception_method
  # we don't expect this to be called more than once, but we're being
  # defensive.
  return if defined?(exception_object)
  return unless defined?(::ActiveSupport::VERSION)

  if ::ActiveSupport::VERSION::STRING < '5.0.0'
    # Earlier versions of Rails did not add the exception itself to the
    # payload accessible via :exception_object, so we create a stand-in
    # error object from the given class name and message.
    # NOTE: no backtrace available this way, but we can notice the error
    # well enough to send the necessary info the UI requires to present it.
    def exception_object(payload)
      exception_class, message = payload[:exception]
      return nil unless exception_class

      NewRelic::Agent::NoticeableError.new(exception_class, message)
    end
  else
    def exception_object(payload)
      payload[:exception_object]
    end
  end
end

#finish(name, id, payload) ⇒ Object



62
63
64
65
66
67
68
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 62

def finish(name, id, payload)
  return unless state.is_execution_traced?

  finish_segment(id, payload)
rescue => e
  log_notification_error(e, name, 'finish')
end

#finish_segment(id, payload) ⇒ Object



76
77
78
79
80
81
82
83
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 76

def finish_segment(id, payload)
  if segment = pop_segment(id)
    if exception = exception_object(payload)
      segment.notice_error(exception)
    end
    segment.finish
  end
end

#log_notification_error(error, name, event_type) ⇒ Object



95
96
97
98
99
100
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 95

def log_notification_error(error, name, event_type)
  # These are important enough failures that we want the backtraces
  # logged at error level, hence the explicit log_exception call.
  NewRelic::Agent.logger.error("Error during #{event_type} callback for event '#{name}':")
  NewRelic::Agent.logger.log_exception(:error, error)
end

#metric_name(name, payload) ⇒ Object

for subclasses



91
92
93
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 91

def metric_name(name, payload)
  "Ruby/#{name}"
end

#pop_segment(id) ⇒ Object



106
107
108
109
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 106

def pop_segment(id)
  segment = segment_stack[id].pop
  segment
end

#push_segment(id, segment) ⇒ Object



102
103
104
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 102

def push_segment(id, segment)
  segment_stack[id].push(segment)
end

#segment_stackObject



111
112
113
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 111

def segment_stack
  Thread.current[@queue_key] ||= Hash.new { |h, id| h[id] = [] }
end

#start(name, id, payload) ⇒ Object

The agent doesn’t use the traditional ActiveSupport::Notifications.subscribe pattern due to threading issues discovered on initial instrumentation. Instead we define a #start and #finish method, which Rails responds to. See: github.com/rails/rails/issues/12069



54
55
56
57
58
59
60
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 54

def start(name, id, payload)
  return unless state.is_execution_traced?

  start_segment(name, id, payload)
rescue => e
  log_notification_error(e, name, 'start')
end

#start_segment(name, id, payload) ⇒ Object



70
71
72
73
74
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 70

def start_segment(name, id, payload)
  segment = Tracer.start_segment(name: metric_name(name, payload))
  add_segment_params(segment, payload)
  push_segment(id, segment)
end

#stateObject



115
116
117
# File 'lib/new_relic/agent/instrumentation/notifications_subscriber.rb', line 115

def state
  NewRelic::Agent::Tracer.state
end