culprit.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. """
  2. This file implements the legacy culprit system. The culprit at this point is
  3. just used as a fallback if no transaction is set. When a transaction is set
  4. the culprit is overridden by the transaction value.
  5. Over time we want to fully phase out the culprit. Until then this is the
  6. code that generates it.
  7. """
  8. from sentry.constants import MAX_CULPRIT_LENGTH
  9. from sentry.utils.safe import get_path
  10. from sentry.utils.strings import truncatechars
  11. def generate_culprit(data):
  12. platform = data.get("platform")
  13. if exceptions := get_path(data, "exception", filter=True):
  14. if isinstance(exceptions, dict):
  15. exceptions = get_path(exceptions, "values", filter=True)
  16. # Synthetic events no longer get a culprit
  17. last_exception = get_path(exceptions, -1)
  18. if get_path(last_exception, "mechanism", "synthetic"):
  19. return ""
  20. stacktraces = [
  21. e["stacktrace"] for e in exceptions if get_path(e, "stacktrace", "frames")
  22. ]
  23. else:
  24. stacktrace = data.get("stacktrace")
  25. if stacktrace and stacktrace.get("frames"):
  26. stacktraces = [stacktrace]
  27. else:
  28. stacktraces = None
  29. culprit = None
  30. if not culprit and stacktraces:
  31. culprit = get_stacktrace_culprit(get_path(stacktraces, -1), platform=platform)
  32. if not culprit and data.get("request"):
  33. culprit = get_path(data, "request", "url")
  34. return truncatechars(culprit or "", MAX_CULPRIT_LENGTH)
  35. def get_stacktrace_culprit(stacktrace, platform):
  36. default = None
  37. for frame in reversed(stacktrace["frames"]):
  38. if not frame:
  39. continue
  40. if frame.get("in_app"):
  41. culprit = get_frame_culprit(frame, platform=platform)
  42. if culprit:
  43. return culprit
  44. elif default is None:
  45. default = get_frame_culprit(frame, platform=platform)
  46. return default
  47. def get_frame_culprit(frame, platform):
  48. # If this frame has a platform, we use it instead of the one that
  49. # was passed in (as that one comes from the exception which might
  50. # not necessarily be the same platform).
  51. platform = frame.get("platform") or platform
  52. if platform in ("objc", "cocoa", "native"):
  53. return frame.get("function") or "?"
  54. fileloc = frame.get("module") or frame.get("filename")
  55. if not fileloc:
  56. return ""
  57. elif platform in ("javascript", "node"):
  58. # function and fileloc might be unicode here, so let it coerce
  59. # to a unicode string if needed.
  60. return "%s(%s)" % (frame.get("function") or "?", fileloc)
  61. return "%s in %s" % (fileloc, frame.get("function") or "?")