mirror of
https://github.com/bugsink/bugsink.git
synced 2025-12-30 09:50:11 -06:00
@@ -1,9 +1,12 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import IngestEventAPIView, IngestEnvelopeAPIView
|
||||
from .views import IngestEventAPIView, IngestEnvelopeAPIView, MinidumpAPIView
|
||||
|
||||
urlpatterns = [
|
||||
# project_pk has to be an int per Sentry Client expectations.
|
||||
path("<int:project_pk>/store/", IngestEventAPIView.as_view()),
|
||||
path("<int:project_pk>/envelope/", IngestEnvelopeAPIView.as_view()),
|
||||
|
||||
# is this "ingest"? it is at least in the sense that it matches the API schema and downstream auth etc.
|
||||
path("<int:project_pk>/minidump/", MinidumpAPIView.as_view()),
|
||||
]
|
||||
|
||||
@@ -39,6 +39,7 @@ from alerts.tasks import send_new_issue_alert, send_regression_alert
|
||||
from compat.timestamp import format_timestamp, parse_timestamp
|
||||
from tags.models import digest_tags
|
||||
from bsmain.utils import b108_makedirs
|
||||
from sentry.minidump import merge_minidump_event
|
||||
|
||||
from .parsers import StreamingEnvelopeParser, ParseError
|
||||
from .filestore import get_filename_for_event_id
|
||||
@@ -680,6 +681,59 @@ class IngestEnvelopeAPIView(BaseIngestAPIView):
|
||||
#
|
||||
|
||||
|
||||
class MinidumpAPIView(BaseIngestAPIView):
|
||||
# A Base "Ingest" APIView in the sense that it reuses some key building blocks (auth).
|
||||
# I'm not 100% sure whether "philosophically" the minidump endpoint is also "ingesting"; we'll see.
|
||||
|
||||
@classmethod
|
||||
def _ingest(cls, ingested_at, event_data, project, request):
|
||||
# TSTTCPW: just ingest the invent as normally after we've done the minidump-parsing "immediately". We make
|
||||
# ready for the expectations of process_event (DIGEST_IMMEDIATELY/event_output_stream) with an if-statement
|
||||
|
||||
event_output_stream = MaxDataWriter("MAX_EVENT_SIZE", io.BytesIO())
|
||||
if get_settings().DIGEST_IMMEDIATELY:
|
||||
# in this case the stream will be an BytesIO object, so we can actually call .get_value() on it.
|
||||
event_output_stream.write(json.dumps(event_data).encode("utf-8"))
|
||||
|
||||
else:
|
||||
# no need to actually touch event_output_stream for this case, we just need to write a file
|
||||
filename = get_filename_for_event_id(event_data["event_id"])
|
||||
b108_makedirs(os.path.dirname(filename))
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(event_data, f)
|
||||
|
||||
cls.process_event(ingested_at, event_data["event_id"], event_output_stream, project, request)
|
||||
|
||||
def post(self, request, project_pk=None):
|
||||
# not reusing the CORS stuff here; minidump-from-browser doesn't make sense.
|
||||
|
||||
ingested_at = datetime.now(timezone.utc)
|
||||
project = self.get_project_for_request(project_pk, request)
|
||||
|
||||
try:
|
||||
# in this flow, we don't get an event_id from the client, so we just generate one here.
|
||||
event_id = uuid.uuid4().hex
|
||||
|
||||
minidump_bytes = request.FILES["upload_file_minidump"].read()
|
||||
|
||||
data = {
|
||||
"event_id": event_id,
|
||||
"platform": "native",
|
||||
"extra": {},
|
||||
"errors": [],
|
||||
}
|
||||
|
||||
merge_minidump_event(data, minidump_bytes)
|
||||
|
||||
self._ingest(ingested_at, data, project, request)
|
||||
|
||||
return JsonResponse({"id": event_id})
|
||||
|
||||
except Exception as e:
|
||||
raise
|
||||
return JsonResponse({"detail": str(e)}, status=HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def download_envelope(request, envelope_id=None):
|
||||
envelope = get_object_or_404(Envelope, pk=envelope_id)
|
||||
|
||||
@@ -66,9 +66,9 @@
|
||||
|
||||
<div class="text-ellipsis overflow-hidden"> {# filename, function, lineno #}
|
||||
{% if frame.in_app %}
|
||||
<span class="font-bold">{{ frame.filename }}</span>{% if frame.function %} in <span class="font-bold">{{ frame.function }}</span>{% endif %}{% if frame.lineno %} line <span class="font-bold">{{ frame.lineno }}</span>{% endif %}.
|
||||
<span class="font-bold">{{ frame.filename }}</span>{% if frame.function %} in <span class="font-bold">{{ frame.function }}</span>{% endif %}{% if frame.lineno %} line <span class="font-bold">{{ frame.lineno }}</span>{% endif %}{% if frame.instruction_addr %} {{ frame.instruction_addr }}{% endif %}.
|
||||
{% else %}
|
||||
<span class="italic">{{ frame.filename }}{% if frame.function %} in {{ frame.function }}{% endif %}{% if frame.lineno%} line {{ frame.lineno }}{% endif %}.</span>
|
||||
<span class="italic">{{ frame.filename }}{% if frame.function %} in {{ frame.function }}{% endif %}{% if frame.lineno%} line {{ frame.lineno }}{% endif %}{% if frame.instruction_addr %} {{ frame.instruction_addr }}{% endif %}.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,24 +6,15 @@ import logging
|
||||
from symbolic import ProcessState
|
||||
|
||||
|
||||
LOG_LEVELS = {
|
||||
logging.NOTSET: "sample",
|
||||
logging.DEBUG: "debug",
|
||||
logging.INFO: "info",
|
||||
logging.WARNING: "warning",
|
||||
logging.ERROR: "error",
|
||||
logging.FATAL: "fatal",
|
||||
}
|
||||
def merge_minidump_event(data, minidump_bytes):
|
||||
state = ProcessState.from_minidump_buffer(minidump_bytes)
|
||||
|
||||
LOG_LEVELS_MAP = {v: k for k, v in LOG_LEVELS.items()}
|
||||
data['level'] = 'fatal' if state.crashed else 'info'
|
||||
|
||||
|
||||
def merge_minidump_event(data, minidump_path):
|
||||
state = ProcessState.from_minidump(minidump_path)
|
||||
|
||||
data['level'] = LOG_LEVELS_MAP['fatal'] if state.crashed else LOG_LEVELS_MAP['info']
|
||||
data['message'] = 'Assertion Error: %s' % state.assertion if state.assertion \
|
||||
exception_value = 'Assertion Error: %s' % state.assertion if state.assertion \
|
||||
else 'Fatal Error: %s' % state.crash_reason
|
||||
# NO_BANANA: data['message'] is not the right target
|
||||
# data['message'] = exception_value
|
||||
|
||||
if state.timestamp:
|
||||
data['timestamp'] = float(state.timestamp)
|
||||
@@ -62,7 +53,7 @@ def merge_minidump_event(data, minidump_path):
|
||||
|
||||
# Extract the crash reason and infos
|
||||
exception = {
|
||||
'value': data['message'],
|
||||
'value': exception_value,
|
||||
'thread_id': crashed_thread['id'],
|
||||
'type': state.crash_reason,
|
||||
# Move stacktrace here from crashed_thread (mutating!)
|
||||
|
||||
Reference in New Issue
Block a user