Class: NewRelic::Agent::CrossAppMonitor

Inherits:
Object
  • Object
show all
Includes:
EncodingFunctions
Defined in:
lib/new_relic/agent/cross_app_monitor.rb

Defined Under Namespace

Modules: EncodingFunctions

Constant Summary collapse

NEWRELIC_ID_HEADER =
'X-NewRelic-ID'
NEWRELIC_APPDATA_HEADER =
'X-NewRelic-App-Data'
NEWRELIC_TXN_HEADER =
'X-NewRelic-Transaction'
NEWRELIC_TXN_HEADER_KEYS =
%W{
  #{NEWRELIC_TXN_HEADER} HTTP_X_NEWRELIC_TRANSACTION X_NEWRELIC_TRANSACTION
}
NEWRELIC_ID_HEADER_KEYS =
%W{
  #{NEWRELIC_ID_HEADER} HTTP_X_NEWRELIC_ID X_NEWRELIC_ID
}
CONTENT_LENGTH_HEADER_KEYS =
%w{Content-Length HTTP_CONTENT_LENGTH CONTENT_LENGTH}
THREAD_ID_KEY =

Because we aren’t in the right spot when our transaction actually starts, hold client_cross_app_id we get thread local until then.

:newrelic_client_cross_app_id
THREAD_TXN_KEY =

Same for the referring transaction guid

:newrelic_cross_app_referring_txn_info

Instance Method Summary collapse

Methods included from EncodingFunctions

decode_with_key, encode_with_key, obfuscate_with_key

Constructor Details

#initialize(events = nil) ⇒ CrossAppMonitor

Returns a new instance of CrossAppMonitor.



58
59
60
61
62
63
64
65
66
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 58

def initialize(events = nil)
  # When we're starting up for real in the agent, we get passed the events
  # Other spots can pull from the agent, during startup the agent doesn't exist yet!
  events ||= Agent.instance.events

  events.subscribe(:finished_configuring) do
    register_event_listeners
  end
end

Instance Method Details

#build_payload(timings, content_length) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 169

def build_payload(timings, content_length)

  # FIXME The transaction name might not be properly encoded.  use a json generator
  # For now we just handle quote characters by dropping them
  transaction_name = timings.transaction_name.gsub(/["']/, "")

  payload = [
    NewRelic::Agent.config[:cross_process_id],
    transaction_name,
    timings.queue_time_in_seconds.to_f,
    timings.app_time_in_seconds.to_f,
    content_length,
    transaction_guid()
  ]
  key = NewRelic::Agent.config[:encoding_key]
  payload = obfuscate_with_key( key, NewRelic.json_dump(payload) )
end

#clear_client_cross_app_idObject



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

def clear_client_cross_app_id
  NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = nil
end

#clear_referring_transaction_infoObject



120
121
122
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 120

def clear_referring_transaction_info
  NewRelic::Agent::AgentThread.current[THREAD_TXN_KEY] = nil
end

#client_cross_app_idObject



106
107
108
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 106

def client_cross_app_id
  NewRelic::Agent::AgentThread.current[THREAD_ID_KEY]
end

#client_referring_transaction_guidObject



124
125
126
127
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 124

def client_referring_transaction_guid
  info = NewRelic::Agent::AgentThread.current[THREAD_TXN_KEY] or return nil
  return info[0]
end

#client_referring_transaction_record_flagObject



129
130
131
132
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 129

def client_referring_transaction_record_flag
  info = NewRelic::Agent::AgentThread.current[THREAD_TXN_KEY] or return nil
  return info[1]
end

#content_length_from_request(request) ⇒ Object



215
216
217
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 215

def content_length_from_request(request)
  from_headers(request, CONTENT_LENGTH_HEADER_KEYS) || -1
end

#cross_app_enabled?Boolean

Returns:

  • (Boolean)


150
151
152
153
154
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 150

def cross_app_enabled?
  NewRelic::Agent.config[:cross_process_id] &&
    (NewRelic::Agent.config[:"cross_application_tracer.enabled"] || 
     NewRelic::Agent.config[:cross_application_tracing])
end

#decoded_id(request) ⇒ Object



207
208
209
210
211
212
213
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 207

def decoded_id(request)
  encoded_id = from_headers(request, NEWRELIC_ID_HEADER_KEYS)
  return "" if encoded_id.nil?

  key = NewRelic::Agent.config[:encoding_key]
  decode_with_key( key, encoded_id )
end

#insert_response_header(request_headers, response_headers) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 134

def insert_response_header(request_headers, response_headers)
  unless client_cross_app_id.nil?
    timings = NewRelic::Agent::BrowserMonitoring.timings
    content_length = content_length_from_request(request_headers)

    set_response_headers(response_headers, timings, content_length)
    set_metrics(client_cross_app_id, timings)

    clear_client_cross_app_id
  end
end

#register_event_listenersObject

Expected sequence of events:

:before_call will save our cross application request id to the thread
:start_transaction will get called when a transaction starts up
:after_call will write our response headers/metrics and clean up the thread


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 73

def register_event_listeners
  NewRelic::Agent.logger.
    debug("Wiring up Cross Application Tracing to events after finished configuring")

  events = Agent.instance.events
  events.subscribe(:before_call) do |env|
    if should_process_request(env)
      save_client_cross_app_id(env)
      save_referring_transaction_info(env)
    end
  end

  events.subscribe(:start_transaction) do |name|
    set_transaction_custom_parameters
  end

  events.subscribe(:after_call) do |env, (status_code, headers, body)|
    insert_response_header(env, headers)
  end

  events.subscribe(:notice_error) do |_, options|
    set_error_custom_parameters(options)
  end
end

#save_client_cross_app_id(request_headers) ⇒ Object



98
99
100
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 98

def save_client_cross_app_id(request_headers)
  NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = decoded_id(request_headers)
end

#save_referring_transaction_info(request_headers) ⇒ Object



110
111
112
113
114
115
116
117
118
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 110

def save_referring_transaction_info(request_headers)
  key = NewRelic::Agent.config[:encoding_key]
  txn_header = from_headers( request_headers, NEWRELIC_TXN_HEADER_KEYS ) or return
  txn_header = decode_with_key( key, txn_header )
  txn_info = NewRelic.json_load( txn_header )
  NewRelic::Agent.logger.debug "Referring txn_info: %p" % [ txn_info ]

  NewRelic::Agent::AgentThread.current[THREAD_TXN_KEY] = txn_info
end

#set_error_custom_parameters(options) ⇒ Object



197
198
199
200
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 197

def set_error_custom_parameters(options)
  options[:client_cross_process_id] = client_cross_app_id() if client_cross_app_id()
  # [MG] TODO: Should the CAT metrics be set here too?
end

#set_metrics(id, timings) ⇒ Object



202
203
204
205
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 202

def set_metrics(id, timings)
  metric = NewRelic::Agent.instance.stats_engine.get_stats_no_scope("ClientApplication/#{id}/all")
  metric.record_data_point(timings.app_time_in_seconds)
end

#set_response_headers(response_headers, timings, content_length) ⇒ Object



165
166
167
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 165

def set_response_headers(response_headers, timings, content_length)
  response_headers[NEWRELIC_APPDATA_HEADER] = build_payload(timings, content_length)
end

#set_transaction_custom_parametersObject



187
188
189
190
191
192
193
194
195
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 187

def set_transaction_custom_parameters
  # We expect to get the before call to set the id (if we have it) before
  # this, and then write our custom parameter when the transaction starts
  NewRelic::Agent.add_custom_parameters(:client_cross_process_id => client_cross_app_id()) if client_cross_app_id()
  NewRelic::Agent.add_custom_parameters(:referring_transaction_guid => client_referring_transaction_guid()) if
    client_referring_transaction_guid()

  NewRelic::Agent.logger.debug "Referring transaction guid: %p" % [client_referring_transaction_guid()]
end

#should_process_request(request_headers) ⇒ Object



146
147
148
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 146

def should_process_request(request_headers)
  return cross_app_enabled? && trusts?(request_headers)
end

#transaction_guidObject



219
220
221
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 219

def transaction_guid
  NewRelic::Agent::TransactionInfo.get.guid
end

#trusts?(request) ⇒ Boolean

Expects an ID of format “12#345”, and will only accept that!

Returns:

  • (Boolean)


157
158
159
160
161
162
163
# File 'lib/new_relic/agent/cross_app_monitor.rb', line 157

def trusts?(request)
  id = decoded_id(request)
  split_id = id.match(/(\d+)#\d+/)
  return false if split_id.nil?

  NewRelic::Agent.config[:trusted_account_ids].include?(split_id.captures.first.to_i)
end