Logs errors by default, in a dedicated file

- log_errors option, true by default
- errors go in log_dir/errors.log (so $XDG_DATA_HOME/errors.log
    by default)

This should help a lot for debugging, and provide a way for people
to easily give debug traces without useless or personal infos.
This commit is contained in:
mathieui 2013-08-03 19:27:25 +02:00
parent c2f6ece39d
commit 31c2e23c4c
8 changed files with 124 additions and 24 deletions

View file

@ -220,6 +220,10 @@ load_log = 10
# you want to use instead. This directory will be created if it doesn't exist # you want to use instead. This directory will be created if it doesn't exist
log_dir = log_dir =
# Log the errors poezio encounters in log_dir/errors.log
# A false value disables this option.
log_errors = true
# If plugins_dir is not set, plugins will be loaded from $XDG_DATA_HOME/poezio/plugins. # If plugins_dir is not set, plugins will be loaded from $XDG_DATA_HOME/poezio/plugins.
# You can specify an other directory to use. It will be created if it doesn't exist # You can specify an other directory to use. It will be created if it doesn't exist
plugins_dir = plugins_dir =

View file

@ -362,6 +362,13 @@ section of this documentation.
i.e. in ``~/.local/share/poezio/logs/``. So, you should specify the directory i.e. in ``~/.local/share/poezio/logs/``. So, you should specify the directory
you want to use instead. This directory will be created if it doesn't exist you want to use instead. This directory will be created if it doesn't exist
log_errors
**Default value:** ``true``
Logs all the tracebacks or poezio/sleekxmpp in :term:`log_dir`/errors.log by
default. ``false`` disables this option.
max_lines_in_memory max_lines_in_memory
**Default value:** ``2048`` **Default value:** ``2048``

View file

@ -22,7 +22,6 @@ from configparser import RawConfigParser, NoOptionError, NoSectionError
from os import environ, makedirs, path from os import environ, makedirs, path
from shutil import copy2 from shutil import copy2
from args import parse_args from args import parse_args
from common import safeJID
class Config(RawConfigParser): class Config(RawConfigParser):
@ -278,3 +277,59 @@ except:
sys.stderr.write('Poezio was unable to read or parse the config file.\n') sys.stderr.write('Poezio was unable to read or parse the config file.\n')
traceback.print_exc(limit=0) traceback.print_exc(limit=0)
sys.exit(1) sys.exit(1)
LOG_DIR = config.get('log_dir', '') or path.join(environ.get('XDG_DATA_HOME') or path.join(environ.get('HOME'), '.local', 'share'), 'poezio')
LOG_DIR = path.expanduser(LOG_DIR)
try:
makedirs(LOG_DIR)
except:
pass
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'simple': {
'format': '%(levelname)s:%(module)s:%(message)s'
}
},
'handlers': {
'debug':{
'level':'DEBUG',
'class':'logging.FileHandler',
'filename': '/tmp/dummy',
'formatter': 'simple',
},
'error': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': '/tmp/dummy',
'formatter': 'simple',
},
},
'root': {
'handlers': [],
'propagate': True,
'level': 'DEBUG',
}
}
if config.get('log_errors', 'true').lower() != 'false':
LOGGING_CONFIG['root']['handlers'].append('error')
LOGGING_CONFIG['handlers']['error']['filename'] = path.join(
LOG_DIR,
'errors.log')
if options.debug:
LOGGING_CONFIG['root']['handlers'].append('debug')
LOGGING_CONFIG['handlers']['debug']['filename'] = options.debug
if LOGGING_CONFIG['root']['handlers']:
logging.config.dictConfig(LOGGING_CONFIG)
else:
logging.basicConfig(level=logging.CRITICAL)
# common import sleekxmpp, which creates then its loggers, so
# it needs to be after logger configuration
from common import safeJID

View file

@ -614,12 +614,19 @@ class Core(object):
try: try:
self.remote_fifo = Fifo(os.path.join(config.get('remote_fifo_path', './'), 'poezio.fifo'), 'w') self.remote_fifo = Fifo(os.path.join(config.get('remote_fifo_path', './'), 'poezio.fifo'), 'w')
except (OSError, IOError) as e: except (OSError, IOError) as e:
log.error('Could not open the fifo for writing (%s)',
os.path.join(config.get('remote_fifo_path', './'), 'poezio.fifo'),
exc_info=True)
self.information('Could not open fifo file for writing: %s' % (e,), 'Error') self.information('Could not open fifo file for writing: %s' % (e,), 'Error')
return return
command_str = ' '.join([pipes.quote(arg.replace('\n', ' ')) for arg in command]) + '\n' command_str = ' '.join([pipes.quote(arg.replace('\n', ' ')) for arg in command]) + '\n'
try: try:
self.remote_fifo.write(command_str) self.remote_fifo.write(command_str)
except (IOError) as e: except (IOError) as e:
log.error('Could not write in the fifo (%s): %s',
os.path.join(config.get('remote_fifo_path', './'), 'poezio.fifo'),
repr(command),
exc_info=True)
self.information('Could not execute %s: %s' % (command, e,), 'Error') self.information('Could not execute %s: %s' % (command, e,), 'Error')
self.remote_fifo = None self.remote_fifo = None
else: else:
@ -627,6 +634,7 @@ class Core(object):
try: try:
e.start() e.start()
except ValueError as e: except ValueError as e:
log.error('Could not execute command (%s)', repr(command), exc_info=True)
self.information('%s' % (e,), 'Error') self.information('%s' % (e,), 'Error')
@ -644,8 +652,7 @@ class Core(object):
try: try:
self.current_tab().execute_command(line) self.current_tab().execute_command(line)
except: except:
import traceback log.error('Execute failed (%s)', line, exc_info=True)
log.debug('Execute failed:\n%s', traceback.format_exc())
########################## TImed Events ####################################### ########################## TImed Events #######################################
@ -1573,9 +1580,9 @@ class Core(object):
self.events.trigger('send_normal_presence', pres) self.events.trigger('send_normal_presence', pres)
pres.send() pres.send()
except : except :
import traceback
self.information(_('Could not send directed presence'), 'Error') self.information(_('Could not send directed presence'), 'Error')
log.debug(_("Could not send directed presence:\n") + traceback.format_exc()) log.debug('Could not send directed presence to %s', jid, exc_info=True)
return
tab = self.get_tab_by_name(jid) tab = self.get_tab_by_name(jid)
if tab: if tab:
if type in ('xa', 'away'): if type in ('xa', 'away'):
@ -1623,7 +1630,7 @@ class Core(object):
try: try:
names = os.listdir(themes_dir) names = os.listdir(themes_dir)
except OSError as e: except OSError as e:
log.debug(_('Completion failed: %s'), e) log.error('Completion for /theme failed', exc_info=True)
return return
theme_files = [name[:-3] for name in names if name.endswith('.py')] theme_files = [name[:-3] for name in names if name.endswith('.py')]
if not 'default' in theme_files: if not 'default' in theme_files:
@ -1910,6 +1917,9 @@ class Core(object):
try: try:
response = self.xmpp.plugin['xep_0030'].get_items(jid=jid.server, block=True, timeout=1) response = self.xmpp.plugin['xep_0030'].get_items(jid=jid.server, block=True, timeout=1)
except: except:
log.error('/join completion: Unable to get the list of rooms for %s',
jid.server,
exc_info=True)
response = None response = None
if response: if response:
items = response['disco_items'].get_items() items = response['disco_items'].get_items()
@ -2499,9 +2509,10 @@ class Core(object):
try: try:
StanzaBase(self.xmpp, xml=ET.fromstring(arg)).send() StanzaBase(self.xmpp, xml=ET.fromstring(arg)).send()
except: except:
import traceback
self.information(_('Could not send custom stanza'), 'Error') self.information(_('Could not send custom stanza'), 'Error')
log.debug(_("Could not send custom stanza:\n") + traceback.format_exc()) log.debug('/rawxml: Could not send custom stanza (%s)',
repr(arg),
exc_info=True)
def command_load(self, arg): def command_load(self, arg):
""" """
@ -2886,7 +2897,7 @@ class Core(object):
nickname=remote_nick) nickname=remote_nick)
return True return True
except CorrectionError: except CorrectionError:
pass log.error('Unable to correct a message', exc_info=True)
return False return False
if not try_modify(): if not try_modify():
@ -3106,7 +3117,7 @@ class Core(object):
self.events.trigger('highlight', message, tab) self.events.trigger('highlight', message, tab)
replaced = True replaced = True
except CorrectionError: except CorrectionError:
pass log.error('Unable to correct a message', exc_info=True)
if not replaced and tab.add_message(body, date, nick_from, history=delayed, identifier=message['id'], jid=message['from'], typ=1): if not replaced and tab.add_message(body, date, nick_from, history=delayed, identifier=message['id'], jid=message['from'], typ=1):
self.events.trigger('highlight', message, tab) self.events.trigger('highlight', message, tab)
@ -3160,7 +3171,7 @@ class Core(object):
nickname=nick_from) nickname=nick_from)
replaced = True replaced = True
except CorrectionError: except CorrectionError:
pass log.error('Unable to correct a message', exc_info=True)
if not replaced: if not replaced:
tab.add_message(body, time=None, nickname=nick_from, tab.add_message(body, time=None, nickname=nick_from,
forced_user=user, forced_user=user,

View file

@ -17,8 +17,9 @@ import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
log_dir = config.get('log_dir', '') or os.path.join(environ.get('XDG_DATA_HOME') or os.path.join(environ.get('HOME'), '.local', 'share'), 'poezio', 'logs') from config import LOG_DIR
log_dir = os.path.expanduser(log_dir)
log_dir = os.path.join(LOG_DIR, 'logs')
message_log_re = re.compile('MR (\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})Z (\d+) <([^ ]+)>  (.*)') message_log_re = re.compile('MR (\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})Z (\d+) <([^ ]+)>  (.*)')
info_log_re = re.compile('MI (\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})Z (\d+) (.*)') info_log_re = re.compile('MI (\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})Z (\d+) (.*)')
@ -69,13 +70,19 @@ class Logger(object):
return return
try: try:
makedirs(log_dir) makedirs(log_dir)
except OSError: except FileExistsError:
pass
except:
log.error('Unable to create the log dir', exc_info=True)
pass pass
try: try:
fd = open(os.path.join(log_dir, room), 'a') fd = open(os.path.join(log_dir, room), 'a')
self.fds[room] = fd self.fds[room] = fd
return fd return fd
except IOError: except IOError:
log.error('Unable to open the log file (%s)',
os.path.join(log_dir, room),
exc_info=True)
return return
def get_logs(self, jid, nb=10): def get_logs(self, jid, nb=10):
@ -90,6 +97,9 @@ class Logger(object):
try: try:
fd = open(os.path.join(log_dir, jid), 'rb') fd = open(os.path.join(log_dir, jid), 'rb')
except: except:
log.error('Unable to open the log file (%s)',
os.path.join(log_dir, room),
exc_info=True)
return return
if not fd: if not fd:
return return
@ -189,11 +199,17 @@ class Logger(object):
for line in lines: for line in lines:
fd.write(' %s\n' % line) fd.write(' %s\n' % line)
except: except:
log.error('Unable to write in the log file (%s)',
os.path.join(log_dir, jid),
exc_info=True)
return False return False
else: else:
try: try:
fd.flush() # TODO do something better here? fd.flush() # TODO do something better here?
except: except:
log.error('Unable to flush the log file (%s)',
os.path.join(log_dir, jid),
exc_info=True)
return False return False
return True return True
@ -207,6 +223,9 @@ class Logger(object):
try: try:
self.roster_logfile = open(os.path.join(log_dir, 'roster.log'), 'a') self.roster_logfile = open(os.path.join(log_dir, 'roster.log'), 'a')
except IOError: except IOError:
log.error('Unable to create the log file (%s)',
os.path.join(log_dir, 'roster.log'),
exc_info=True)
return False return False
try: try:
str_time = datetime.now().strftime('%Y%m%dT%H:%M:%SZ') str_time = datetime.now().strftime('%Y%m%dT%H:%M:%SZ')
@ -219,6 +238,9 @@ class Logger(object):
self.roster_logfile.write(' %s\n' % line) self.roster_logfile.write(' %s\n' % line)
self.roster_logfile.flush() self.roster_logfile.flush()
except: except:
log.error('Unable to write in the log file (%s)',
os.path.join(log_dir, 'roster.log'),
exc_info=True)
return False return False
return True return True

View file

@ -14,24 +14,22 @@ import sys
import os import os
import signal import signal
import logging import logging.config
sys.path.append(os.path.dirname(os.path.abspath(__file__))) sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from logger import logger
from config import options from config import options
from logger import logger
import singleton import singleton
import core import core
log = logging.getLogger('')
def main(): def main():
""" """
Enter point Enter point
""" """
signal.signal(signal.SIGINT, signal.SIG_IGN) # ignore ctrl-c signal.signal(signal.SIGINT, signal.SIG_IGN) # ignore ctrl-c
if options.debug:
logging.basicConfig(filename=options.debug, level=logging.DEBUG)
else:
logging.basicConfig(level=logging.CRITICAL)
cocore = singleton.Singleton(core.Core) cocore = singleton.Singleton(core.Core)
signal.signal(signal.SIGUSR1, cocore.sigusr_handler) # reload the config signal.signal(signal.SIGUSR1, cocore.sigusr_handler) # reload the config
signal.signal(signal.SIGHUP, cocore.exit_from_signal) signal.signal(signal.SIGHUP, cocore.exit_from_signal)
@ -48,6 +46,7 @@ def main():
print("Poezio could not start, maybe you tried aborting it while it was starting?\n" print("Poezio could not start, maybe you tried aborting it while it was starting?\n"
"If you think it is abnormal, please run it with the -d option and report the bug.") "If you think it is abnormal, please run it with the -d option and report the bug.")
else: else:
log.error('------------------------ new poezio start ------------------------')
cocore.main_loop() # Refresh the screen, wait for user events etc cocore.main_loop() # Refresh the screen, wait for user events etc
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -544,6 +544,7 @@ class ChatTab(Tab):
ET.fromstring(arg) ET.fromstring(arg)
except: except:
self.core.information('Could not send custom xhtml', 'Error') self.core.information('Could not send custom xhtml', 'Error')
log.error('/xhtml: Unable to send custom xhtml', exc_info=True)
return return
msg = self.core.xmpp.make_message(self.get_dest_jid()) msg = self.core.xmpp.make_message(self.get_dest_jid())
@ -1917,7 +1918,7 @@ class PrivateTab(ChatTab):
user=user, jid=self.core.xmpp.boundjid, nickname=self.own_nick) user=user, jid=self.core.xmpp.boundjid, nickname=self.own_nick)
replaced = True replaced = True
except: except:
pass log.error('Unable to correct a message', exc_info=True)
if not replaced: if not replaced:
self.add_message(msg['body'], self.add_message(msg['body'],
@ -2638,6 +2639,7 @@ class RosterInfoTab(Tab):
handle.close() handle.close()
except IOError: except IOError:
self.core.information('Could not open %s' % filepath, 'Error') self.core.information('Could not open %s' % filepath, 'Error')
log.error('Unable to correct a message', exc_info=True)
return return
for jid in lines: for jid in lines:
self.command_add(jid.lstrip('\n')) self.command_add(jid.lstrip('\n'))
@ -3107,7 +3109,7 @@ class ConversationTab(ChatTab):
nickname=self.core.own_nick) nickname=self.core.own_nick)
replaced = True replaced = True
except: except:
pass log.error('Unable to correct a message', exc_info=True)
if not replaced: if not replaced:
self.add_message(msg['body'], self.add_message(msg['body'],
nickname=self.core.own_nick, nickname=self.core.own_nick,

View file

@ -229,8 +229,8 @@ def xhtml_to_poezio_colors(xml):
if isinstance(xml, str): if isinstance(xml, str):
try: try:
xml = ET.fromstring(xml) xml = ET.fromstring(xml)
except cElementTree.ParserError as e: except:
log.error("Error decoding XML: [%s] (%s)" % (xml, e)) log.error("Error decoding XML: [%s]", repr(xml), exc_info=True)
return "" return ""
def parse_css(css): def parse_css(css):
def get_color(value): def get_color(value):