SickRage/sickbeard/logger.py

214 lines
7.6 KiB
Python

# Author: Nic Wolfe <nic@wolfeden.ca>
# URL: http://code.google.com/p/sickbeard/
#
# This file is part of SickRage.
#
# SickRage is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SickRage is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SickRage. If not, see <http://www.gnu.org/licenses/>.
from __future__ import with_statement
import os
import re
import sys
import logging
import logging.handlers
import threading
import platform
import sickbeard
from sickbeard import classes, encodingKludge as ek
from github import Github
from pastebin import PastebinAPI
# log levels
ERROR = logging.ERROR
WARNING = logging.WARNING
INFO = logging.INFO
DEBUG = logging.DEBUG
DB = 5
reverseNames = {u'ERROR': ERROR,
u'WARNING': WARNING,
u'INFO': INFO,
u'DEBUG': DEBUG,
u'DB': DB}
censoredItems = {}
class NullHandler(logging.Handler):
def emit(self, record):
pass
class CensoredFormatter(logging.Formatter):
def format(self, record):
msg = super(CensoredFormatter, self).format(record)
for k, v in censoredItems.items():
if v and len(v) > 0 and v in msg:
msg = msg.replace(v, len(v) * '*')
return msg
class Logger(object):
def __init__(self):
self.logger = logging.getLogger('sickrage')
self.loggers = [
logging.getLogger('sickrage'),
logging.getLogger('tornado.general'),
logging.getLogger('tornado.application'),
#logging.getLogger('tornado.access'),
]
self.consoleLogging = False
self.fileLogging = False
self.debugLogging = False
self.logFile = None
def initLogging(self, consoleLogging=False, fileLogging=False, debugLogging=False):
self.logFile = self.logFile or os.path.join(sickbeard.LOG_DIR, 'sickrage.log')
self.debugLogging = debugLogging
self.consoleLogging = consoleLogging
self.fileLogging = fileLogging
# add a new logging level DB
logging.addLevelName(DB, 'DB')
# nullify root logger
logging.getLogger().addHandler(NullHandler())
# set custom root logger
for logger in self.loggers:
if logger is not self.logger:
logger.root = self.logger
logger.parent = self.logger
# set minimum logging level allowed for loggers
for logger in self.loggers:
logger.setLevel(DB)
# console log handler
if self.consoleLogging:
console = logging.StreamHandler()
console.setFormatter(CensoredFormatter('%(asctime)s %(levelname)s::%(message)s', '%H:%M:%S'))
console.setLevel(INFO if not self.debugLogging else DEBUG)
for logger in self.loggers:
logger.addHandler(console)
# rotating log file handler
if self.fileLogging:
rfh = logging.handlers.RotatingFileHandler(self.logFile, maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
rfh.setFormatter(CensoredFormatter('%(asctime)s %(levelname)-8s %(message)s', '%Y-%m-%d %H:%M:%S'))
rfh.setLevel(DEBUG)
for logger in self.loggers:
logger.addHandler(rfh)
def log(self, msg, level=INFO, *args, **kwargs):
meThread = threading.currentThread().getName()
message = meThread + u" :: " + msg
# pass exception information if debugging enabled
if level == ERROR:
self.logger.exception(message, *args, **kwargs)
classes.ErrorViewer.add(classes.UIError(message))
#if sickbeard.GIT_AUTOISSUES:
# self.submit_errors()
else:
self.logger.log(level, message, *args, **kwargs)
def log_error_and_exit(self, error_msg, *args, **kwargs):
self.log(error_msg, ERROR, *args, **kwargs)
if not self.consoleLogging:
sys.exit(error_msg.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace'))
else:
sys.exit(1)
def submit_errors(self):
if not (sickbeard.GIT_USERNAME and sickbeard.GIT_PASSWORD and len(classes.ErrorViewer.errors) > 0):
return
gh_org = sickbeard.GIT_ORG or 'SiCKRAGETV'
gh_repo = 'sickrage-issues'
self.gh_issues = Github(login_or_token=sickbeard.GIT_USERNAME, password=sickbeard.GIT_PASSWORD,
user_agent="SiCKRAGE").get_organization(gh_org).get_repo(gh_repo)
try:
# read log file
if self.logFile and os.path.isfile(self.logFile):
with ek.ek(open, self.logFile) as f:
log_data = f.readlines()
log_data = [line for line in reversed(log_data)]
# parse and submit errors to issue tracker
for curError in sorted(classes.ErrorViewer.errors, key=lambda error: error.time, reverse=True)[:500]:
if not curError.title:
continue
pastebin_url = None
regex = "^(%s)\s*([A-Z]+)\s*(.+?)\s*\:\:\s*(.*)$" % curError.time
for i, x in enumerate(log_data):
x = ek.ss(x)
match = re.match(regex, x)
if match:
level = match.group(2)
if reverseNames[level] == ERROR:
paste_data = "".join(log_data[i:50])
pastebin_url = PastebinAPI().paste('f59b8e9fa1fc2d033e399e6c7fb09d19', paste_data)
break
message = u"### INFO\n"
message += u"Python Version: **" + sys.version[:120] + "**\n"
message += u"Operating System: **" + platform.platform() + "**\n"
message += u"Branch: **" + sickbeard.BRANCH + "**\n"
message += u"Commit: SiCKRAGETV/SickRage@" + sickbeard.CUR_COMMIT_HASH + "\n"
if pastebin_url:
message += u"Pastebin Log URL: " + pastebin_url + "\n"
message += u"### ERROR\n"
message += u"```\n"
message += curError.message + "\n"
message += u"```\n"
message += u"---\n"
message += u"_STAFF NOTIFIED_: @SiCKRAGETV/owners @SiCKRAGETV/moderators"
issue = self.gh_issues.create_issue("[APP SUBMITTED]: " + curError.title, message)
if issue:
self.log('Your issue ticket #%s was submitted successfully!' % issue.number)
if not sickbeard.GIT_AUTOISSUES:
ui.notifications.message('Your issue ticket #%s was submitted successfully!' % issue.number)
# clear error from error list
classes.ErrorViewer.errors.remove(curError)
except Exception as e:
self.log(sickbeard.exceptions.ex(e), ERROR)
class Wrapper(object):
instance = Logger()
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
try:
return getattr(self.wrapped, name)
except AttributeError:
return getattr(self.instance, name)
_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])