Completed
Push — master ( 3a5649...6a4ed1 )
by
unknown
42s
created

send_stacktrace_sentry()   C

Complexity

Conditions 7

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 7
dl 0
loc 41
rs 5.5
c 3
b 0
f 0
1
# coding: utf8
2
3
"""
4
This software is licensed under the Apache 2 license, quoted below.
5
6
Copyright 2014 Crystalnix Limited
7
8
Licensed under the Apache License, Version 2.0 (the "License"); you may not
9
use this file except in compliance with the License. You may obtain a copy of
10
the License at
11
12
    http://www.apache.org/licenses/LICENSE-2.0
13
14
Unless required by applicable law or agreed to in writing, software
15
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17
License for the specific language governing permissions and limitations under
18
the License.
19
"""
20
21
from builtins import str
22
23
import os
24
import re
25
26
from django.conf import settings
27
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist, ValidationError
28
29
from clom import clom
30
from celery import signature
31
32
from crash.settings import MINIDUMP_STACKWALK_PATH, SYMBOLS_PATH
33
from crash.stacktrace_to_json import pipe_dump_to_json_dump
34
from crash.senders import get_sender
35
from omaha.models import Version
36
from sparkle.models import SparkleVersion
37
38
39
# This gets either a SentrySender or an ELKSender depending on config.
40
crash_sender = get_sender()
41
42
43
class FileNotFoundError(Exception):
44
    pass
45
46
47
minidump_stackwalk = clom[MINIDUMP_STACKWALK_PATH].with_opts('-m')
48
49
50
def get_stacktrace(crashdump_path):
51
    if not os.path.isfile(crashdump_path):
52
        raise FileNotFoundError
53
54
    result = minidump_stackwalk(crashdump_path, SYMBOLS_PATH).shell()
55
    return result
56
57
58
def add_signature_to_frame(frame):
59
    frame = frame.copy()
60
    if 'function' in frame:
61
        # Remove spaces before all stars, ampersands, and commas
62
        function = re.sub(' (?=[\*&,])', '', frame['function'])
63
        # Ensure a space after commas
64
        function = re.sub(',(?! )', ', ', function)
65
        frame['function'] = function
66
        signature = function
67
    elif 'abs_path' in frame and 'lineno' in frame:
68
        signature = '%s#%d' % (frame['abs_path'], frame['lineno'])
69
    elif 'filename' in frame and 'module_offset' in frame:
70
        signature = '%s@%s' % (frame['filename'], frame['module_offset'])
71
    else:
72
        signature = '@%s' % frame['offset']
73
    frame['signature'] = signature
74
    frame['short_signature'] = re.sub('\(.*\)', '', signature)
75
    return frame
76
77
78
def parse_stacktrace(stacktrace):
79
    stacktrace_dict = pipe_dump_to_json_dump(str(stacktrace).splitlines())
80
    stacktrace_dict['crashing_thread']['frames'] = list(
81
        map(add_signature_to_frame,
82
            stacktrace_dict['crashing_thread']['frames']))
83
    return dict(stacktrace_dict)
84
85
86
def get_signature(stacktrace):
87
    try:
88
        frame = stacktrace['crashing_thread']['frames'][0]
89
        signature = frame['signature']
90
    except (KeyError, IndexError):
91
        signature = 'EMPTY: no frame data available'
92
    return signature
93
94
95
def get_os(stacktrace):
96
    return stacktrace.get('system_info', {}).get('os', '') if stacktrace else ''
97
98
99
def send_stacktrace(crash):
100
    stacktrace = crash.stacktrace_json
101
    exception = {
102
        "values": [
103
            {
104
                "type": stacktrace.get('crash_info', {}).get('type', 'unknown exception'),
105
                "value": stacktrace.get('crash_info', {}).get('crash_address', '0x0'),
106
                "stacktrace": stacktrace['crashing_thread']
107
            }
108
        ]
109
    }
110
111
    sentry_data = {'sentry.interfaces.Exception': exception}
112
113
    if crash.userid:
114
        sentry_data['sentry.interfaces.User'] = dict(id=crash.userid)
115
116
    extra = dict(
117
        crash_admin_panel_url='http://{}{}'.format(
118
            settings.HOST_NAME,
119
            '/admin/crash/crash/%s/' % crash.pk),
120
        crashdump_url=crash.upload_file_minidump.url,
121
    )
122
123
    tags = {}
124
    if crash.meta:
125
        extra.update(crash.meta)
126
        ver = crash.meta.get('ver')
127
        if ver:
128
            tags['ver'] = ver
129
    if crash.channel:
130
        tags['channel'] = crash.channel
131
    if crash.archive:
132
        extra['archive_url'] = crash.archive.url
133
134
    tags.update(stacktrace.get('system_info', {}))
135
136
    if crash.appid:
137
        tags['appid'] = crash.appid
138
139
    crash_sender.send(crash.signature, extra=extra, tags=tags, sentry_data=sentry_data, crash_obj=crash)
140
141
142
def parse_debug_meta_info(head, exception=Exception):
143
    head = head.decode()
144
    head_list = head.split(' ', 4)
145
    if head_list[0] != 'MODULE':
146
        raise exception(u"The file contains invalid data.")
147
    return dict(debug_id=head_list[-2],
148
                debug_file=head_list[-1])
149
150
151
def get_channel(build_number, os):
152
    try:
153
        if os == 'Mac OS X':      # We expect that sparkle supports only Mac platform
154
            version = SparkleVersion.objects.select_related('channel').get(short_version=build_number)
155
        else:                       # All other platforms will be related to Omaha
156
            version = Version.objects.select_related('channel').get(version=build_number)
157
    except (MultipleObjectsReturned, ObjectDoesNotExist, ValidationError):
158
        return 'undefined'
159
    return version.channel.name