diff --git a/README.md b/README.md index 5ca07d6aa..8774af8a3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # bazarr -Bazarr is a companion application to Sonarr and Radarr. It can manage and download subtitles based on your requirements. You define your preferences by TV show or movie and Bazarr takes care of everything for you. +Bazarr is a companion application to Sonarr and Radarr. It manages and downloads subtitles based on your requirements. You define your preferences by TV show or movies and Bazarr takes care of everything for you. Be aware that Bazarr doesn't scan disk to detect series and movies: It only takes care of the series and movies that are indexed in Sonarr and Radarr. diff --git a/bazarr/get_settings.py b/bazarr/get_settings.py index ebe291686..038cbb583 100644 --- a/bazarr/get_settings.py +++ b/bazarr/get_settings.py @@ -39,10 +39,10 @@ def get_general_settings(): else: path_mappings = '[]' - if cfg.has_option('general', 'log_level'): - log_level = cfg.get('general', 'log_level') + if cfg.has_option('general', 'debug'): + debug = cfg.getboolean('general', 'debug') else: - log_level = 'INFO' + debug = False if cfg.has_option('general', 'branch'): branch = cfg.get('general', 'branch') @@ -154,7 +154,7 @@ def get_general_settings(): port = '6767' base_url = '/' path_mappings = '[]' - log_level = 'INFO' + debug = False branch = 'master' auto_update = True single_language = False @@ -177,7 +177,7 @@ def get_general_settings(): only_monitored = False adaptive_searching = False - return [ip, port, base_url, path_mappings, log_level, branch, auto_update, single_language, minimum_score, use_scenename, use_postprocessing, postprocessing_cmd, use_sonarr, use_radarr, path_mappings_movie, serie_default_enabled, serie_default_language, serie_default_hi, movie_default_enabled,movie_default_language, movie_default_hi, page_size, minimum_score_movie, use_embedded_subs, only_monitored, adaptive_searching] + return [ip, port, base_url, path_mappings, debug, branch, auto_update, single_language, minimum_score, use_scenename, use_postprocessing, postprocessing_cmd, use_sonarr, use_radarr, path_mappings_movie, serie_default_enabled, serie_default_language, serie_default_hi, movie_default_enabled,movie_default_language, movie_default_hi, page_size, minimum_score_movie, use_embedded_subs, only_monitored, adaptive_searching] def get_auth_settings(): @@ -450,7 +450,7 @@ ip = result[0] port = result[1] base_url = result[2] path_mappings = ast.literal_eval(result[3]) -log_level = result[4] +debug = result[4] branch = result[5] automatic = result[6] single_language = result[7] diff --git a/bazarr/get_subtitle.py b/bazarr/get_subtitle.py index b596b6ef6..ff4de38cd 100644 --- a/bazarr/get_subtitle.py +++ b/bazarr/get_subtitle.py @@ -20,6 +20,7 @@ from notifier import send_notifications, send_notifications_movie import cPickle as pickle import codecs from get_providers import get_providers, get_providers_auth +from subliminal.providers.legendastv import LegendasTVSubtitle # configure the cache region.configure('dogpile.cache.memory') @@ -231,7 +232,10 @@ def manual_search(path, language, hi, providers, providers_auth, sceneName, medi continue if used_sceneName: not_matched.remove('hash') - subtitles_list.append(dict(score=round((compute_score(s, video, hearing_impaired=hi) / max_score * 100), 2), language=alpha2_from_alpha3(s.language.alpha3), hearing_impaired=str(s.hearing_impaired), provider=s.provider_name, subtitle=codecs.encode(pickle.dumps(s), "base64").decode(), url=s.page_link, matches=list(matched), dont_matches=list(not_matched))) + if type(s) is LegendasTVSubtitle: + # The pickle doesn't work very well with RAR (rarfile.RarFile) or ZIP (zipfile.ZipFile) + s.archive.content = None + subtitles_list.append(dict(score=round((compute_score(s, video, hearing_impaired=hi) / max_score * 100), 2), language=alpha2_from_alpha3(s.language.alpha3), hearing_impaired=str(s.hearing_impaired), provider=s.provider_name, subtitle=codecs.encode(pickle.dumps(s), "base64").decode(), url=s.page_link, matches=list(matched), dont_matches=list(not_matched))) subtitles_dict = {} subtitles_dict = sorted(subtitles_list, key=lambda x: x['score'], reverse=True) logging.debug('BAZARR ' + str(len(subtitles_dict)) + " subtitles have been found for this file: " + path) diff --git a/bazarr/init.py b/bazarr/init.py index 1e07b032e..ed11565ba 100644 --- a/bazarr/init.py +++ b/bazarr/init.py @@ -71,6 +71,13 @@ if cfg.has_section('auth'): cfg.remove_option('auth', 'enabled') with open(config_file, 'w+') as configfile: cfg.write(configfile) + +if cfg.has_section('general'): + if cfg.has_option('general', 'log_level'): + cfg.remove_option('general', 'log_level') + cfg.set('general', 'debug', 'False') + with open(config_file, 'w+') as configfile: + cfg.write(configfile) from cork import Cork import time diff --git a/bazarr/logger.py b/bazarr/logger.py new file mode 100644 index 000000000..1a7e34f29 --- /dev/null +++ b/bazarr/logger.py @@ -0,0 +1,157 @@ +import os +import sys +import logging +import re + +from logging.handlers import TimedRotatingFileHandler +from get_argv import config_dir +from get_settings import get_general_settings + +logger = logging.getLogger() + +debug = get_general_settings()[4] +if debug is False: + log_level = "INFO" +else: + log_level = "DEBUG" + +class OneLineExceptionFormatter(logging.Formatter): + def formatException(self, exc_info): + """ + Format an exception so that it prints on a single line. + """ + result = super(OneLineExceptionFormatter, self).formatException(exc_info) + return repr(result) # or format into one line however you want to + + def format(self, record): + s = super(OneLineExceptionFormatter, self).format(record) + if record.exc_text: + s = s.replace('\n', '') + '|' + return s + + +class NoExceptionFormatter(logging.Formatter): + def format(self, record): + record.exc_text = '' # ensure formatException gets called + return super(NoExceptionFormatter, self).format(record) + + def formatException(self, record): + return '' + + +def configure_logging(): + logger.handlers = [] + + logger.setLevel(log_level) + + # Console logging + ch = logging.StreamHandler() + cf = NoExceptionFormatter('%(asctime)-15s - %(name)-32s (%(thread)x) : %(levelname)s (%(module)s:%(lineno)d) ' + '- %(message)s') + ch.setFormatter(cf) + + ch.setLevel(log_level) + # ch.addFilter(MyFilter()) + logger.addHandler(ch) + + #File Logging + global fh + fh = TimedRotatingFileHandler(os.path.join(config_dir, 'log/bazarr.log'), when="midnight", interval=1, + backupCount=7) + f = OneLineExceptionFormatter('%(asctime)s|%(levelname)-8s|%(name)-32s|%(message)s|', + '%d/%m/%Y %H:%M:%S') + fh.setFormatter(f) + fh.addFilter(BlacklistFilter()) + fh.addFilter(PublicIPFilter()) + + if debug is True: + logging.getLogger("apscheduler").setLevel(logging.DEBUG) + logging.getLogger("subliminal").setLevel(logging.DEBUG) + logging.getLogger("git").setLevel(logging.DEBUG) + logging.getLogger("apprise").setLevel(logging.DEBUG) + else: + logging.getLogger("apscheduler").setLevel(logging.WARNING) + logging.getLogger("subliminal").setLevel(logging.CRITICAL) + + logging.getLogger("enzyme").setLevel(logging.CRITICAL) + logging.getLogger("guessit").setLevel(logging.WARNING) + logging.getLogger("rebulk").setLevel(logging.WARNING) + logging.getLogger("stevedore.extension").setLevel(logging.CRITICAL) + fh.setLevel(log_level) + logger.addHandler(fh) + + +class MyFilter(logging.Filter): + def __init__(self): + pass + + def filter(self, record): + if record.name != 'root': + return False + return True + + +class BlacklistFilter(logging.Filter): + """ + Log filter for blacklisted tokens and passwords + """ + def __init__(self): + pass + + def filter(self, record): + try: + apikeys = re.findall(r'apikey(?:=|%3D)([a-zA-Z0-9]+)', record.msg) + for apikey in apikeys: + record.msg = record.msg.replace(apikey, 8 * '*' + apikey[-2:]) + + args = [] + for arg in record.args: + apikeys = re.findall(r'apikey(?:=|%3D)([a-zA-Z0-9]+)', arg) if isinstance(arg, basestring) else [] + for apikey in apikeys: + arg = arg.replace(apikey, 8 * '*' + apikey[-2:]) + args.append(arg) + record.args = tuple(args) + except: + pass + return True + + +class PublicIPFilter(logging.Filter): + """ + Log filter for public IP addresses + """ + def __init__(self): + pass + + def filter(self, record): + try: + # Currently only checking for ipv4 addresses + ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})', record.msg) + for ip in ipv4: + record.msg = record.msg.replace(ip, ip.partition('.')[0] + '.***.***.***') + + args = [] + for arg in record.args: + ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}(?!\d*-[a-z0-9]{6})', arg) if isinstance(arg, basestring) else [] + for ip in ipv4: + arg = arg.replace(ip, ip.partition('.')[0] + '.***.***.***') + args.append(arg) + record.args = tuple(args) + except: + pass + + return True + + +def empty_log(): + fh.doRollover() + + +def update_settings(debug): + if debug == 'False': + level = "INFO" + else: + level = "DEBUG" + logger.setLevel(level) + for handler in logger.handlers: + handler.setLevel(level) diff --git a/bazarr/main.py b/bazarr/main.py index 8c33b04c9..72c1c5293 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -23,42 +23,7 @@ update_notifier() from get_settings import get_general_settings, get_proxy_settings import logging -from logging.handlers import TimedRotatingFileHandler - -log_level = get_general_settings()[4] -if log_level is None: - log_level = "INFO" - -class OneLineExceptionFormatter(logging.Formatter): - def formatException(self, exc_info): - """ - Format an exception so that it prints on a single line. - """ - result = super(OneLineExceptionFormatter, self).formatException(exc_info) - return repr(result) # or format into one line however you want to - - def format(self, record): - s = super(OneLineExceptionFormatter, self).format(record) - if record.exc_text: - s = s.replace('\n', '') + '|' - return s - -def configure_logging(): - global fh - fh = TimedRotatingFileHandler(os.path.join(config_dir, 'log/bazarr.log'), when="midnight", interval=1, backupCount=7) - f = OneLineExceptionFormatter('%(asctime)s|%(levelname)s|%(message)s|', - '%d/%m/%Y %H:%M:%S') - fh.setFormatter(f) - logging.getLogger("enzyme").setLevel(logging.CRITICAL) - logging.getLogger("apscheduler").setLevel(logging.WARNING) - logging.getLogger("subliminal").setLevel(logging.CRITICAL) - logging.getLogger("guessit").setLevel(logging.WARNING) - logging.getLogger("rebulk").setLevel(logging.WARNING) - logging.getLogger("stevedore.extension").setLevel(logging.CRITICAL) - root = logging.getLogger() - root.setLevel(log_level) - root.addHandler(fh) - +from logger import configure_logging, empty_log, update_settings configure_logging() import requests @@ -228,7 +193,7 @@ def restart(): except Exception as e: logging.error('BAZARR Cannot create bazarr.restart file.') else: - print 'Bazarr is being restarted...' + # print 'Bazarr is being restarted...' logging.info('Bazarr is being restarted...') restart_file.write('') restart_file.close() @@ -454,7 +419,7 @@ def emptylog(): authorize() ref = request.environ['HTTP_REFERER'] - fh.doRollover() + empty_log() logging.info('BAZARR Log file emptied') redirect(ref) @@ -1069,7 +1034,11 @@ def save_settings(): settings_general_baseurl = request.forms.get('settings_general_baseurl') if settings_general_baseurl.endswith('/') is False: settings_general_baseurl += '/' - settings_general_loglevel = request.forms.get('settings_general_loglevel') + settings_general_debug = request.forms.get('settings_general_debug') + if settings_general_debug is None: + settings_general_debug = 'False' + else: + settings_general_debug = 'True' settings_general_sourcepath = request.forms.getall('settings_general_sourcepath') settings_general_destpath = request.forms.getall('settings_general_destpath') settings_general_pathmapping = [] @@ -1131,8 +1100,8 @@ def save_settings(): settings_general = get_general_settings() - before = (unicode(settings_general[0]), int(settings_general[1]), unicode(settings_general[2]), unicode(settings_general[4]), unicode(settings_general[3]), unicode(settings_general[12]), unicode(settings_general[13]), unicode(settings_general[14])) - after = (unicode(settings_general_ip), int(settings_general_port), unicode(settings_general_baseurl), unicode(settings_general_loglevel), unicode(settings_general_pathmapping), unicode(settings_general_use_sonarr), unicode(settings_general_use_radarr), unicode(settings_general_pathmapping_movie)) + before = (unicode(settings_general[0]), int(settings_general[1]), unicode(settings_general[2]), unicode(settings_general[3]), unicode(settings_general[12]), unicode(settings_general[13]), unicode(settings_general[14])) + after = (unicode(settings_general_ip), int(settings_general_port), unicode(settings_general_baseurl), unicode(settings_general_pathmapping), unicode(settings_general_use_sonarr), unicode(settings_general_use_radarr), unicode(settings_general_pathmapping_movie)) from six import text_type cfg = ConfigParser() @@ -1144,7 +1113,7 @@ def save_settings(): cfg.set('general', 'port', text_type(settings_general_port)) cfg.set('general', 'base_url', text_type(settings_general_baseurl)) cfg.set('general', 'path_mappings', text_type(settings_general_pathmapping)) - cfg.set('general', 'log_level', text_type(settings_general_loglevel)) + cfg.set('general', 'debug', text_type(settings_general_debug)) cfg.set('general', 'branch', text_type(settings_general_branch)) cfg.set('general', 'auto_update', text_type(settings_general_automatic)) cfg.set('general', 'single_language', text_type(settings_general_single_language)) @@ -1160,6 +1129,8 @@ def save_settings(): cfg.set('general', 'use_embedded_subs', text_type(settings_general_embedded)) cfg.set('general', 'only_monitored', text_type(settings_general_only_monitored)) cfg.set('general', 'adaptive_searching', text_type(settings_general_adaptive_searching)) + + update_settings(settings_general_debug) if after != before: configured() @@ -1763,7 +1734,7 @@ warnings.simplefilter("ignore", DeprecationWarning) server = CherryPyWSGIServer((str(ip), int(port)), app) try: logging.info('BAZARR is started and waiting for request on http://' + str(ip) + ':' + str(port) + str(base_url)) - print 'Bazarr is started and waiting for request on http://' + str(ip) + ':' + str(port) + str(base_url) + # print 'Bazarr is started and waiting for request on http://' + str(ip) + ':' + str(port) + str(base_url) server.start() except KeyboardInterrupt: shutdown() diff --git a/views/logs.tpl b/views/logs.tpl index 16186c757..b8ec92f0a 100644 --- a/views/logs.tpl +++ b/views/logs.tpl @@ -34,26 +34,26 @@ %line = log.split('|')