# -*- coding: utf-8 -*- # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from datetime import timedelta from appenlight.lib.enums import LogLevelPython, ParsedSentryEventType EXCLUDED_LOG_VARS = [ 'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename', 'funcName', 'levelname', 'levelno', 'lineno', 'message', 'module', 'msecs', 'msg', 'name', 'pathname', 'process', 'processName', 'relativeCreated', 'thread', 'threadName'] EXCLUDE_SENTRY_KEYS = [ 'csp', 'culprit', 'event_id', 'exception', 'extra', 'level', 'logentry', 'logger', 'message', 'modules', 'platform', 'query', 'release', 'request', 'sentry.interfaces.Csp', 'sentry.interfaces.Exception', 'sentry.interfaces.Http', 'sentry.interfaces.Message', 'sentry.interfaces.Query', 'sentry.interfaces.Stacktrace', 'sentry.interfaces.Template', 'sentry.interfaces.User', 'sentry.interfaces.csp.Csp', 'sentry.interfaces.exception.Exception', 'sentry.interfaces.http.Http', 'sentry.interfaces.message.Message', 'sentry.interfaces.query.Query', 'sentry.interfaces.stacktrace.Stacktrace', 'sentry.interfaces.template.Template', 'sentry.interfaces.user.User', 'server_name', 'stacktrace', 'tags', 'template', 'time_spent', 'timestamp', 'user'] def get_keys(list_of_keys, json_body): for k in list_of_keys: if k in json_body: return json_body[k] def get_logentry(json_body): key_names = ['logentry', 'sentry.interfaces.message.Message', 'sentry.interfaces.Message' ] logentry = get_keys(key_names, json_body) return logentry def get_exception(json_body): parsed_exception = {} key_names = ['exception', 'sentry.interfaces.exception.Exception', 'sentry.interfaces.Exception' ] exception = get_keys(key_names, json_body) or {} if exception: if isinstance(exception, dict): exception = exception['values'][0] else: exception = exception[0] parsed_exception['type'] = exception.get('type') parsed_exception['value'] = exception.get('value') parsed_exception['module'] = exception.get('module') parsed_stacktrace = get_stacktrace(exception) or {} parsed_exception = exception or {} return parsed_exception, parsed_stacktrace def get_stacktrace(json_body): parsed_stacktrace = [] key_names = ['stacktrace', 'sentry.interfaces.stacktrace.Stacktrace', 'sentry.interfaces.Stacktrace' ] stacktrace = get_keys(key_names, json_body) if stacktrace: for frame in stacktrace['frames']: parsed_stacktrace.append( {"cline": frame.get('context_line', ''), "file": frame.get('filename', ''), "module": frame.get('module', ''), "fn": frame.get('function', ''), "line": frame.get('lineno', ''), "vars": list(frame.get('vars', {}).items()) } ) return parsed_stacktrace def get_template(json_body): parsed_template = {} key_names = ['template', 'sentry.interfaces.template.Template', 'sentry.interfaces.Template' ] template = get_keys(key_names, json_body) if template: for frame in template['frames']: parsed_template.append( {"cline": frame.get('context_line', ''), "file": frame.get('filename', ''), "fn": '', "line": frame.get('lineno', ''), "vars": [] } ) return parsed_template def get_request(json_body): parsed_http = {} key_names = ['request', 'sentry.interfaces.http.Http', 'sentry.interfaces.Http' ] http = get_keys(key_names, json_body) or {} for k, v in http.items(): if k == 'headers': parsed_http['headers'] = {} for sk, sv in http['headers'].items(): parsed_http['headers'][sk.title()] = sv else: parsed_http[k.lower()] = v return parsed_http def get_user(json_body): parsed_user = {} key_names = ['user', 'sentry.interfaces.user.User', 'sentry.interfaces.User' ] user = get_keys(key_names, json_body) if user: parsed_user['id'] = user.get('id') parsed_user['username'] = user.get('username') parsed_user['email'] = user.get('email') parsed_user['ip_address'] = user.get('ip_address') return parsed_user def get_query(json_body): query = None key_name = ['query', 'sentry.interfaces.query.Query', 'sentry.interfaces.Query' ] query = get_keys(key_name, json_body) return query def parse_sentry_event(json_body): request_id = json_body.get('event_id') # required message = json_body.get('message') log_timestamp = json_body.get('timestamp') level = json_body.get('level') if isinstance(level, int): level = LogLevelPython.key_from_value(level) namespace = json_body.get('logger') language = json_body.get('platform') # optional server_name = json_body.get('server_name') culprit = json_body.get('culprit') release = json_body.get('release') tags = json_body.get('tags', {}) if hasattr(tags, 'items'): tags = list(tags.items()) extra = json_body.get('extra', {}) if hasattr(extra, 'items'): extra = list(extra.items()) parsed_req = get_request(json_body) user = get_user(json_body) template = get_template(json_body) query = get_query(json_body) # other unidentified keys found other_keys = [(k, json_body[k]) for k in json_body.keys() if k not in EXCLUDE_SENTRY_KEYS] logentry = get_logentry(json_body) if logentry: message = logentry['message'] exception, stacktrace = get_exception(json_body) alt_stacktrace = get_stacktrace(json_body) event_type = None if not exception and not stacktrace and not alt_stacktrace and not template: event_type = ParsedSentryEventType.LOG event_dict = { 'log_level': level, 'message': message, 'namespace': namespace, 'request_id': request_id, 'server': server_name, 'date': log_timestamp, 'tags': tags } event_dict['tags'].extend( [(k, v) for k, v in extra if k not in EXCLUDED_LOG_VARS]) # other keys can be various object types event_dict['tags'].extend([(k, v) for k, v in other_keys if isinstance(v, str)]) if culprit: event_dict['tags'].append(('sentry_culprit', culprit)) if language: event_dict['tags'].append(('sentry_language', language)) if release: event_dict['tags'].append(('sentry_release', release)) if exception or stacktrace or alt_stacktrace or template: event_type = ParsedSentryEventType.ERROR_REPORT event_dict = { 'client': 'sentry', 'error': message, 'namespace': namespace, 'request_id': request_id, 'server': server_name, 'start_time': log_timestamp, 'end_time': None, 'tags': tags, 'extra': extra, 'language': language, 'view_name': json_body.get('culprit'), 'http_status': None, 'username': None, 'url': parsed_req.get('url'), 'ip': None, 'user_agent': None, 'request': None, 'slow_calls': None, 'request_stats': None, 'traceback': None } event_dict['extra'].extend(other_keys) if release: event_dict['tags'].append(('sentry_release', release)) event_dict['request'] = parsed_req if 'headers' in parsed_req: event_dict['user_agent'] = parsed_req['headers'].get('User-Agent') if 'env' in parsed_req: event_dict['ip'] = parsed_req['env'].get('REMOTE_ADDR') ts_ms = int(json_body.get('time_spent') or 0) if ts_ms > 0: event_dict['end_time'] = event_dict['start_time'] + \ timedelta(milliseconds=ts_ms) if stacktrace or alt_stacktrace or template: event_dict['traceback'] = stacktrace or alt_stacktrace or template for k in list(event_dict.keys()): if event_dict[k] is None: del event_dict[k] if user: event_dict['username'] = user['username'] or user['id'] \ or user['email'] return event_dict, event_type