# -*- 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