Class: OneApm::Agent::CrossAppMonitor

Inherits:
InboundRequestMonitor show all
Defined in:
lib/one_apm/agent/cross_app/cross_app_monitor.rb

Constant Summary collapse

ONEAPM_ID_HEADER =
'X-OneApm-ID'
ONEAPM_TXN_HEADER =
'X-OneApm-Transaction'
ONEAPM_APPDATA_HEADER =
'X-OneApm-App-Data'
ONEAPM_ID_HEADER_KEY =
'HTTP_X_ONEAPM_ID'.freeze
ONEAPM_TXN_HEADER_KEY =
'HTTP_X_ONEAPM_TRANSACTION'.freeze
OA_CONTENT_LENGTH_HEADER_KEY =
'HTTP_CONTENT_LENGTH'.freeze

Instance Attribute Summary

Attributes inherited from InboundRequestMonitor

#obfuscator

Instance Method Summary collapse

Methods inherited from InboundRequestMonitor

#deserialize_header, #initialize, #setup_obfuscator

Constructor Details

This class inherits a constructor from OneApm::Agent::InboundRequestMonitor

Instance Method Details

#build_payload(state, timings, content_length) ⇒ Object

[View source]

126
127
128
129
130
131
132
133
134
135
136
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 126

def build_payload(state, timings, content_length)
  payload = [
    OneApm::Manager.config[:cross_process_id],
    timings.transaction_name,
    timings.queue_time_in_seconds.to_f,
    timings.app_time_in_seconds.to_f,
    content_length,
    state.request_guid
  ]
  payload = obfuscator.obfuscate(OneApm::JSONWrapper.dump(payload))
end

#clear_client_cross_app_id(state) ⇒ Object

[View source]

59
60
61
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 59

def clear_client_cross_app_id(state)
  state.client_cross_app_id = nil
end

#client_referring_transaction_guid(state) ⇒ Object

[View source]

69
70
71
72
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 69

def client_referring_transaction_guid(state)
  info = state.referring_transaction_info or return nil
  return info[0]
end

#client_referring_transaction_path_hash(state) ⇒ Object

[View source]

84
85
86
87
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 84

def client_referring_transaction_path_hash(state)
  info = state.referring_transaction_info or return nil
  return info[3].is_a?(String) && info[3]
end

#client_referring_transaction_record_flag(state) ⇒ Object

[View source]

74
75
76
77
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 74

def client_referring_transaction_record_flag(state)
  info = state.referring_transaction_info or return nil
  return info[1]
end

#client_referring_transaction_trip_id(state) ⇒ Object

[View source]

79
80
81
82
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 79

def client_referring_transaction_trip_id(state)
  info = state.referring_transaction_info or return nil
  return info[2].is_a?(String) && info[2]
end

#content_length_from_request(request) ⇒ Object

[View source]

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

def content_length_from_request(request)
  request[OA_CONTENT_LENGTH_HEADER_KEY] || -1
end

#cross_app_enabled?Boolean

Returns:

  • (Boolean)
[View source]

109
110
111
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 109

def cross_app_enabled?
  OneApm::Agent::CrossAppTracing.cross_app_enabled?
end

#decoded_id(request) ⇒ Object

[View source]

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

def decoded_id(request)
  encoded_id = request[ONEAPM_ID_HEADER_KEY]
  return "" if encoded_id.nil?

  obfuscator.deobfuscate(encoded_id)
end

#hash_transaction_name(identifier) ⇒ Object

[View source]

169
170
171
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 169

def hash_transaction_name(identifier)
  Digest::MD5.digest(identifier).unpack("@12N").first & 0xffffffff
end

#insert_response_header(state, request_headers, response_headers) ⇒ Object

[View source]

89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 89

def insert_response_header(state, request_headers, response_headers)
  unless state.client_cross_app_id.nil?
    txn = state.current_transaction
    unless txn.nil?
      txn.freeze_name_and_execute_if_not_ignored do
        timings = state.timings
        content_length = content_length_from_request(request_headers)

        set_response_headers(state, response_headers, timings, content_length)
        set_metrics(state.client_cross_app_id, timings)
      end
    end
    clear_client_cross_app_id(state)
  end
end

#on_finished_configuring(events) ⇒ Object

[View source]

21
22
23
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 21

def on_finished_configuring(events)
  register_event_listeners(events)
end

#path_hash(txn_name, seed) ⇒ Object

[View source]

173
174
175
176
177
178
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 173

def path_hash(txn_name, seed)
  rotated    = ((seed << 1) | (seed >> 31)) & 0xffffffff
  app_name   = OneApm::Manager.config.app_names.first
  identifier = "#{app_name};#{txn_name}"
  sprintf("%08x", rotated ^ hash_transaction_name(identifier))
end

#register_event_listeners(events) ⇒ Object

Expected sequence of events:

:before_call will save our cross application request id to the thread
:after_call will write our response headers/metrics and clean up the thread
[View source]

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 28

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

  events.subscribe(:before_call) do |env|
    if should_process_request(env)
      state = OneApm::TransactionState.tl_get

      save_client_cross_app_id(state, env)
      save_referring_transaction_info(state, env)
      set_transaction_custom_parameters(state)
    end
  end

  events.subscribe(:after_call) do |env, (_status_code, headers, _body)|
    state = OneApm::TransactionState.tl_get

    insert_response_header(state, env, headers)
  end

  events.subscribe(:notice_error) do |_, options|
    state = OneApm::TransactionState.tl_get

    set_error_custom_parameters(state, options)
  end
end

#save_client_cross_app_id(state, request_headers) ⇒ Object

[View source]

55
56
57
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 55

def save_client_cross_app_id(state, request_headers)
  state.client_cross_app_id = decoded_id(request_headers)
end

#save_referring_transaction_info(state, request_headers) ⇒ Object

[View source]

63
64
65
66
67
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 63

def save_referring_transaction_info(state, request_headers)
  txn_header = request_headers[ONEAPM_TXN_HEADER_KEY] or return
  txn_info = deserialize_header(txn_header, ONEAPM_TXN_HEADER)
  state.referring_transaction_info = txn_info
end

#set_error_custom_parameters(state, options) ⇒ Object

[View source]

149
150
151
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 149

def set_error_custom_parameters(state, options)
  options[:client_cross_process_id] = state.client_cross_app_id if state.client_cross_app_id
end

#set_metrics(id, timings) ⇒ Object

[View source]

153
154
155
156
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 153

def set_metrics(id, timings)
  metric_name = "ClientApplication/#{id}/all"
  OneApm::Manager.record_metric(metric_name, timings.app_time_in_seconds)
end

#set_response_headers(state, response_headers, timings, content_length) ⇒ Object

[View source]

122
123
124
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 122

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

#set_transaction_custom_parameters(state) ⇒ Object

[View source]

138
139
140
141
142
143
144
145
146
147
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 138

def set_transaction_custom_parameters(state)
  # 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
  OneApm::Manager.add_custom_parameters(:client_cross_process_id => state.client_cross_app_id) if state.client_cross_app_id

  referring_guid = client_referring_transaction_guid(state)
  if referring_guid
    OneApm::Manager.add_custom_parameters(:referring_transaction_guid => referring_guid)
  end
end

#should_process_request(request_headers) ⇒ Object

[View source]

105
106
107
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 105

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

#trusts?(request) ⇒ Boolean

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

Returns:

  • (Boolean)
[View source]

114
115
116
117
118
119
120
# File 'lib/one_apm/agent/cross_app/cross_app_monitor.rb', line 114

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

  OneApm::Manager.config[:trusted_account_ids].include?(split_id.captures.first.to_i)
end