Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import redis
- from daemon import DaemonContext
- import pprint
- import mysql.connector
- import json
- import base64
- import zlib
- import io
- import struct
- import logging
- import re
- import os
- import sys
- file_root = '/opt/crash-handler/files'
- def read_compressed_file(data):
- current_file_index = struct.unpack("i", data.read(4))[0]
- #print(current_file_index)
- filename_len = struct.unpack("i", data.read(4))[0]
- #print(filename_len)
- fmt = "{}s".format(filename_len)
- filename = struct.unpack(fmt, data.read(filename_len))[0].split(b'\0', 1)[0]
- #print(filename)
- contents_len = struct.unpack("i", data.read(4))[0]
- #print(contents_len)
- fmt = "{}s".format(contents_len)
- contents = struct.unpack(fmt, data.read(contents_len))[0]
- #if filename[-3:] in ['wer', 'log', 'xml', 'txt']:
- # print(">>>> {}".format(filename))
- # print(contents)
- # print("<<<<")
- #print("read {}".format(filename))
- file = {
- 'filename' : filename,
- 'contents' : contents,
- }
- return file
- def read_uploaded_files(compressed_data):
- uncompressed_data = zlib.decompress(compressed_data)
- print("decompressed {} to {}".format(human_bytes(len(compressed_data)), human_bytes(len(uncompressed_data))))
- files = []
- data = io.BytesIO(uncompressed_data)
- while data.tell() < len(uncompressed_data):
- files.extend([read_compressed_file(data)])
- return files
- def human_bytes(num, suffix='B'):
- for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
- if abs(num) < 1024.0:
- return "%3.1f%s%s" % (num, unit, suffix)
- num /= 1024.0
- return "%.1f%s%s" % (num, 'Yi', suffix)
- def on_crash_created(raw_data):
- try:
- report = json.loads(raw_data)
- # make sure id is clean by only keeping hexadecimal characters
- report['uuid'] = re.sub(r'[^0-9a-f]', '', report['id'])
- report['id'] = None # we call it uuid
- decoded_data = base64.b64decode(report['payload'])
- files = read_uploaded_files(decoded_data)
- if create_report(report):
- for file in files:
- save_file(file, report['uuid'])
- update_derived_data(report['uuid'])
- except Exception, e:
- print(str(e))
- pass
- def report_fields():
- return [
- 'uuid',
- 'reported_at',
- 'reported_by',
- 'user',
- 'computer',
- 'source_file',
- 'source_context',
- 'callstack',
- 'game_version',
- ]
- def create_report(report):
- try:
- conn = mysql.connector.connect(user='root',
- password='supersecret',
- host='localhost',
- database='crash',
- connection_timeout=5)
- cursor = conn.cursor()
- fields = filter(lambda x : x in report, report_fields())
- query = ("INSERT INTO reports "
- "({}) "
- "VALUES ({})".format(
- ", ".join(fields),
- ", ".join(map(lambda x: "%({})s".format(x), fields))))
- print(query)
- cursor.execute(query, report)
- conn.commit()
- cursor.close()
- conn.close()
- print("report saved")
- return True
- except Exception, e:
- logging.exception('report not saved')
- if cursor:
- print(cursor.statement or 'no statement')
- return False
- def save_report(report):
- try:
- conn = mysql.connector.connect(user='crash',
- password='supersecret',
- host='localhost',
- database='crash',
- connection_timeout=5)
- cursor = conn.cursor()
- fields = report_fields()
- query = ("UPDATE reports "
- "SET {} "
- "WHERE uuid = %(uuid)s "
- "LIMIT 1".format(
- ", ".join(map(lambda x: "{0}=%({0})s".format(x), fields))))
- cursor.execute(query, report)
- conn.commit()
- cursor.close()
- conn.close()
- print("report saved")
- return True
- except Exception, e:
- logging.exception('report not saved')
- if cursor:
- print(cursor.statement or 'no statement')
- return False
- def save_file_to_storage(file, report_uuid):
- root = file_root
- d = os.path.join(root, report_uuid)
- if not os.path.exists(d):
- os.mkdir(d)
- print("dir is {}".format(d))
- filepath = os.path.join(d, file['filename'])
- if not os.path.realpath(filepath).startswith(d):
- raise Exception("Path {} is not in directory {}, will not continue".format(filepath, d))
- with open(filepath, 'wb') as f:
- f.write(file['contents'])
- print("wrote {} to {}".format(human_bytes(len(file['filename'])), os.path.realpath(filepath)))
- def load_file_from_storage(file, report_uuid):
- root = file_root
- filepath = os.path.join(root, report_uuid, file['filename'])
- if not os.path.exists(filepath):
- return None
- with open(filepath, 'rb') as f:
- file['contents'] = f.read()
- return file
- def save_file(file, report_uuid):
- try:
- save_file_to_storage(file, report_uuid)
- conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
- cursor = conn.cursor()
- existing_id = get_file_id(file, report_uuid)
- if not existing_id:
- query = ("INSERT INTO files "
- "(report_uuid, filename, size) "
- "VALUES (%(report_uuid)s, %(filename)s, %(size)s)")
- data = {
- 'report_uuid': report_uuid,
- 'filename': file['filename'],
- 'size': len(file['contents']),
- }
- cursor.execute(query, data)
- conn.commit()
- else:
- # file entry already exists, update it
- query = ("UPDATE files "
- "SET size = %(size)s "
- "WHERE id = %(id)s LIMIT 1")
- data = {
- 'size': len(file['contents']),
- 'id': existing_id,
- }
- cursor.execute(query, data)
- conn.commit()
- cursor.close()
- conn.close()
- print("file {} saved".format(file['filename']))
- return True
- except Exception, e:
- logging.exception('file not saved')
- if cursor:
- print(cursor.statement or 'no statement')
- return False
- def get_file_id(file, report_uuid):
- """returns row id if already exists in db"""
- try:
- conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
- cursor = conn.cursor()
- query = ("SELECT id FROM files "
- "WHERE report_uuid = %(report_uuid)s AND filename = %(filename)s")
- data = {
- 'report_uuid': report_uuid,
- 'filename': file['filename'],
- }
- cursor.execute(query, data)
- found_id = None
- row = cursor.fetchone()
- if row:
- found_id = row[0]
- print("{} for {} exists as id {}".format(file['filename'], report_uuid, found_id))
- cursor.close()
- conn.close()
- return found_id
- except Exception, e:
- logging.exception('unable to find file in db')
- return None
- def get_all_report_ids():
- conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
- cursor = conn.cursor()
- ids = []
- try:
- query = ("SELECT uuid FROM reports "
- "ORDER BY reported_at DESC")
- cursor.execute(query)
- for (uuid,) in cursor:
- ids.extend([uuid])
- except Exception, e:
- logging.exception(e)
- finally:
- cursor.close()
- conn.close()
- return ids
- def get_report_by_id(report_uuid):
- conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
- cursor = conn.cursor()
- report = None
- try:
- query = ("SELECT uuid, reported_by, reported_at, user, computer, source_file, source_context, callstack FROM reports "
- "WHERE uuid = %(uuid)s LIMIT 1")
- data = {'uuid': report_uuid}
- cursor.execute(query, data)
- row = cursor.fetchone()
- if row:
- (uuid, reported_by, reported_at, user, computer, source_file, source_context, callstack) = row
- report = {
- 'uuid': uuid,
- 'reported_by': reported_by,
- 'reported_at': reported_at,
- 'user': user,
- 'computer': computer,
- 'source_file': source_file,
- 'source_context': source_context,
- 'callstack': callstack,
- }
- except Exception, e:
- logging.exception(e)
- finally:
- cursor.close()
- conn.close()
- return report
- def get_filenames(report_uuid):
- conn = mysql.connector.connect(user='crash', password='supersecret', host='localhost', database='crash')
- cursor = conn.cursor()
- filenames = []
- try:
- query = ("SELECT filename FROM files "
- "WHERE report_uuid = %(report_uuid)s ")
- data = {
- 'report_uuid': report_uuid
- }
- cursor.execute(query, data)
- for (filename,) in cursor:
- filenames.extend([filename])
- except Exception, e:
- logging.exception(e)
- finally:
- cursor.close()
- conn.close()
- return filenames
- def get_file_by_name(report_uuid, filename):
- filenames = get_filenames(report_uuid)
- for name in filenames:
- if name == filename:
- file = {'filename': name}
- file = load_file_from_storage(file, report_uuid)
- if file:
- return file['contents']
- else:
- return None
- return None
- def get_first_match_from_file(report_uuid, filename, regex, flags=re.MULTILINE):
- file = get_file_by_name(report_uuid, filename)
- if file:
- m = re.search(regex, file, flags)
- if m and m.group(1):
- return m.group(1).strip()
- return None
- def get_dd_game_version(report_uuid):
- return get_first_match_from_file(report_uuid, 'Diagnostics.txt', r'^Game version (.*)$')
- def get_dd_user(report_uuid):
- return get_first_match_from_file(report_uuid, 'MyGame.log', r'.*LogInit: User: ([\w\\]*)')
- def get_dd_computer(report_uuid):
- return get_first_match_from_file(report_uuid, 'MyGame.log', r'.*LogInit: Computer: ([\w\\]*)')
- def get_dd_source_file(report_uuid):
- return get_first_match_from_file(report_uuid, 'Diagnostics.txt', r'^Source context from "(.*)"')
- def get_dd_source_context(report_uuid):
- return get_first_match_from_file(report_uuid, 'Diagnostics.txt', r'<SOURCE START>(.*)<SOURCE END>', re.MULTILINE|re.DOTALL)
- def get_dd_callstack(report_uuid):
- return get_first_match_from_file(report_uuid, 'Diagnostics.txt', r'<CALLSTACK START>(.*)<CALLSTACK END>', re.MULTILINE|re.DOTALL)
- def update_derived_data(report_uuid):
- """updates data saved in db but read from files, can be run again to refresh"""
- report = get_report_by_id(report_uuid)
- if not report:
- raise Exception("Unable to update derived data for uuid {}, report not found".format(report_uuid))
- report['game_version'] = get_dd_game_version(report_uuid)
- report['user'] = get_dd_user(report_uuid)
- report['computer'] = get_dd_computer(report_uuid)
- report['source_file'] = get_dd_source_file(report_uuid)
- report['source_context'] = get_dd_source_context(report_uuid)
- report['callstack'] = get_dd_callstack(report_uuid)
- save_report(report)
- def update_all_derived_data():
- ids = get_all_report_ids()
- for id in ids:
- update_derived_data(id)
- def main():
- r = redis.Redis(host='redis.example.com')
- pubsub = r.pubsub()
- try:
- pubsub.subscribe(['crash.created'])
- for item in pubsub.listen():
- if item['type'] == 'message':
- print("data size: {}".format(human_bytes(len(item['data']))))
- on_crash_created(item['data'])
- finally:
- pubsub.close()
- if __name__ == '__main__':
- #with DaemonContext():
- if len(sys.argv) > 1:
- cmd = sys.argv[1]
- if cmd == 'update_all_derived_data':
- update_all_derived_data()
- else:
- print('unknown command {}'.format(cmd))
- else:
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement