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:
parent
c2f6ece39d
commit
31c2e23c4c
8 changed files with 124 additions and 24 deletions
|
@ -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 =
|
||||||
|
|
|
@ -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``
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
31
src/core.py
31
src/core.py
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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__':
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in a new issue