2017-11-16 19:51:44 +01:00
|
|
|
# System documented in https://zulip.readthedocs.io/en/latest/subsystems/logging.html
|
2013-04-23 18:51:17 +02:00
|
|
|
|
2012-12-06 22:00:34 +01:00
|
|
|
import logging
|
2013-01-02 23:25:40 +01:00
|
|
|
import platform
|
2017-12-01 01:54:24 +01:00
|
|
|
import os
|
|
|
|
import subprocess
|
2017-11-16 00:50:28 +01:00
|
|
|
import traceback
|
|
|
|
from typing import Any, Dict, Optional
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2017-11-16 00:50:28 +01:00
|
|
|
from django.conf import settings
|
2016-06-05 07:51:18 +02:00
|
|
|
from django.http import HttpRequest
|
2019-02-02 23:53:55 +01:00
|
|
|
from django.views.debug import get_exception_reporter_filter
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2017-12-01 01:26:37 +01:00
|
|
|
from zerver.lib.logging_util import find_log_caller_module
|
2013-11-13 19:12:22 +01:00
|
|
|
from zerver.lib.queue import queue_json_publish
|
2017-12-01 01:54:24 +01:00
|
|
|
from version import ZULIP_VERSION
|
|
|
|
|
|
|
|
def try_git_describe() -> Optional[str]:
|
2018-03-11 17:29:38 +01:00
|
|
|
try: # nocoverage
|
2017-12-01 01:54:24 +01:00
|
|
|
return subprocess.check_output(
|
|
|
|
['git',
|
|
|
|
'--git-dir', os.path.join(os.path.dirname(__file__), '../.git'),
|
|
|
|
'describe', '--tags', '--always', '--dirty', '--long'],
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
).strip().decode('utf-8')
|
|
|
|
except Exception: # nocoverage
|
|
|
|
return None
|
|
|
|
|
|
|
|
def add_deployment_metadata(report: Dict[str, Any]) -> None:
|
|
|
|
report['git_described'] = try_git_describe()
|
|
|
|
report['zulip_version_const'] = ZULIP_VERSION
|
|
|
|
|
|
|
|
version_path = os.path.join(os.path.dirname(__file__), '../version')
|
|
|
|
if os.path.exists(version_path):
|
|
|
|
report['zulip_version_file'] = open(version_path).read().strip() # nocoverage
|
2013-11-13 19:12:22 +01:00
|
|
|
|
2017-11-27 07:33:05 +01:00
|
|
|
def add_request_metadata(report: Dict[str, Any], request: HttpRequest) -> None:
|
2017-11-30 23:45:45 +01:00
|
|
|
report['has_request'] = True
|
|
|
|
|
2017-03-26 06:53:10 +02:00
|
|
|
report['path'] = request.path
|
|
|
|
report['method'] = request.method
|
|
|
|
report['remote_addr'] = request.META.get('REMOTE_ADDR', None),
|
|
|
|
report['query_string'] = request.META.get('QUERY_STRING', None),
|
|
|
|
report['server_name'] = request.META.get('SERVER_NAME', None),
|
|
|
|
try:
|
|
|
|
from django.contrib.auth.models import AnonymousUser
|
|
|
|
user_profile = request.user
|
|
|
|
if isinstance(user_profile, AnonymousUser):
|
|
|
|
user_full_name = None
|
|
|
|
user_email = None
|
|
|
|
else:
|
|
|
|
user_full_name = user_profile.full_name
|
|
|
|
user_email = user_profile.email
|
|
|
|
except Exception:
|
|
|
|
# Unexpected exceptions here should be handled gracefully
|
|
|
|
traceback.print_exc()
|
|
|
|
user_full_name = None
|
|
|
|
user_email = None
|
|
|
|
report['user_email'] = user_email
|
|
|
|
report['user_full_name'] = user_full_name
|
|
|
|
|
|
|
|
exception_filter = get_exception_reporter_filter(request)
|
|
|
|
try:
|
|
|
|
report['data'] = request.GET if request.method == 'GET' else \
|
|
|
|
exception_filter.get_post_parameters(request)
|
|
|
|
except Exception:
|
|
|
|
# exception_filter.get_post_parameters will throw
|
|
|
|
# RequestDataTooBig if there's a really big file uploaded
|
|
|
|
report['data'] = {}
|
|
|
|
|
|
|
|
try:
|
|
|
|
report['host'] = request.get_host().split(':')[0]
|
|
|
|
except Exception:
|
|
|
|
# request.get_host() will throw a DisallowedHost
|
|
|
|
# exception if the host is invalid
|
|
|
|
report['host'] = platform.node()
|
|
|
|
|
2017-11-30 23:06:21 +01:00
|
|
|
class AdminNotifyHandler(logging.Handler):
|
|
|
|
"""An logging handler that sends the log/exception to the queue to be
|
|
|
|
turned into an email and/or a Zulip message for the server admins.
|
2012-12-06 22:00:34 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
# adapted in part from django/utils/log.py
|
|
|
|
|
2017-11-27 07:33:05 +01:00
|
|
|
def __init__(self) -> None:
|
2012-12-06 22:00:34 +01:00
|
|
|
logging.Handler.__init__(self)
|
|
|
|
|
2017-11-27 07:33:05 +01:00
|
|
|
def emit(self, record: logging.LogRecord) -> None:
|
2017-12-22 21:02:18 +01:00
|
|
|
report = {} # type: Dict[str, Any]
|
2017-11-30 22:55:48 +01:00
|
|
|
|
2018-07-02 09:55:42 +02:00
|
|
|
# This parameter determines whether Zulip should attempt to
|
|
|
|
# send Zulip messages containing the error report. If there's
|
|
|
|
# syntax that makes the markdown processor throw an exception,
|
|
|
|
# we really don't want to send that syntax into a new Zulip
|
|
|
|
# message in exception handler (that's the stuff of which
|
|
|
|
# recursive exception loops are made).
|
|
|
|
#
|
|
|
|
# We initialize is_bugdown_rendering_exception to `True` to
|
|
|
|
# prevent the infinite loop of zulip messages by ERROR_BOT if
|
|
|
|
# the outer try block here throws an exception before we have
|
|
|
|
# a chance to check the exception for whether it comes from
|
|
|
|
# bugdown.
|
|
|
|
is_bugdown_rendering_exception = True
|
|
|
|
|
2012-12-06 22:00:34 +01:00
|
|
|
try:
|
2017-11-30 22:55:48 +01:00
|
|
|
report['node'] = platform.node()
|
|
|
|
report['host'] = platform.node()
|
|
|
|
|
2017-12-01 01:54:24 +01:00
|
|
|
add_deployment_metadata(report)
|
|
|
|
|
2013-11-13 19:12:22 +01:00
|
|
|
if record.exc_info:
|
2017-11-30 22:55:48 +01:00
|
|
|
stack_trace = ''.join(traceback.format_exception(*record.exc_info))
|
2017-03-26 07:45:10 +02:00
|
|
|
message = str(record.exc_info[1])
|
2018-07-02 09:55:42 +02:00
|
|
|
is_bugdown_rendering_exception = record.msg.startswith('Exception in Markdown parser')
|
2013-11-13 19:12:22 +01:00
|
|
|
else:
|
2017-11-30 23:18:16 +01:00
|
|
|
stack_trace = 'No stack trace available'
|
2017-03-26 07:45:10 +02:00
|
|
|
message = record.getMessage()
|
2017-04-26 03:52:12 +02:00
|
|
|
if '\n' in message:
|
|
|
|
# Some exception code paths in queue processors
|
|
|
|
# seem to result in super-long messages
|
|
|
|
stack_trace = message
|
|
|
|
message = message.split('\n')[0]
|
2018-07-02 09:55:42 +02:00
|
|
|
is_bugdown_rendering_exception = False
|
2017-11-30 22:55:48 +01:00
|
|
|
report['stack_trace'] = stack_trace
|
|
|
|
report['message'] = message
|
2013-11-13 19:12:22 +01:00
|
|
|
|
2017-12-01 01:26:37 +01:00
|
|
|
report['logger_name'] = record.name
|
|
|
|
report['log_module'] = find_log_caller_module(record)
|
|
|
|
report['log_lineno'] = record.lineno
|
|
|
|
|
2017-03-26 06:54:56 +02:00
|
|
|
if hasattr(record, "request"):
|
|
|
|
add_request_metadata(report, record.request) # type: ignore # record.request is added dynamically
|
2017-11-30 22:55:48 +01:00
|
|
|
|
2017-01-08 16:36:19 +01:00
|
|
|
except Exception:
|
2017-11-30 22:55:48 +01:00
|
|
|
report['message'] = "Exception in preparing exception report!"
|
|
|
|
logging.warning(report['message'], exc_info=True)
|
|
|
|
report['stack_trace'] = "See /var/log/zulip/errors.log"
|
2012-12-06 22:00:34 +01:00
|
|
|
|
2018-03-21 01:03:12 +01:00
|
|
|
if settings.DEBUG_ERROR_REPORTING: # nocoverage
|
|
|
|
logging.warning("Reporting an error to admins...")
|
|
|
|
logging.warning("Reporting an error to admins: {} {} {} {} {}" .format(
|
|
|
|
record.levelname, report['logger_name'], report['log_module'],
|
|
|
|
report['message'], report['stack_trace']))
|
|
|
|
|
2013-01-25 20:15:26 +01:00
|
|
|
try:
|
2016-07-19 06:41:55 +02:00
|
|
|
if settings.STAGING_ERROR_NOTIFICATIONS:
|
2013-11-13 19:12:22 +01:00
|
|
|
# On staging, process the report directly so it can happen inside this
|
|
|
|
# try/except to prevent looping
|
2017-03-26 07:00:08 +02:00
|
|
|
from zerver.lib.error_notify import notify_server_error
|
2018-07-02 09:55:42 +02:00
|
|
|
notify_server_error(report, is_bugdown_rendering_exception)
|
2015-08-21 09:02:03 +02:00
|
|
|
else:
|
|
|
|
queue_json_publish('error_reports', dict(
|
|
|
|
type = "server",
|
|
|
|
report = report,
|
2017-11-24 13:18:46 +01:00
|
|
|
))
|
2017-01-08 16:36:19 +01:00
|
|
|
except Exception:
|
2013-01-25 20:15:26 +01:00
|
|
|
# If this breaks, complain loudly but don't pass the traceback up the stream
|
|
|
|
# However, we *don't* want to use logging.exception since that could trigger a loop.
|
|
|
|
logging.warning("Reporting an exception triggered an exception!", exc_info=True)
|