2017-04-12 06:14:09 +04:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
|
|
|
|
2017-04-12 06:39:48 +04:00
|
|
|
u"""Afficher les lignes d'un fichier en mettant en surbrillance certains patterns"""
|
2017-04-12 06:14:09 +04:00
|
|
|
|
|
|
|
import sys, subprocess, re
|
|
|
|
from collections import OrderedDict
|
|
|
|
|
2018-06-19 09:06:49 +04:00
|
|
|
def fixnlf(line, mo=None): return re.sub(r'\\n', '\n', line)
|
|
|
|
def bluef(line, mo=None): return '\x1B[34m%s\x1B[0m' % line
|
|
|
|
def greenf(line, mo=None): return '\x1B[32m%s\x1B[0m' % line
|
|
|
|
def yellowf(line, mo=None): return '\x1B[33m%s\x1B[0m' % line
|
|
|
|
def redf(line, mo=None): return '\x1B[31m%s\x1B[0m' % line
|
|
|
|
def nonef(line, mo=None): return re.sub('\x1B\[.*?m', '', line)
|
2017-04-12 06:14:09 +04:00
|
|
|
|
|
|
|
FORMATS = OrderedDict([
|
2018-06-19 01:15:39 +04:00
|
|
|
('fixnl', fixnlf),
|
2017-04-12 06:14:09 +04:00
|
|
|
('blue', bluef),
|
|
|
|
('green', greenf),
|
|
|
|
('yellow', yellowf),
|
|
|
|
('red', redf),
|
|
|
|
('none', nonef),
|
|
|
|
])
|
|
|
|
FORMAT_ALIASES = {
|
2018-06-19 01:15:39 +04:00
|
|
|
'f': 'fixnl',
|
2017-04-12 06:14:09 +04:00
|
|
|
'b': 'blue',
|
|
|
|
'g': 'green',
|
|
|
|
'y': 'yellow',
|
|
|
|
'r': 'red',
|
|
|
|
'': 'none',
|
|
|
|
}
|
|
|
|
|
2018-06-19 01:15:39 +04:00
|
|
|
DEFAULT_PATTERNS = OrderedDict([
|
2018-06-18 22:10:00 +04:00
|
|
|
(r'(?i)error', redf),
|
|
|
|
(r'(?i)warn(ing)?', yellowf),
|
|
|
|
(r'(?i)info', bluef),
|
|
|
|
(None, nonef),
|
|
|
|
])
|
2018-06-19 01:15:39 +04:00
|
|
|
|
|
|
|
APACHE_PATTERNS = [
|
|
|
|
r'(?i)error:red',
|
|
|
|
r'(?i)warn(ing)?:yellow',
|
|
|
|
r'(?i)info:blue',
|
|
|
|
r':none',
|
|
|
|
]
|
|
|
|
PHP_PATTERNS = [
|
|
|
|
r'(?i)php fatal error:fixnl,red',
|
|
|
|
r'(?i)php notice:yellow,fixnl',
|
|
|
|
r'(?i)php warning:fixnl,blue',
|
|
|
|
r':fixnl,none',
|
|
|
|
]
|
2018-06-18 22:10:00 +04:00
|
|
|
|
|
|
|
PRESETS = {
|
2018-06-19 01:15:39 +04:00
|
|
|
'apache': ('/var/log/apache2/error.log', True, APACHE_PATTERNS, False),
|
|
|
|
'php': ('/var/log/apache2/error.log', True, PHP_PATTERNS, False),
|
2018-06-18 22:10:00 +04:00
|
|
|
}
|
|
|
|
PRESET_ALIASES = {
|
|
|
|
'a': 'apache',
|
|
|
|
'p': 'php',
|
|
|
|
}
|
|
|
|
|
2017-04-12 06:14:09 +04:00
|
|
|
def strip_nl(s):
|
|
|
|
if s is None: return None
|
|
|
|
elif s.endswith("\r\n"): s = s[:-2]
|
|
|
|
elif s.endswith("\n"): s = s[:-1]
|
|
|
|
elif s.endswith("\r"): s = s[:-1]
|
|
|
|
return s
|
|
|
|
|
2017-04-12 06:39:48 +04:00
|
|
|
def run_tailor(inputfile=None, follow=False, patterns=None):
|
|
|
|
if inputfile is None or not follow:
|
|
|
|
if inputfile is None:
|
|
|
|
inf = sys.stdin
|
|
|
|
close = False
|
|
|
|
else:
|
|
|
|
inf = open(inputfile, 'rb')
|
|
|
|
close = True
|
2017-04-12 06:14:09 +04:00
|
|
|
def next_line():
|
2017-04-12 06:39:48 +04:00
|
|
|
try:
|
|
|
|
while True:
|
|
|
|
try: line = inf.readline()
|
|
|
|
except: break
|
|
|
|
if line == '': break
|
|
|
|
yield line
|
|
|
|
finally:
|
|
|
|
if close: inf.close()
|
2017-04-12 06:14:09 +04:00
|
|
|
else:
|
|
|
|
def next_line():
|
|
|
|
p = subprocess.Popen(
|
|
|
|
['tail', '-f', inputfile],
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
|
while True:
|
|
|
|
try: line = p.stdout.readline()
|
|
|
|
except: break
|
|
|
|
if line == '': break
|
|
|
|
yield line
|
|
|
|
if patterns is None: patterns = DEFAULT_PATTERNS
|
|
|
|
|
|
|
|
for line in next_line():
|
|
|
|
line = nonef(strip_nl(line))
|
|
|
|
func = False
|
|
|
|
mo = None
|
|
|
|
for p, f in patterns.items():
|
|
|
|
if p is None:
|
|
|
|
func = f
|
|
|
|
mo = None
|
|
|
|
break
|
|
|
|
mo = re.search(p, line)
|
|
|
|
if mo is not None:
|
|
|
|
func = f
|
|
|
|
break
|
|
|
|
if func is not False:
|
|
|
|
line = func(line, mo)
|
|
|
|
sys.stdout.write(line)
|
|
|
|
sys.stdout.write('\n')
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2017-05-01 13:39:40 +04:00
|
|
|
from argparse import ArgumentParser, HelpFormatter
|
|
|
|
if sys.argv[1:2] == ['--compat']:
|
|
|
|
# Avec l'argument --compat, désactiver la classe FancyHelpFormatter qui
|
|
|
|
# se base sur une API non documentée
|
|
|
|
sys.argv = sys.argv[0:1] + sys.argv[2:]
|
|
|
|
FancyHelpFormatter = HelpFormatter
|
|
|
|
else:
|
|
|
|
class FancyHelpFormatter(HelpFormatter):
|
|
|
|
"""Comme HelpFormatter, mais ne touche pas aux lignes qui commencent par les
|
|
|
|
caractères '>>>'. Cela permet de mixer du texte formaté et du texte non
|
|
|
|
formaté.
|
|
|
|
"""
|
|
|
|
def _fill_text(self, text, width, indent):
|
|
|
|
return ''.join([indent + line for line in text.splitlines(True)])
|
|
|
|
def _split_lines(self, text, width):
|
|
|
|
lines = ['']
|
|
|
|
for line in text.splitlines():
|
|
|
|
if line.startswith('>>>'):
|
|
|
|
lines.append(line)
|
|
|
|
lines.append('')
|
|
|
|
else:
|
|
|
|
lines[-1] += '\n' + line
|
|
|
|
lines = filter(None, lines)
|
|
|
|
texts = []
|
|
|
|
for line in lines:
|
|
|
|
if line.startswith('>>>'):
|
|
|
|
texts.append(line[3:])
|
|
|
|
else:
|
|
|
|
texts.extend(super(FancyHelpFormatter, self)._split_lines(line, width))
|
|
|
|
return texts
|
2018-06-18 22:10:00 +04:00
|
|
|
|
|
|
|
pattern_vars = dict(formats=', '.join(FORMATS.keys()))
|
2017-04-12 06:39:48 +04:00
|
|
|
pattern_help = u"""\
|
|
|
|
Ajouter une spécification de pattern et le format dans lequel il doit être affiché.
|
2017-05-01 13:39:40 +04:00
|
|
|
Le format par défaut est red. Les formats valides sont:
|
2018-06-18 22:10:00 +04:00
|
|
|
>>> %(formats)s
|
|
|
|
Les lignes qui ne correspondent à aucun pattern ne sont pas affichées.""" % pattern_vars
|
2017-05-01 13:39:40 +04:00
|
|
|
default_patterns = [u"%s:%s" % (p or '', f.__name__[:-1]) for (p, f) in DEFAULT_PATTERNS.items()]
|
2018-06-18 22:10:00 +04:00
|
|
|
no_defaults_vars = dict(default_patterns='\n'.join([u">>> %s" % pattern for pattern in default_patterns]))
|
2017-05-01 13:39:40 +04:00
|
|
|
no_defaults_help = u"""\
|
2018-06-19 01:15:39 +04:00
|
|
|
Ne pas ajouter les patterns par défaut à ceux définis par l'option --pattern. Sans cette option, les patterns par défaut sont:
|
2018-06-18 22:10:00 +04:00
|
|
|
%(default_patterns)s""" % no_defaults_vars
|
|
|
|
follow_help = u"""Suivre le contenu du fichier spécifié"""
|
|
|
|
presets_help = u"""Utiliser un ensemble prédéfini de paramètres.
|
|
|
|
Si cette option est utilisée, les autres options sont ignorées sauf --pattern qui peut être utilisé pour insérer des motifs avant les motifs par défaut du préréglage.
|
|
|
|
>>>Les valeurs valides sont apache et php.
|
|
|
|
Ces deux préréglages ont en commun de suivre le fichier /var/log/apache2/error.log en mettant en surbrillance les lignes intéressantes."""
|
|
|
|
inputfile_help = u"""\
|
|
|
|
Fichier à afficher ou dont il faut suivre le contenu.
|
|
|
|
Si cet argument n'est pas spécifié, l'entrée standard est utilisée comme source"""
|
|
|
|
|
|
|
|
AP = ArgumentParser(
|
|
|
|
usage=u"%(prog)s [-f] [INPUTFILE]",
|
|
|
|
description=__doc__,
|
|
|
|
formatter_class=FancyHelpFormatter,
|
|
|
|
)
|
2018-10-06 16:46:15 +04:00
|
|
|
AP.set_defaults(inputfile=None, follow=None, patterns=None, defaults=None, presets=None)
|
2018-06-18 22:10:00 +04:00
|
|
|
AP.add_argument('-e', '--pattern', action='append', dest='patterns', metavar='PATTERN:FORMAT', help=pattern_help)
|
|
|
|
AP.add_argument('-z', '--no-defaults', action='store_false', dest='defaults', help=no_defaults_help)
|
2018-06-19 01:15:39 +04:00
|
|
|
AP.add_argument('-d', '--defaults', action='store_true', dest='defaults', help=no_defaults_help)
|
2018-06-18 22:10:00 +04:00
|
|
|
AP.add_argument('-f', '--follow', action='store_true', dest='follow', help=follow_help)
|
|
|
|
AP.add_argument('-p', '--presets', action='store', dest='presets', help=presets_help)
|
|
|
|
AP.add_argument('inputfile', metavar='INPUTFILE', nargs='?', help=inputfile_help)
|
2017-04-12 06:14:09 +04:00
|
|
|
o = AP.parse_args()
|
|
|
|
|
2018-06-18 22:10:00 +04:00
|
|
|
if o.presets is not None:
|
|
|
|
presets = PRESET_ALIASES.get(o.presets, o.presets)
|
|
|
|
if presets not in PRESETS:
|
|
|
|
raise ValueError("%s: argument invalide" % presets)
|
2018-06-19 01:15:39 +04:00
|
|
|
inputfile, follow, opatterns, odefaults = PRESETS.get(presets)
|
2018-10-06 16:46:15 +04:00
|
|
|
if o.inputfile is not None: inputfile = o.inputfile
|
|
|
|
if o.follow is not None: follow = o.follow
|
|
|
|
if o.patterns is not None: opatterns = o.patterns
|
|
|
|
if o.defaults is not None: odefaults = o.defaults
|
2017-04-12 06:14:09 +04:00
|
|
|
else:
|
2018-06-19 01:15:39 +04:00
|
|
|
inputfile, follow, opatterns, odefaults = o.inputfile, o.follow, o.patterns, o.defaults
|
2018-10-06 16:46:15 +04:00
|
|
|
if follow is None: follow = False
|
|
|
|
if odefaults is None: odefaults = True
|
2018-06-18 22:10:00 +04:00
|
|
|
|
2018-06-19 01:15:39 +04:00
|
|
|
if opatterns is None:
|
2018-06-18 22:10:00 +04:00
|
|
|
patterns = DEFAULT_PATTERNS
|
2018-06-19 01:15:39 +04:00
|
|
|
else:
|
2017-04-12 06:14:09 +04:00
|
|
|
patterns = OrderedDict()
|
2018-06-19 01:15:39 +04:00
|
|
|
for pf in opatterns:
|
|
|
|
mo = re.match('(.*):([a-zA-Z,]*)$', pf)
|
2017-04-12 06:14:09 +04:00
|
|
|
if mo is not None:
|
|
|
|
p = mo.group(1)
|
2018-06-19 01:15:39 +04:00
|
|
|
ofs = mo.group(2)
|
2017-04-12 06:14:09 +04:00
|
|
|
else:
|
|
|
|
p = pf
|
2018-06-19 01:15:39 +04:00
|
|
|
ofs = 'red'
|
2017-04-12 06:39:48 +04:00
|
|
|
if p == '': p = None
|
2018-06-19 01:15:39 +04:00
|
|
|
ofs = filter(None, ofs.lower().split(','))
|
|
|
|
fs = None
|
|
|
|
for of in ofs:
|
|
|
|
f = FORMAT_ALIASES.get(of, of)
|
|
|
|
if f not in FORMATS:
|
|
|
|
raise ValueError("%s: format invalide" % of)
|
|
|
|
f = FORMATS[f]
|
|
|
|
if fs is None:
|
|
|
|
fs = f
|
|
|
|
else:
|
|
|
|
def wrapf(line, mo, curf=f, prevf=fs):
|
|
|
|
return curf(prevf(line, mo))
|
|
|
|
fs = wrapf
|
|
|
|
patterns[p] = fs
|
|
|
|
if odefaults:
|
2017-04-12 06:39:48 +04:00
|
|
|
for p, f in DEFAULT_PATTERNS.items():
|
|
|
|
patterns.setdefault(p, f)
|
2017-04-12 06:14:09 +04:00
|
|
|
|
2018-06-18 22:10:00 +04:00
|
|
|
run_tailor(inputfile, follow, patterns)
|