def generate_request_headers(transaction): """ Return a list of NewRelic specific headers as tuples [(HEADER_NAME0, HEADER_VALUE0), (HEADER_NAME1, HEADER_VALUE1)] """ if transaction is None: return [] settings = transaction.settings nr_headers = [] if settings.cross_application_tracer.enabled: transaction.is_part_of_cat = True encoded_cross_process_id = obfuscate(settings.cross_process_id, settings.encoding_key) nr_headers.append(('X-NewRelic-ID', encoded_cross_process_id)) transaction_data = [ transaction.guid, transaction.record_tt, transaction.trip_id, transaction.path_hash ] encoded_transaction = obfuscate(json_encode(transaction_data), settings.encoding_key) nr_headers.append(('X-NewRelic-Transaction', encoded_transaction)) if transaction.synthetics_header: nr_headers.append( ('X-NewRelic-Synthetics', transaction.synthetics_header)) return nr_headers
def browser_monitoring_intrinsics(self, obfuscation_key): txn_name = obfuscate(self.path, obfuscation_key) queue_start = self.queue_start or self.start_time start_time = self.start_time end_time = time.time() queue_duration = int((start_time - queue_start) * 1000) request_duration = int((end_time - start_time) * 1000) intrinsics = { "beacon": self._settings.beacon, "errorBeacon": self._settings.error_beacon, "licenseKey": self._settings.browser_key, "applicationID": self._settings.application_id, "transactionName": txn_name, "queueTime": queue_duration, "applicationTime": request_duration, "agent": self._settings.js_agent_file, } if self._settings.browser_monitoring.ssl_for_http is not None: ssl_for_http = self._settings.browser_monitoring.ssl_for_http intrinsics['sslForHttp'] = ssl_for_http return intrinsics
def create_incoming_headers(transaction): settings = transaction.settings encoding_key = settings.encoding_key headers = [] cross_process_id = '1#2' path = 'test' queue_time = 1.0 duration = 2.0 read_length = 1024 guid = '0123456789012345' record_tt = False payload = (cross_process_id, path, queue_time, duration, read_length, guid, record_tt) app_data = json_encode(payload) value = obfuscate(app_data, encoding_key) assert isinstance(value, type('')) headers.append(('X-NewRelic-App-Data', value)) return headers
def target_wsgi_application(environ, start_response): status = '200 OK' txn_name = environ.get('txn') if six.PY2: txn_name = txn_name.decode('UTF-8') txn_name = txn_name.split('/', 3) guid = environ.get('guid') old_cat = environ.get('old_cat') == 'True' txn = current_transaction() txn.guid = guid for req in OUTBOUD_REQUESTS: # Change the transaction name before making an outbound call. outgoing_name = req['outboundTxnName'].split('/', 3) if outgoing_name[0] != 'WebTransaction': set_background_task(True) set_transaction_name(outgoing_name[2], group=outgoing_name[1]) expected_outbound_header = obfuscate( json_encode(req['expectedOutboundPayload']), ENCODING_KEY) generated_outbound_header = dict( ExternalTrace.generate_request_headers(txn)) # A 500 error is returned because 'assert' statements in the wsgi app # are ignored. if old_cat: if (expected_outbound_header != generated_outbound_header['X-NewRelic-Transaction']): status = '500 Outbound Headers Check Failed.' else: if 'X-NewRelic-Transaction' in generated_outbound_header: status = '500 Outbound Headers Check Failed.' r = urlopen(environ['server_url']) r.read(10) # Set the final transaction name. if txn_name[0] != 'WebTransaction': set_background_task(True) set_transaction_name(txn_name[2], group=txn_name[1]) text = '<html><head>%s</head><body><p>RESPONSE</p>%s</body></html>' output = (text % (get_browser_timing_header(), get_browser_timing_footer())).encode('UTF-8') response_headers = [('Content-type', 'text/html; charset=utf-8'), ('Content-Length', str(len(output)))] start_response(status, response_headers) return [output]
def generate_request_headers(cls, transaction): """ Return a list of NewRelic specific headers as tuples [(HEADER_NAME0, HEADER_VALUE0), (HEADER_NAME1, HEADER_VALUE1)] """ if transaction is None: return [] settings = transaction.settings nr_headers = [] if settings.distributed_tracing.enabled: payload = transaction.create_distributed_trace_payload() if not payload: return [] encoded_header = payload.http_safe() nr_headers.append((cls.cat_distributed_trace_key, encoded_header)) elif settings.cross_application_tracer.enabled: transaction.is_part_of_cat = True encoded_cross_process_id = obfuscate(settings.cross_process_id, settings.encoding_key) nr_headers.append((cls.cat_id_key, encoded_cross_process_id)) transaction_data = [ transaction.guid, transaction.record_tt, transaction.trip_id, transaction.path_hash ] encoded_transaction = obfuscate(json_encode(transaction_data), settings.encoding_key) nr_headers.append((cls.cat_transaction_key, encoded_transaction)) if transaction.synthetics_header: nr_headers.append( (cls.cat_synthetics_key, transaction.synthetics_header)) return nr_headers
def generate_request_headers(cls, transaction): """ Return a list of NewRelic specific headers as tuples [(HEADER_NAME0, HEADER_VALUE0), (HEADER_NAME1, HEADER_VALUE1)] """ if transaction is None or transaction.settings is None: return [] settings = transaction.settings nr_headers = [] if settings.distributed_tracing.enabled: transaction.insert_distributed_trace_headers(nr_headers) elif settings.cross_application_tracer.enabled: transaction.is_part_of_cat = True path_hash = transaction.path_hash if path_hash is None: # Disable cat if path_hash fails to generate. transaction.is_part_of_cat = False else: encoded_cross_process_id = obfuscate(settings.cross_process_id, settings.encoding_key) nr_headers.append((cls.cat_id_key, encoded_cross_process_id)) transaction_data = [transaction.guid, transaction.record_tt, transaction.trip_id, path_hash] encoded_transaction = obfuscate(json_encode(transaction_data), settings.encoding_key) nr_headers.append( (cls.cat_transaction_key, encoded_transaction)) if transaction.synthetics_header: nr_headers.append( (cls.cat_synthetics_key, transaction.synthetics_header)) return nr_headers
def browser_timing_footer(self): """Returns the JavaScript footer to be included in any HTML response to perform real user monitoring. This function returns the footer as a native Python string. In Python 2 native strings are stored as bytes. In Python 3 native strings are stored as unicode. """ if not self.enabled: return '' if self._state != self.STATE_RUNNING: return '' if self.ignore_transaction: return '' # Only generate a footer if the header had already been # generated and we haven't already generated the footer. if not self.rum_header_generated: return '' if self.rum_footer_generated: return '' # Make sure we freeze the path. self._freeze_path() # When obfuscating values for the footer, we only use the # first 13 characters of the account license key. obfuscation_key = self._settings.license_key[:13] attributes = {} user_attributes = {} for attr in self.user_attributes: if attr.destinations & DST_BROWSER_MONITORING: user_attributes[attr.name] = attr.value if user_attributes: attributes['u'] = user_attributes request_parameters = self.request_parameters request_parameter_attributes = self.filter_request_parameters( request_parameters) agent_attributes = {} for attr in request_parameter_attributes: if attr.destinations & DST_BROWSER_MONITORING: agent_attributes[attr.name] = attr.value if agent_attributes: attributes['a'] = agent_attributes # create the data structure that pull all our data in footer_data = self.browser_monitoring_intrinsics(obfuscation_key) if attributes: attributes = obfuscate(json_encode(attributes), obfuscation_key) footer_data['atts'] = attributes footer = _js_agent_footer_fragment % json_encode(footer_data) # To avoid any issues with browser encodings, we will make sure that # the javascript we inject for the browser agent is ASCII encodable. # Since we obfuscate all agent and user attributes, and the transaction # name with base 64 encoding, this will preserve those strings, if # they have values outside of the ASCII character set. # In the case of Python 2, we actually then use the encoded value # as we need a native string, which for Python 2 is a byte string. # If encoding as ASCII fails we will return an empty string. try: if six.PY2: footer = footer.encode('ascii') else: footer.encode('ascii') except UnicodeError: if not WebTransaction.unicode_error_reported: _logger.error('ASCII encoding of js-agent-footer failed.', footer) WebTransaction.unicode_error_reported = True footer = '' # We remember if we have returned a non empty string value and # if called a second time we will not return it again. if footer: self.rum_footer_generated = True return footer
def process_response(self, status, response_headers, *args): """Processes response status and headers, extracting any details required and returning a set of additional headers to merge into that being returned for the web transaction. """ additional_headers = [] # Extract the HTTP status response code. try: self.response_code = int(status.split(' ')[0]) except Exception: pass # Extract response content length and type for inclusion in agent # attributes try: for header, value in response_headers: lower_header = header.lower() if 'content-length' == lower_header: self._response_properties['CONTENT_LENGTH'] = int(value) elif 'content-type' == lower_header: self._response_properties['CONTENT_TYPE'] = value except Exception: pass # Generate metrics and response headers for inbound cross # process web external calls. if self.client_cross_process_id is not None: # Need to work out queueing time and duration up to this # point for inclusion in metrics and response header. If the # recording of the transaction had been prematurely stopped # via an API call, only return time up until that call was # made so it will match what is reported as duration for the # transaction. if self.queue_start: queue_time = self.start_time - self.queue_start else: queue_time = 0 if self.end_time: duration = self.end_time = self.start_time else: duration = time.time() - self.start_time # Generate the metric identifying the caller. metric_name = 'ClientApplication/%s/all' % ( self.client_cross_process_id) self.record_custom_metric(metric_name, duration) # Generate the additional response headers which provide # information back to the caller. We need to freeze the # transaction name before adding to the header. self._freeze_path() payload = (self._settings.cross_process_id, self.path, queue_time, duration, self._read_length, self.guid, self.record_tt) app_data = json_encode(payload) additional_headers.append(('X-NewRelic-App-Data', obfuscate( app_data, self._settings.encoding_key))) # The additional headers returned need to be merged into the # original response headers passed back by the application. return additional_headers