def to_json(self, client): # type: (Optional[sentry_sdk.Client]) -> Dict[str, Any] rv = { "trace_id": self.trace_id, "span_id": self.span_id, "parent_span_id": self.parent_span_id, "same_process_as_parent": self.same_process_as_parent, "op": self.op, "description": self.description, "start_timestamp": partial_serialize( client, self.start_timestamp, is_databag=False, should_repr_strings=False, ), "timestamp": partial_serialize( client, self.timestamp, is_databag=False, should_repr_strings=False ), } # type: Dict[str, Any] transaction = self.transaction if transaction: rv["transaction"] = transaction tags = self._tags if tags: rv["tags"] = tags data = self._data if data: rv["data"] = data return rv
def finish(self, hub=None): # type: (Optional[sentry_sdk.Hub]) -> Optional[str] hub = hub or self.hub or sentry_sdk.Hub.current if self.timestamp is not None: # This transaction is already finished, so we should not flush it again. return None self.timestamp = datetime.now() _maybe_create_breadcrumbs_from_span(hub, self) if self._span_recorder is None: return None self._span_recorder.finish_span(self) if self.transaction is None: # If this has no transaction set we assume there's a parent # transaction for this span that would be flushed out eventually. return None client = hub.client if client is None: # We have no client and therefore nowhere to send this transaction # event. return None if not self.sampled: # At this point a `sampled = None` should have already been # resolved to a concrete decision. If `sampled` is `None`, it's # likely that somebody used `with sentry_sdk.Hub.start_span(..)` on a # non-transaction span and later decided to make it a transaction. if self.sampled is None: logger.warning("Discarding transaction Span without sampling decision") return None return hub.capture_event( { "type": "transaction", "transaction": self.transaction, "contexts": {"trace": self.get_trace_context()}, "timestamp": partial_serialize( client, self.timestamp, is_databag=False, should_repr_strings=False ), "start_timestamp": partial_serialize( client, self.start_timestamp, is_databag=False, should_repr_strings=False, ), "spans": [ s.to_json(client) for s in self._span_recorder.finished_spans if s is not self ], } )
def event_processor(event, hint): # type: (Event, Hint) -> Optional[Event] client = Hub.current.client with capture_internal_exceptions(): extra = event.setdefault("extra", {}) extra["celery-job"] = partial_serialize( client, { "task_name": task.name, "args": args, "kwargs": kwargs }, should_repr_strings=False, ) if "exc_info" in hint: with capture_internal_exceptions(): if issubclass(hint["exc_info"][0], SoftTimeLimitExceeded): event["fingerprint"] = [ "celery", "SoftTimeLimitExceeded", partial_serialize( client, getattr(task, "name", task), should_repr_strings=False, ), ] return event
def _emit(self, record): # type: (LogRecord) -> None if not _can_record(record): return hub = Hub.current if hub.client is None: return client_options = hub.client.options # exc_info might be None or (None, None, None) if record.exc_info is not None and record.exc_info[0] is not None: event, hint = event_from_exception( record.exc_info, client_options=client_options, mechanism={ "type": "logging", "handled": True }, ) elif record.exc_info and record.exc_info[0] is None: event = {} hint = {} with capture_internal_exceptions(): event["threads"] = { "values": [{ "stacktrace": current_stacktrace(client_options["with_locals"]), "crashed": False, "current": True, }] } else: event = {} hint = {} hint["log_record"] = record client = Hub.current.client event["level"] = _logging_to_event_level(record.levelname) event["logger"] = record.name event["logentry"] = { "message": to_string(record.msg), "params": partial_serialize(client, record.args, should_repr_strings=False), } event["extra"] = partial_serialize(client, _extra_from_record(record), should_repr_strings=False) hub.capture_event(event, hint=hint)
def aiohttp_processor( event, # type: Dict[str, Any] hint, # type: Dict[str, Tuple[type, BaseException, Any]] ): # type: (...) -> Dict[str, Any] request = weak_request() if request is None: return event with capture_internal_exceptions(): request_info = event.setdefault("request", {}) request_info["url"] = "%s://%s%s" % ( request.scheme, request.host, request.path, ) request_info["query_string"] = request.query_string request_info["method"] = request.method request_info["env"] = {"REMOTE_ADDR": request.remote} hub = Hub.current request_info["headers"] = partial_serialize( hub.client, _filter_headers(dict(request.headers)), should_repr_strings=False, ) # Just attach raw data here if it is within bounds, if available. # Unfortunately there's no way to get structured data from aiohttp # without awaiting on some coroutine. request_info["data"] = get_aiohttp_request_data(hub, request) return event
def tornado_processor(event, hint): # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] handler = weak_handler() if handler is None: return event request = handler.request with capture_internal_exceptions(): method = getattr(handler, handler.request.method.lower()) event["transaction"] = transaction_from_function(method) with capture_internal_exceptions(): extractor = TornadoRequestExtractor(request) extractor.extract_into_event(event) request_info = event["request"] request_info["url"] = "%s://%s%s" % ( request.protocol, request.host, request.path, ) request_info["query_string"] = partial_serialize( Hub.current.client, request.query, should_repr_strings=False) request_info["method"] = request.method request_info["env"] = {"REMOTE_ADDR": request.remote_ip} request_info["headers"] = _filter_headers(dict(request.headers)) with capture_internal_exceptions(): if handler.current_user and _should_send_default_pii(): event.setdefault("user", {})["is_authenticated"] = True return event
def event_processor(self, event, hint, asgi_scope): # type: (Event, Hint, Any) -> Optional[Event] request_info = event.get("request", {}) if asgi_scope["type"] in ("http", "websocket"): request_info["url"] = self.get_url(asgi_scope) request_info["method"] = asgi_scope["method"] request_info["headers"] = _filter_headers( self.get_headers(asgi_scope)) request_info["query_string"] = self.get_query(asgi_scope) if asgi_scope.get("client") and _should_send_default_pii(): request_info["env"] = {"REMOTE_ADDR": asgi_scope["client"][0]} if asgi_scope.get("endpoint"): # Webframeworks like Starlette mutate the ASGI env once routing is # done, which is sometime after the request has started. If we have # an endpoint, overwrite our path-based transaction name. event["transaction"] = self.get_transaction(asgi_scope) event["request"] = partial_serialize(Hub.current.client, request_info, should_repr_strings=False) return event
def aiohttp_processor( event, # type: Dict[str, Any] hint, # type: Dict[str, Tuple[type, BaseException, Any]] ): # type: (...) -> Dict[str, Any] request = weak_request() if request is None: return event with capture_internal_exceptions(): # TODO: Figure out what to do with request body. Methods on request # are async, but event processors are not. request_info = event.setdefault("request", {}) request_info["url"] = "%s://%s%s" % ( request.scheme, request.host, request.path, ) request_info["query_string"] = request.query_string request_info["method"] = request.method request_info["env"] = {"REMOTE_ADDR": request.remote} request_info["headers"] = partial_serialize( Hub.current.client, _filter_headers(dict(request.headers)), should_repr_strings=False, ) return event
def event_processor(event, hint): # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] job = weak_job() if job is not None: with capture_internal_exceptions(): extra = event.setdefault("extra", {}) extra["rq-job"] = partial_serialize( Hub.current.client, { "job_id": job.id, "func": job.func_name, "args": job.args, "kwargs": job.kwargs, "description": job.description, }, should_repr_strings=False, ) if "exc_info" in hint: with capture_internal_exceptions(): if issubclass(hint["exc_info"][0], JobTimeoutException): event["fingerprint"] = [ "rq", "JobTimeoutException", job.func_name ] return event
def set_extra( self, key, # type: str value, # type: Any ): # type: (...) -> None """Sets an extra key to a specific value.""" self._extras[key] = partial_serialize( sentry_sdk.Hub.current.client, value, should_repr_strings=False )
def set_context( self, key, # type: str value, # type: Any ): # type: (...) -> None """Binds a context at a certain key to a specific value.""" self._contexts[key] = partial_serialize( sentry_sdk.Hub.current.client, value, should_repr_strings=False )
def add_breadcrumb( self, crumb=None, # type: Optional[Breadcrumb] hint=None, # type: Optional[BreadcrumbHint] **kwargs # type: Any ): # type: (...) -> None """ Adds a breadcrumb. :param crumb: Dictionary with the data as the sentry v7/v8 protocol expects. :param hint: An optional value that can be used by `before_breadcrumb` to customize the breadcrumbs that are emitted. """ client, scope = self._stack[-1] if client is None: logger.info("Dropped breadcrumb because no client bound") return crumb = dict(crumb or ()) # type: Breadcrumb crumb.update(kwargs) if not crumb: return hint = dict(hint or ()) # type: Hint if crumb.get("timestamp") is None: crumb["timestamp"] = datetime.utcnow() if crumb.get("type") is None: crumb["type"] = "default" crumb = partial_serialize(client, crumb, should_repr_strings=False) if client.options["before_breadcrumb"] is not None: new_crumb = client.options["before_breadcrumb"](crumb, hint) else: new_crumb = crumb if new_crumb is not None: scope._breadcrumbs.append(new_crumb) else: logger.info("before breadcrumb dropped breadcrumb (%s)", crumb) max_breadcrumbs = client.options["max_breadcrumbs"] # type: int while len(scope._breadcrumbs) > max_breadcrumbs: scope._breadcrumbs.popleft()
def event_processor(event, hint): # type: (Event, Hint) -> Optional[Event] extra = event.setdefault("extra", {}) extra["lambda"] = { "remaining_time_in_millis": aws_context.get_remaining_time_in_millis(), "function_name": aws_context.function_name, "function_version": aws_context.function_version, "invoked_function_arn": aws_context.invoked_function_arn, "aws_request_id": aws_context.aws_request_id, } request = event.get("request", {}) if "httpMethod" in aws_event: request["method"] = aws_event["httpMethod"] request["url"] = _get_url(aws_event, aws_context) if "queryStringParameters" in aws_event: request["query_string"] = aws_event["queryStringParameters"] if "headers" in aws_event: request["headers"] = _filter_headers(aws_event["headers"]) if aws_event.get("body", None): # Unfortunately couldn't find a way to get structured body from AWS # event. Meaning every body is unstructured to us. request["data"] = AnnotatedValue("", {"rem": [["!raw", "x", 0, 0]]}) if _should_send_default_pii(): user_info = event.setdefault("user", {}) id = aws_event.get("identity", {}).get("userArn") if id is not None: user_info["id"] = id ip = aws_event.get("identity", {}).get("sourceIp") if ip is not None: user_info["ip_address"] = ip event["request"] = partial_serialize(Hub.current.client, request, should_repr_strings=False) return event
def extract_into_event(self, event): # type: (Dict[str, Any]) -> None client = Hub.current.client if client is None: return data = None # type: Optional[Union[AnnotatedValue, Dict[str, Any]]] content_length = self.content_length() request_info = event.get("request", {}) if _should_send_default_pii(): request_info["cookies"] = dict(self.cookies()) bodies = client.options["request_bodies"] if (bodies == "never" or (bodies == "small" and content_length > 10**3) or (bodies == "medium" and content_length > 10**4)): data = AnnotatedValue( "", { "rem": [["!config", "x", 0, content_length]], "len": content_length }, ) else: parsed_body = self.parsed_body() if parsed_body is not None: data = parsed_body elif self.raw_data(): data = AnnotatedValue( "", { "rem": [["!raw", "x", 0, content_length]], "len": content_length }, ) else: data = None if data is not None: request_info["data"] = data event["request"] = partial_serialize(client, request_info, should_repr_strings=False)
def capture_message( self, message, # type: str level=None, # type: Optional[str] ): # type: (...) -> Optional[str] """Captures a message. The message is just a string. If no level is provided the default level is `info`. :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.Client.capture_event`). """ if self.client is None: return None if level is None: level = "info" return self.capture_event({ "message": partial_serialize(self.client, message, should_repr_strings=False), "level": level, })
def _prepare_event( self, event, # type: Event hint, # type: Optional[Hint] scope, # type: Optional[Scope] ): # type: (...) -> Optional[Event] client = self # type: Client # type: ignore if event.get("timestamp") is None: event["timestamp"] = partial_serialize(client, datetime.utcnow(), is_databag=False, should_repr_strings=False) hint = dict(hint or ()) # type: Hint if scope is not None: event_ = scope.apply_to_event(event, hint) if event_ is None: return None event = event_ if (self.options["attach_stacktrace"] and "exception" not in event and "stacktrace" not in event and "threads" not in event): with capture_internal_exceptions(): event["threads"] = { "values": [{ "stacktrace": current_stacktrace(self.options["with_locals"]), "crashed": False, "current": True, }] } for key in "release", "environment", "server_name", "dist": if event.get(key) is None and self.options[key] is not None: event[key] = text_type(self.options[key]).strip() if event.get("sdk") is None: sdk_info = dict(SDK_INFO) sdk_info["integrations"] = sorted(self.integrations.keys()) event["sdk"] = sdk_info if event.get("platform") is None: event["platform"] = "python" event = handle_in_app(event, self.options["in_app_exclude"], self.options["in_app_include"]) # Postprocess the event here so that annotated types do # generally not surface in before_send if event is not None and not self.options["_experiments"].get( "fast_serialize", False): event = serialize(event) before_send = self.options["before_send"] if before_send is not None: new_event = None with capture_internal_exceptions(): new_event = before_send(event, hint or {}) if new_event is None: logger.info("before send dropped event (%s)", event) event = new_event # type: ignore return event
def set_data(self, key, value): # type: (str, Any) -> None self._data[key] = partial_serialize( sentry_sdk.Hub.current.client, value, should_repr_strings=False )