def test_get_and_set_full_context(self): mock_tracer_get = mock.Mock() mock_span_get = mock.Mock() execution_context.set_opencensus_tracer(mock_tracer_get) execution_context.set_current_span(mock_span_get) execution_context.set_opencensus_attr("test", "test_value") tracer, span, attrs = execution_context.get_opencensus_full_context() self.assertEqual(mock_tracer_get, tracer) self.assertEqual(mock_span_get, span) self.assertEqual({"test": "test_value"}, attrs) mock_tracer_set = mock.Mock() mock_span_set = mock.Mock() execution_context.set_opencensus_full_context(mock_tracer_set, mock_span_set, None) self.assertEqual(mock_tracer_set, execution_context.get_opencensus_tracer()) self.assertEqual(mock_span_set, execution_context.get_current_span()) self.assertEqual({}, execution_context.get_opencensus_attrs()) execution_context.set_opencensus_full_context( mock_tracer_set, mock_span_set, {"test": "test_value"}) self.assertEqual("test_value", execution_context.get_opencensus_attr("test"))
def call(self, method, url, body, headers, *args, **kwargs): _tracer = execution_context.get_opencensus_tracer() blacklist_hostnames = execution_context.get_opencensus_attr( 'blacklist_hostnames') dest_url = '{}:{}'.format(self.host, self.port) if utils.disable_tracing_hostname(dest_url, blacklist_hostnames): return request_func(self, method, url, body, headers, *args, **kwargs) _span = _tracer.start_span() _span.span_kind = span_module.SpanKind.CLIENT _span.name = '[httplib]{}'.format(request_func.__name__) # Add the request url to attributes _tracer.add_attribute_to_current_span(HTTP_URL, url) # Add the request method to attributes _tracer.add_attribute_to_current_span(HTTP_METHOD, method) # Store the current span id to thread local. execution_context.set_opencensus_attr( 'httplib/current_span_id', _span.span_id) try: headers = headers.copy() headers.update(_tracer.propagator.to_headers( _span.context_tracer.span_context)) except Exception: # pragma: NO COVER pass return request_func(self, method, url, body, headers, *args, **kwargs)
def emit(self, span_datas): """ :type span_datas: list of :class: `~opencensus.trace.span_data.SpanData` :param list of opencensus.trace.span_data.SpanData span_datas: SpanData tuples to emit """ envelopes = [self.span_data_to_envelope(sd) for sd in span_datas] # TODO: prevent requests being tracked blacklist_hostnames = execution_context.get_opencensus_attr( 'blacklist_hostnames', ) execution_context.set_opencensus_attr( 'blacklist_hostnames', ['dc.services.visualstudio.com'], ) response = requests.post( url=self.options.endpoint, data=json.dumps(envelopes), headers={ 'Accept': 'application/json', 'Content-Type': 'application/json; charset=utf-8', }, timeout=self.options.timeout, ) execution_context.set_opencensus_attr( 'blacklist_hostnames', blacklist_hostnames, ) response = response # noqa
def _get_django_request(): """Get Django request from thread local. :rtype: str :returns: Django request. """ return execution_context.get_opencensus_attr(REQUEST_THREAD_LOCAL_KEY)
def _get_django_span(): """Get Django span from thread local. :rtype: str :returns: Django request. """ return execution_context.get_opencensus_attr(SPAN_THREAD_LOCAL_KEY)
def call(url, *args, **kwargs): blacklist_hostnames = execution_context.get_opencensus_attr( 'blacklist_hostnames') parsed_url = urlparse(url) if parsed_url.port is None: dest_url = parsed_url.hostname else: dest_url = '{}:{}'.format(parsed_url.hostname, parsed_url.port) if utils.disable_tracing_hostname(dest_url, blacklist_hostnames): return requests_func(url, *args, **kwargs) _tracer = execution_context.get_opencensus_tracer() _span = _tracer.start_span() _span.name = '[requests]{}'.format(requests_func.__name__) _span.span_kind = span_module.SpanKind.CLIENT # Add the requests url to attributes _tracer.add_attribute_to_current_span(HTTP_URL, url) result = requests_func(url, *args, **kwargs) # Add the status code to attributes _tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, str(result.status_code)) _tracer.end_span() return result
def test_has_attrs(self): key = 'key' value = 'value' execution_context.set_opencensus_attr(key, value) result = execution_context.get_opencensus_attr(key) self.assertEqual(result, value)
def call(url, *args, **kwargs): # Check if request was sent from an exporter. If so, do not wrap. if execution_context.is_exporter(): return requests_func(url, *args, **kwargs) excludelist_hostnames = execution_context.get_opencensus_attr( 'excludelist_hostnames') parsed_url = urlparse(url) if parsed_url.port is None: dest_url = parsed_url.hostname else: dest_url = '{}:{}'.format(parsed_url.hostname, parsed_url.port) if utils.disable_tracing_hostname(dest_url, excludelist_hostnames): return requests_func(url, *args, **kwargs) path = parsed_url.path if parsed_url.path else '/' _tracer = execution_context.get_opencensus_tracer() _span = _tracer.start_span() _span.name = '{}'.format(path) _span.span_kind = span_module.SpanKind.CLIENT # Add the component type to attributes _tracer.add_attribute_to_current_span("component", "HTTP") # Add the requests host to attributes _tracer.add_attribute_to_current_span(HTTP_HOST, dest_url) # Add the requests method to attributes _tracer.add_attribute_to_current_span(HTTP_METHOD, requests_func.__name__.upper()) # Add the requests path to attributes _tracer.add_attribute_to_current_span(HTTP_PATH, path) # Add the requests url to attributes _tracer.add_attribute_to_current_span(HTTP_URL, url) try: result = requests_func(url, *args, **kwargs) except requests.Timeout: _span.set_status(exceptions_status.TIMEOUT) raise except requests.URLRequired: _span.set_status(exceptions_status.INVALID_URL) raise except Exception as e: _span.set_status(exceptions_status.unknown(e)) raise else: # Add the status code to attributes _tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, result.status_code) _span.set_status(utils.status_from_http_code(result.status_code)) return result finally: _tracer.end_span()
def _on_finish(func, handler, args, kwargs): if execution_context.get_opencensus_attr(TORNADO_EXCEPTION) is not None: return tracer = execution_context.get_opencensus_tracer() span = execution_context.get_current_span() span.add_attribute(attribute_key=HTTP_STATUS_CODE, attribute_value=str(handler.get_status())) tracer.finish() return func(*args, **kwargs)
def call(self, *args, **kwargs): _tracer = execution_context.get_opencensus_tracer() current_span_id = execution_context.get_opencensus_attr( 'httplib/current_span_id') span = _tracer.current_span() # No corresponding request span is found, request not traced. if span.span_id != current_span_id: return response_func(self, *args, **kwargs) result = response_func(self, *args, **kwargs) # Add the status code to attributes _tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, str(result.status)) _tracer.end_span() return result
def wrap_session_request(wrapped, instance, args, kwargs): """Wrap the session function to trace it.""" # Check if request was sent from an exporter. If so, do not wrap. if execution_context.is_exporter(): return wrapped(*args, **kwargs) method = kwargs.get('method') or args[0] url = kwargs.get('url') or args[1] blacklist_hostnames = execution_context.get_opencensus_attr( 'blacklist_hostnames') parsed_url = urlparse(url) if parsed_url.port is None: dest_url = parsed_url.hostname else: dest_url = '{}:{}'.format(parsed_url.hostname, parsed_url.port) if utils.disable_tracing_hostname(dest_url, blacklist_hostnames): return wrapped(*args, **kwargs) _tracer = execution_context.get_opencensus_tracer() _span = _tracer.start_span() _span.name = '[requests]{}'.format(method) _span.span_kind = span_module.SpanKind.CLIENT try: tracer_headers = _tracer.propagator.to_headers(_tracer.span_context) kwargs.setdefault('headers', {}).update(tracer_headers) except Exception: # pragma: NO COVER pass # Add the requests url to attributes _tracer.add_attribute_to_current_span(HTTP_URL, url) result = wrapped(*args, **kwargs) # Add the status code to attributes _tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, str(result.status_code)) _tracer.end_span() return result
def call(self, *args, **kwargs): # Check if request was sent from an exporter. If so, do not wrap. if execution_context.is_exporter(): return response_func(self, *args, **kwargs) _tracer = execution_context.get_opencensus_tracer() current_span_id = execution_context.get_opencensus_attr( 'httplib/current_span_id') span = _tracer.current_span() # No corresponding request span is found, request not traced. if not span or span.span_id != current_span_id: return response_func(self, *args, **kwargs) result = response_func(self, *args, **kwargs) # Add the status code to attributes _tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, result.status) _tracer.end_span() return result
def wrap_session_request(wrapped, instance, args, kwargs): """Wrap the session function to trace it.""" # Check if request was sent from an exporter. If so, do not wrap. if execution_context.is_exporter(): return wrapped(*args, **kwargs) method = kwargs.get('method') or args[0] url = kwargs.get('url') or args[1] excludelist_hostnames = execution_context.get_opencensus_attr( 'excludelist_hostnames') parsed_url = urlparse(url) if parsed_url.port is None: dest_url = parsed_url.hostname else: dest_url = '{}:{}'.format(parsed_url.hostname, parsed_url.port) if utils.disable_tracing_hostname(dest_url, excludelist_hostnames): return wrapped(*args, **kwargs) path = parsed_url.path if parsed_url.path else '/' _tracer = execution_context.get_opencensus_tracer() _span = _tracer.start_span() _span.name = '{}'.format(path) _span.span_kind = span_module.SpanKind.CLIENT try: tracer_headers = _tracer.propagator.to_headers(_tracer.span_context) kwargs.setdefault('headers', {}).update(tracer_headers) except Exception: # pragma: NO COVER pass # Add the component type to attributes _tracer.add_attribute_to_current_span("component", "HTTP") # Add the requests host to attributes _tracer.add_attribute_to_current_span(HTTP_HOST, dest_url) # Add the requests method to attributes _tracer.add_attribute_to_current_span(HTTP_METHOD, method.upper()) # Add the requests path to attributes _tracer.add_attribute_to_current_span(HTTP_PATH, path) # Add the requests url to attributes _tracer.add_attribute_to_current_span(HTTP_URL, url) try: result = wrapped(*args, **kwargs) except requests.Timeout: _span.set_status(exceptions_status.TIMEOUT) raise except requests.URLRequired: _span.set_status(exceptions_status.INVALID_URL) raise except Exception as e: _span.set_status(exceptions_status.unknown(e)) raise else: # Add the status code to attributes _tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, result.status_code) _span.set_status(utils.status_from_http_code(result.status_code)) return result finally: _tracer.end_span()
def _transmit_without_retry(self, envelopes): # Contains logic from transport._transmit # TODO: Remove this function from exporter and # consolidate with transport._transmit to cover # all exporter use cases. # Uses cases pertain to properly handling failures # and implementing a retry policy for this exporter # TODO: implement retry policy """ Transmit the data envelopes to the ingestion service. Does not perform retry logic. For partial success and non-retryable failure, simply outputs result to logs. This function should never throw exception. """ blacklist_hostnames = execution_context.get_opencensus_attr( 'blacklist_hostnames', ) execution_context.set_opencensus_attr( 'blacklist_hostnames', ['dc.services.visualstudio.com'], ) try: response = requests.post( url=self.options.endpoint, data=json.dumps(envelopes), headers={ 'Accept': 'application/json', 'Content-Type': 'application/json; charset=utf-8', }, timeout=self.options.timeout, ) except Exception as ex: # No retry policy, log output logger.warning('Transient client side error %s.', ex) return finally: execution_context.set_opencensus_attr( 'blacklist_hostnames', blacklist_hostnames, ) text = 'N/A' data = None # Handle the possible results from the response if response is None: logger.warning('Error: cannot read response.') return try: status_code = response.status_code except Exception as ex: logger.warning('Error while reading response status code %s.', ex) return try: text = response.text except Exception as ex: logger.warning('Error while reading response body %s.', ex) return try: data = json.loads(text) except Exception as ex: logger.warning( 'Error while loading ' + 'json from response body %s.', ex) return if status_code == 200: logger.info('Transmission succeeded: %s.', text) return # Check for retryable partial content if status_code == 206: if data: try: retryable_envelopes = [] for error in data['errors']: if error['statusCode'] in ( 429, # Too Many Requests 500, # Internal Server Error 503, # Service Unavailable ): retryable_envelopes.append( envelopes[error['index']]) else: logger.error( 'Data drop %s: %s %s.', error['statusCode'], error['message'], envelopes[error['index']], ) # show the envelopes that can be # retried manually for visibility if retryable_envelopes: logger.warning( 'Error while processing data. Data dropped. ' + 'Consider manually retrying for envelopes: %s.', retryable_envelopes) return except Exception: logger.exception('Error while processing %s: %s.', status_code, text) return # Check for non-tryable result if status_code in ( 206, # Partial Content 429, # Too Many Requests 500, # Internal Server Error 503, # Service Unavailable ): # server side error (retryable) logger.warning( 'Transient server side error %s: %s. ' + 'Consider manually trying.', status_code, text, ) else: # server side error (non-retryable) logger.error( 'Non-retryable server side error %s: %s.', status_code, text, )
def _transmit(self, envelopes): """ Transmit the data envelopes to the ingestion service. Return a negative value for partial success or non-retryable failure. Return 0 if all envelopes have been successfully ingested. Return the next retry time in seconds for retryable failure. This function should never throw exception. """ # TODO: prevent requests being tracked blacklist_hostnames = execution_context.get_opencensus_attr( 'blacklist_hostnames', ) execution_context.set_opencensus_attr( 'blacklist_hostnames', ['dc.services.visualstudio.com'], ) try: response = requests.post( url=self.options.endpoint, data=json.dumps(envelopes), headers={ 'Accept': 'application/json', 'Content-Type': 'application/json; charset=utf-8', }, timeout=self.options.timeout, ) except Exception as ex: # TODO: consider RequestException logger.warning('Transient client side error %s.', ex) # client side error (retryable) return self.options.minimum_retry_interval finally: execution_context.set_opencensus_attr( 'blacklist_hostnames', blacklist_hostnames, ) text = 'N/A' data = None try: text = response.text except Exception as ex: logger.warning('Error while reading response body %s.', ex) else: try: data = json.loads(text) except Exception: pass if response.status_code == 200: logger.info('Transmission succeeded: %s.', text) return 0 if response.status_code == 206: # Partial Content # TODO: store the unsent data if data: try: resend_envelopes = [] for error in data['errors']: if error['statusCode'] in ( 429, # Too Many Requests 500, # Internal Server Error 503, # Service Unavailable ): resend_envelopes.append(envelopes[error['index']]) else: logger.error( 'Data drop %s: %s %s.', error['statusCode'], error['message'], envelopes[error['index']], ) if resend_envelopes: self.storage.put(resend_envelopes) except Exception as ex: logger.error( 'Error while processing %s: %s %s.', response.status_code, text, ex, ) return -response.status_code # cannot parse response body, fallback to retry if response.status_code in ( 206, # Partial Content 429, # Too Many Requests 500, # Internal Server Error 503, # Service Unavailable ): logger.warning( 'Transient server side error %s: %s.', response.status_code, text, ) # server side error (retryable) return self.options.minimum_retry_interval logger.error( 'Non-retryable server side error %s: %s.', response.status_code, text, ) # server side error (non-retryable) return -response.status_code
def _trace_db_call(execute, sql, params, many, context): explain_mode = execution_context.get_opencensus_attr('explain_mode') if "EXPLAIN" in sql: logger.debug("_trace_db_call: Processing EXPLAIN statement") return execute(sql, params, many, context) logger.debug(f"_trace_db_call: {sql}") tracer = _get_current_tracer() if not tracer: return execute(sql, params, many, context) vendor = context['connection'].vendor alias = context['connection'].alias span = tracer.start_span() try: (sql_command, subcommand, *_) = sql.split(maxsplit=2) logger.info(f"subcommand: '{subcommand}'") if "COUNT(*)" == subcommand: span.name = '{}.COUNT'.format(vendor) else: span.name = '{}.{}'.format(vendor, sql_command) except Exception: span.name = '{}.OTHER'.format(vendor) logger.warning("Could not parse SQL statement for detailed tracing", exc_info=True) span.span_kind = span_module.SpanKind.CLIENT tracer.add_attribute_to_current_span('component', vendor) tracer.add_attribute_to_current_span('db.instance', alias) tracer.add_attribute_to_current_span('db.statement', sql) tracer.add_attribute_to_current_span('db.type', 'sql') tracer.add_attribute_to_current_span('python.traceback', common_utils.get_traceback()) # EXPLAIN is expensive and needs to be explicitly enabled if explain_mode is not None: try: with connections[alias].cursor() as cursor: # EXPLAIN ANALYZE only works under certain circumstances if "analyze" == explain_mode and "postgresql" == vendor and "SELECT" == sql_command: cursor.execute("EXPLAIN ANALYZE {0}".format(sql), params) else: cursor.execute("EXPLAIN {0}".format(sql), params) planresult = cursor.fetchall() logger.debug("EXPLAIN plan: {0}".format(planresult)) tracer.add_attribute_to_current_span('db.plan', "{0}".format(planresult)) except Exception: # pragma: NO COVER logger.warning("Could not retrieve EXPLAIN plan", exc_info=True) tracer.add_attribute_to_current_span('db.plan', 'not available') pass try: result = execute(sql, params, many, context) except Exception as exc: # pragma: NO COVER status = status_module.Status.from_exception(exc) span.set_status(status) raise else: return result finally: tracer.end_span()
def get_parent(): """Returns parent execution context. """ return execution_context.get_opencensus_attr(_threadlocal_parent)
def test_no_attrs(self): key = 'key' result = execution_context.get_opencensus_attr(key) self.assertIsNone(result)