sentry.py
317 lines
| 9.3 KiB
| text/x-python
|
PythonLexer
r2 | # -*- coding: utf-8 -*- | |||
r112 | # Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors | |||
r2 | # | |||
r112 | # 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 | ||||
r2 | # | |||
r112 | # http://www.apache.org/licenses/LICENSE-2.0 | |||
r2 | # | |||
r112 | # 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. | ||||
r2 | ||||
from datetime import timedelta | ||||
from appenlight.lib.enums import LogLevelPython, ParsedSentryEventType | ||||
EXCLUDED_LOG_VARS = [ | ||||
r153 | "args", | |||
"asctime", | ||||
"created", | ||||
"exc_info", | ||||
"exc_text", | ||||
"filename", | ||||
"funcName", | ||||
"levelname", | ||||
"levelno", | ||||
"lineno", | ||||
"message", | ||||
"module", | ||||
"msecs", | ||||
"msg", | ||||
"name", | ||||
"pathname", | ||||
"process", | ||||
"processName", | ||||
"relativeCreated", | ||||
"thread", | ||||
"threadName", | ||||
] | ||||
r2 | ||||
EXCLUDE_SENTRY_KEYS = [ | ||||
r153 | "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", | ||||
] | ||||
r2 | ||||
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): | ||||
r153 | key_names = [ | |||
"logentry", | ||||
"sentry.interfaces.message.Message", | ||||
"sentry.interfaces.Message", | ||||
] | ||||
r2 | logentry = get_keys(key_names, json_body) | |||
return logentry | ||||
def get_exception(json_body): | ||||
parsed_exception = {} | ||||
r153 | key_names = [ | |||
"exception", | ||||
"sentry.interfaces.exception.Exception", | ||||
"sentry.interfaces.Exception", | ||||
] | ||||
r2 | exception = get_keys(key_names, json_body) or {} | |||
if exception: | ||||
if isinstance(exception, dict): | ||||
r153 | exception = exception["values"][0] | |||
r2 | else: | |||
exception = exception[0] | ||||
r153 | parsed_exception["type"] = exception.get("type") | |||
parsed_exception["value"] = exception.get("value") | ||||
parsed_exception["module"] = exception.get("module") | ||||
r2 | parsed_stacktrace = get_stacktrace(exception) or {} | |||
parsed_exception = exception or {} | ||||
return parsed_exception, parsed_stacktrace | ||||
def get_stacktrace(json_body): | ||||
parsed_stacktrace = [] | ||||
r153 | key_names = [ | |||
"stacktrace", | ||||
"sentry.interfaces.stacktrace.Stacktrace", | ||||
"sentry.interfaces.Stacktrace", | ||||
] | ||||
r2 | stacktrace = get_keys(key_names, json_body) | |||
if stacktrace: | ||||
r153 | for frame in stacktrace["frames"]: | |||
r2 | parsed_stacktrace.append( | |||
r153 | { | |||
"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()), | ||||
} | ||||
r2 | ) | |||
return parsed_stacktrace | ||||
def get_template(json_body): | ||||
parsed_template = {} | ||||
r153 | key_names = [ | |||
"template", | ||||
"sentry.interfaces.template.Template", | ||||
"sentry.interfaces.Template", | ||||
] | ||||
r2 | template = get_keys(key_names, json_body) | |||
if template: | ||||
r153 | for frame in template["frames"]: | |||
r2 | parsed_template.append( | |||
r153 | { | |||
"cline": frame.get("context_line", ""), | ||||
"file": frame.get("filename", ""), | ||||
"fn": "", | ||||
"line": frame.get("lineno", ""), | ||||
"vars": [], | ||||
} | ||||
r2 | ) | |||
return parsed_template | ||||
def get_request(json_body): | ||||
parsed_http = {} | ||||
r153 | key_names = ["request", "sentry.interfaces.http.Http", "sentry.interfaces.Http"] | |||
r2 | http = get_keys(key_names, json_body) or {} | |||
for k, v in http.items(): | ||||
r153 | if k == "headers": | |||
parsed_http["headers"] = {} | ||||
for sk, sv in http["headers"].items(): | ||||
parsed_http["headers"][sk.title()] = sv | ||||
r2 | else: | |||
parsed_http[k.lower()] = v | ||||
return parsed_http | ||||
def get_user(json_body): | ||||
parsed_user = {} | ||||
r153 | key_names = ["user", "sentry.interfaces.user.User", "sentry.interfaces.User"] | |||
r2 | user = get_keys(key_names, json_body) | |||
if user: | ||||
r153 | 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") | ||||
r2 | ||||
return parsed_user | ||||
def get_query(json_body): | ||||
query = None | ||||
r153 | key_name = ["query", "sentry.interfaces.query.Query", "sentry.interfaces.Query"] | |||
r2 | query = get_keys(key_name, json_body) | |||
return query | ||||
def parse_sentry_event(json_body): | ||||
r153 | request_id = json_body.get("event_id") | |||
r2 | ||||
# required | ||||
r153 | message = json_body.get("message") | |||
log_timestamp = json_body.get("timestamp") | ||||
level = json_body.get("level") | ||||
r2 | if isinstance(level, int): | |||
level = LogLevelPython.key_from_value(level) | ||||
r153 | namespace = json_body.get("logger") | |||
language = json_body.get("platform") | ||||
r2 | ||||
# optional | ||||
r153 | server_name = json_body.get("server_name") | |||
culprit = json_body.get("culprit") | ||||
release = json_body.get("release") | ||||
r2 | ||||
r153 | tags = json_body.get("tags", {}) | |||
if hasattr(tags, "items"): | ||||
r2 | tags = list(tags.items()) | |||
r153 | extra = json_body.get("extra", {}) | |||
if hasattr(extra, "items"): | ||||
r2 | 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 | ||||
r153 | other_keys = [ | |||
(k, json_body[k]) for k in json_body.keys() if k not in EXCLUDE_SENTRY_KEYS | ||||
] | ||||
r2 | ||||
logentry = get_logentry(json_body) | ||||
if logentry: | ||||
r153 | message = logentry["message"] | |||
r2 | ||||
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 = { | ||||
r153 | "log_level": level, | |||
"message": message, | ||||
"namespace": namespace, | ||||
"request_id": request_id, | ||||
"server": server_name, | ||||
"date": log_timestamp, | ||||
"tags": tags, | ||||
r2 | } | |||
r153 | event_dict["tags"].extend( | |||
[(k, v) for k, v in extra if k not in EXCLUDED_LOG_VARS] | ||||
) | ||||
r2 | ||||
# other keys can be various object types | ||||
r153 | event_dict["tags"].extend([(k, v) for k, v in other_keys if isinstance(v, str)]) | |||
r2 | if culprit: | |||
r153 | event_dict["tags"].append(("sentry_culprit", culprit)) | |||
r2 | if language: | |||
r153 | event_dict["tags"].append(("sentry_language", language)) | |||
r2 | if release: | |||
r153 | event_dict["tags"].append(("sentry_release", release)) | |||
r2 | ||||
if exception or stacktrace or alt_stacktrace or template: | ||||
event_type = ParsedSentryEventType.ERROR_REPORT | ||||
event_dict = { | ||||
r153 | "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, | ||||
r2 | } | |||
r153 | event_dict["extra"].extend(other_keys) | |||
r2 | if release: | |||
r153 | 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) | ||||
r2 | if ts_ms > 0: | |||
r153 | event_dict["end_time"] = event_dict["start_time"] + timedelta( | |||
milliseconds=ts_ms | ||||
) | ||||
r2 | if stacktrace or alt_stacktrace or template: | |||
r153 | event_dict["traceback"] = stacktrace or alt_stacktrace or template | |||
r2 | for k in list(event_dict.keys()): | |||
if event_dict[k] is None: | ||||
del event_dict[k] | ||||
if user: | ||||
r153 | event_dict["username"] = user["username"] or user["id"] or user["email"] | |||
r2 | return event_dict, event_type | |||