def _prepare(tracer, func, handler, args, kwargs): start_time = time_ns() request = handler.request if _excluded_urls.url_disabled(request.uri): return func(*args, **kwargs) _start_span(tracer, handler, start_time) return func(*args, **kwargs)
def fetch_async(tracer, func, _, args, kwargs): start_time = time_ns() # Return immediately if no args were provided (error) # or original_request is set (meaning we are in a redirect step). if len(args) == 0 or hasattr(args[0], "original_request"): return func(*args, **kwargs) # Force the creation of a HTTPRequest object if needed, # so we can inject the context into the headers. args, kwargs = _normalize_request(args, kwargs) request = args[0] span = tracer.start_span( request.method, kind=trace.SpanKind.CLIENT, start_time=start_time, ) if span.is_recording(): attributes = { "http.url": request.url, "http.method": request.method, } for key, value in attributes.items(): span.set_attribute(key, value) with tracer.use_span(span): inject(type(request.headers).__setitem__, request.headers) future = func(*args, **kwargs) future.add_done_callback( functools.partial(_finish_tracing_callback, span=span)) return future
def _wrapped_app(wrapped_app_environ, start_response): # We want to measure the time for route matching, etc. # In theory, we could start the span here and use # update_name later but that API is "highly discouraged" so # we better avoid it. wrapped_app_environ[_ENVIRON_STARTTIME_KEY] = time_ns() def _start_response(status, response_headers, *args, **kwargs): if not _excluded_urls.url_disabled(flask.request.url): span = flask.request.environ.get(_ENVIRON_SPAN_KEY) if span: otel_wsgi.add_response_attributes( span, status, response_headers ) else: _logger.warning( "Flask environ's OpenTelemetry span " "missing at _start_response(%s)", status, ) return start_response(status, response_headers, *args, **kwargs) return wsgi_app(wrapped_app_environ, _start_response)
def worker(self): timeout = self.schedule_delay_millis / 1e3 flush_request = None # type: typing.Optional[_FlushRequest] while not self.done: with self.condition: if self.done: # done flag may have changed, avoid waiting break flush_request = self._get_and_unset_flush_request() if (len(self.queue) < self.max_export_batch_size and flush_request is None): self.condition.wait(timeout) flush_request = self._get_and_unset_flush_request() if not self.queue: # spurious notification, let's wait again, reset timeout timeout = self.schedule_delay_millis / 1e3 self._notify_flush_request_finished(flush_request) flush_request = None continue if self.done: # missing spans will be sent when calling flush break # subtract the duration of this export call to the next timeout start = time_ns() self._export(flush_request) end = time_ns() duration = (end - start) / 1e9 timeout = self.schedule_delay_millis / 1e3 - duration self._notify_flush_request_finished(flush_request) flush_request = None # there might have been a new flush request while export was running # and before the done flag switched to true with self.condition: shutdown_flush_request = self._get_and_unset_flush_request() # be sure that all spans are sent self._drain_queue() self._notify_flush_request_finished(flush_request) self._notify_flush_request_finished(shutdown_flush_request)
def end(self, end_time: Optional[int] = None) -> None: with self._lock: if self._start_time is None: raise RuntimeError("Calling end() on a not started span.") if self._end_time is not None: logger.warning("Calling end() on an ended span.") return self._end_time = end_time if end_time is not None else time_ns() self._span_processor.on_end(self._readable_span())
def start( self, start_time: Optional[int] = None, parent_context: Optional[context_api.Context] = None, ) -> None: with self._lock: if self._start_time is not None: logger.warning("Calling start() on a started span.") return self._start_time = (start_time if start_time is not None else time_ns()) self._span_processor.on_start(self, parent_context=parent_context)
def add_event( self, name: str, attributes: types.Attributes = None, timestamp: Optional[int] = None, ) -> None: _filter_attribute_values(attributes) attributes = _create_immutable_attributes(attributes) self._add_event( Event( name=name, attributes=attributes, timestamp=time_ns() if timestamp is None else timestamp, ))
def worker(self): timeout = self.schedule_delay_millis / 1e3 while not self.done: if not self._flushing: with self.condition: self.condition.wait(timeout) if not self.check_traces_queue: # spurious notification, let's wait again, reset timeout timeout = self.schedule_delay_millis / 1e3 continue if self.done: # missing spans will be sent when calling flush break # substract the duration of this export call to the next timeout start = time_ns() self.export() end = time_ns() duration = (end - start) / 1e9 timeout = self.schedule_delay_millis / 1e3 - duration # be sure that all spans are sent self._drain_queue()
def trace_tween(request): if _excluded_urls.url_disabled(request.url): request.environ[_ENVIRON_ENABLED_KEY] = False # short-circuit when we don't want to trace anything return handler(request) request.environ[_ENVIRON_ENABLED_KEY] = True request.environ[_ENVIRON_STARTTIME_KEY] = time_ns() try: response = handler(request) response_or_exception = response except HTTPException as exc: # If the exception is a pyramid HTTPException, # that's still valuable information that isn't necessarily # a 500. For instance, HTTPFound is a 302. # As described in docs, Pyramid exceptions are all valid # response types response_or_exception = exc raise finally: span = request.environ.get(_ENVIRON_SPAN_KEY) enabled = request.environ.get(_ENVIRON_ENABLED_KEY) if not span and enabled: _logger.warning( "Pyramid environ's OpenTelemetry span missing." "If the OpenTelemetry tween was added manually, make sure" "PyramidInstrumentor().instrument_config(config) is called" ) elif enabled: otel_wsgi.add_response_attributes( span, response_or_exception.status, response_or_exception.headers, ) activation = request.environ.get(_ENVIRON_ACTIVATION_KEY) if isinstance(response_or_exception, HTTPException): activation.__exit__( type(response_or_exception), response_or_exception, getattr(response_or_exception, "__traceback__", None), ) else: activation.__exit__(None, None, None) context.detach(request.environ.get(_ENVIRON_TOKEN)) return response
def force_flush(self, timeout_millis: int = 30000) -> bool: """Sequentially calls force_flush on all underlying :class:`SpanProcessor` Args: timeout_millis: The maximum amount of time over all span processors to wait for spans to be exported. In case the first n span processors exceeded the timeout followup span processors will be skipped. Returns: True if all span processors flushed their spans within the given timeout, False otherwise. """ deadline_ns = time_ns() + timeout_millis * 1000000 for sp in self._span_processors: current_time_ns = time_ns() if current_time_ns >= deadline_ns: return False if not sp.force_flush((deadline_ns - current_time_ns) // 1000000): return False return True
def test_events(self): self.assertEqual(trace_api.get_current_span(), trace_api.INVALID_SPAN) with self.tracer.start_as_current_span("root") as root: # only event name root.add_event("event0") # event name and attributes root.add_event("event1", { "name": "pluto", "some_bools": [True, False] }) # event name, attributes and timestamp now = time_ns() root.add_event("event2", {"name": ["birthday"]}, now) mutable_list = ["original_contents"] root.add_event("event3", {"name": mutable_list}) self.assertEqual(len(root.events), 4) self.assertEqual(root.events[0].name, "event0") self.assertEqual(root.events[0].attributes, {}) self.assertEqual(root.events[1].name, "event1") self.assertEqual( root.events[1].attributes, { "name": "pluto", "some_bools": (True, False) }, ) self.assertEqual(root.events[2].name, "event2") self.assertEqual(root.events[2].attributes, {"name": ("birthday", )}) self.assertEqual(root.events[2].timestamp, now) self.assertEqual(root.events[3].name, "event3") self.assertEqual(root.events[3].attributes, {"name": ("original_contents", )}) mutable_list = ["new_contents"] self.assertEqual(root.events[3].attributes, {"name": ("original_contents", )})
def __call__(self, env, start_response): if _excluded_urls.url_disabled(env.get("PATH_INFO", "/")): return super().__call__(env, start_response) start_time = time_ns() token = context.attach(extract(otel_wsgi.carrier_getter, env)) span = self._tracer.start_span( otel_wsgi.get_default_span_name(env), kind=trace.SpanKind.SERVER, start_time=start_time, ) if span.is_recording(): attributes = otel_wsgi.collect_request_attributes(env) for key, value in attributes.items(): span.set_attribute(key, value) activation = self._tracer.use_span(span, end_on_exit=True) activation.__enter__() env[_ENVIRON_SPAN_KEY] = span env[_ENVIRON_ACTIVATION_KEY] = activation def _start_response(status, response_headers, *args, **kwargs): otel_wsgi.add_response_attributes(span, status, response_headers) response = start_response( status, response_headers, *args, **kwargs ) activation.__exit__(None, None, None) context.detach(token) return response try: return super().__call__(env, _start_response) except Exception as exc: activation.__exit__( type(exc), exc, getattr(exc, "__traceback__", None), ) context.detach(token) raise
def _finish_span(tracer, handler, error=None): status_code = handler.get_status() reason = getattr(handler, "_reason") finish_args = (None, None, None) ctx = getattr(handler, _HANDLER_CONTEXT_KEY, None) if error: if isinstance(error, tornado.web.HTTPError): status_code = error.status_code if not ctx and status_code == 404: ctx = _start_span(tracer, handler, time_ns()) if status_code != 404: finish_args = ( type(error), error, getattr(error, "__traceback__", None), ) status_code = 500 reason = None if not ctx: return if ctx.span.is_recording(): if reason: ctx.span.set_attribute("http.status_text", reason) ctx.span.set_attribute("http.status_code", status_code) ctx.span.set_status( Status( status_code=http_status_to_status_code(status_code), description=reason, )) ctx.activation.__exit__(*finish_args) context.detach(ctx.token) delattr(handler, _HANDLER_CONTEXT_KEY)
def test_time_seconds_from_ns(self): time_nanoseconds = time_ns() result = util.time_seconds_from_ns(time_nanoseconds) self.assertEqual(result, time_nanoseconds / 1e9)
def __init__(self, name: str, timestamp: Optional[int] = None) -> None: self._name = name if timestamp is None: self._timestamp = time_ns() else: self._timestamp = timestamp