Projet

Général

Profil

logparsing.py

parseur sarg - Joël Cuissinat, 19/03/2010 14:35

Télécharger (8,84 ko)

 
1
# -*- coding: UTF-8 -*-
2
###########################################################################
3
# Eole NG - 2007
4
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
5
# Licence CeCill  cf /root/LicenceEole.txt
6
# eole@ac-dijon.fr
7
#
8
# Outil de parsing des fichiers de logs renvoie les lignes de logs par ip,
9
# url, login, tranche horaire
10
#
11
###########################################################################
12
""" Outils de parsing des fichiers de logs """
13
from twisted.python import log
14
from ead2.backend.actions.lib.logparser.config import LOG_DIR, TEMP_FILENAME, LOGPARSER_DIR
15
from ead2.backend.actions.lib.logparser import file_listing
16
from os.path import join, isdir, splitext
17
import commands
18
from os import unlink
19
from time import ctime, strptime, mktime, strftime, localtime
20
import locale
21

    
22
## fonction à utiliser de l'extérieur du module
23
def get_logs(filenames=[], ip=None, url=None, login=None, hours=(None, None), denied=None):
24
    """ renvoie les logs selon les critères fournis
25
        @filenmames: noms des fichiers à traiter
26
        @ip: ip de l'utilisateur
27
        @url: url visitée
28
        @login: login de l'utilisateur
29
        @hours: tranche horaire
30
        @denied: logs interdits?
31
    """
32
    if type(filenames) != list:
33
        filenames = [filenames]
34
    if hours == (None, None):
35
        return _get_filtered_logs(filenames, ip=ip, url=url, login=login, denied=denied)
36
    if None in hours:
37
        raise "Erreur : Il manque l'heure de début ou de fin."
38
    result = []
39
    beg, end = hours
40
    for filename in filenames:
41
        date = _get_file_date(filename)
42
        if date:
43
            timestamps = _construct_datetime_range(date, beg, end)
44
            parsed_logs = _get_filtered_log(filename, ip=ip, url=url,
45
                            login=login, date=timestamps, denied=denied)
46
            result.extend(parsed_logs)
47
    return result
48

    
49
## parse les logs d'un ensemble de fichier
50
def _get_filtered_logs(filenames=[], url=None, ip=None, login=None, denied=None):
51
    """ renvoie les logs filtrés pour les fichiers filenames """
52
    if filenames == []:
53
        raise Exception, "Précisez les jours de la recherche logparsing.py"
54
    result = []
55
    for filename in filenames:
56
        result.extend(_get_filtered_log(filename, url=url, ip=ip, login=login,
57
            denied=denied))
58
    return result
59

    
60
## parse les logs de filename selon url, ip, login, date
61
## en supprimant les doublons (s'appuie sur parser.sh)
62
def _get_filtered_log(filename, url=None, ip=None, login=None, date=None, denied=None):
63
    """ renvoie les logs filtrés selon les parametres envoyés """
64
    filepath = join(LOG_DIR, filename)
65
    cmd = ''
66
    if splitext(filename)[1] == '.gz':
67
        filetype = "gz"
68
        act_grep = 'zgrep'
69
        act_cat = 'zcat'
70
    else:
71
        filetype = "plain"
72
        act_grep = 'grep'
73
        act_cat = 'cat'
74
    ## on a des " dans les champs du csv, donc on doit les escaper
75
    ## pour le passage de la commande au script
76
    ## (une fois pour python, une fois pour grep)
77
    grep = act_grep + ' \'%s\' ' + filepath
78
    othergrep = ' | grep \'%s\' '
79
    if url is not None:
80
        cmd += grep % url
81
        grep = othergrep
82
    if ip is not None:
83
        # FIXME
84
        #ip = '\\"%s\\"' % ip
85
        ip = '%s' % ip
86
        cmd += grep % ip
87
        grep = othergrep
88
    if login is not None:
89
        # FIXME
90
        #esc_login = '\\"%s\\"' % login
91
        esc_login = '%s' % login
92
        cmd += grep % esc_login
93
        grep = othergrep
94
    if denied is not None:
95
        cmd += grep % 'DENIED'
96
    if date is not None:
97
        if cmd == '':
98
            if filetype == "gz" :
99
                cmd = 'gunzip -c ' + filepath + '| awk \''+str(date[0])+'<\$1 && '+str(date[1])+'>\$1 { print \$N }\' '
100
            else:
101
                cmd =  'awk \''+str(date[0])+'<\$1 && '+str(date[1])+'>\$1 { print \$N }\' ' + filepath 
102
        else:
103
            cmd += '| awk \''+str(date[0])+'<\$1 && '+str(date[1])+'>\$1 { print \$N }\' '
104
    if cmd != '':
105
        parsersh = join(LOGPARSER_DIR, 'parser.sh')
106
        cmd = "%s \"%s\" \"%s\"" % (parsersh, cmd, TEMP_FILENAME)
107
    else:
108
        #FIXME: on passe par un fichier temporaire pour éviter de dézipper
109
        # (utilisation de zcat)
110
        # on doit pouvoir faire mieux
111
        date_range = _construct_datetime_range(_get_file_date(filename), 0, 24)
112
        if filetype == "gz":
113
            cmd = 'gunzip -c ' + filepath + '| awk \''+str(date_range[0])+'<$1 && '+str(date_range[1])+'>$1 { print $N }\' >' + TEMP_FILENAME
114
        else:
115
            cmd =  'awk \''+str(date_range[0])+'<$1 && '+str(date_range[1])+'>$1 { print $N }\' ' + filepath + '>' + TEMP_FILENAME
116
#        cmd = "%s %s >%s"%(act_cat, filepath, TEMP_FILENAME)
117
    return_code, return_value = commands.getstatusoutput(cmd)
118
    if return_code % 256:
119
        log.err("Erreur: logparsing.py %s" % return_value)
120
        raise Exception, "Erreur : le parsing en ligne de commande a échoué"
121
    grepped_datas = file(TEMP_FILENAME).read().splitlines()
122
    unlink(TEMP_FILENAME)
123
    parsed_datas = list(_parse_log_lines(grepped_datas))
124
    p_datas = []
125
    for line in parsed_datas:
126
        if line not in p_datas:
127
            p_datas.append(line)
128
    if login is not None:
129
        parsed_datas = _filter_login(p_datas, login)
130
    return p_datas
131

    
132
# cree un dico depuis des lignes de logs
133
#def _parse_log_lines(datas):
134
#    """ renvoie les logs sous forme de liste de dico """
135
#    for line in datas:
136
#        if len(line.split(','))>3:
137
#            mylog = {'login':line.split(',')[1][1:-1],
138
#                            'date':line.split(',')[0][1:-1],
139
#                            'url':_get_domain(line.split(',')[3][1:-1]),
140
#                            'ip':line.split(',')[2][1:-1],
141
#                            'longurl':_replace_cars(line.split(',')[3][1:-1])}
142
#            if 'DENIED' in line:
143
#                mylog['denied'] = 'True'
144
#            yield mylog
145

    
146
def _parse_log_lines(datas):
147
    """ renvoie les logs sous forme de liste de dico """
148
    #locale.setlocale(locale.LC_ALL,'fr_FR.UTF-8')
149
    for line in datas:
150
        if len(line.split())>3:
151
            log = {'login':line.split()[7],
152
                            'date':ctime(float(line.split()[0])),
153
                            'url':_get_domain(line.split()[6]),
154
                            'ip':line.split()[2],
155
                            'longurl':line.split()[6]}
156
            if 'DENIED' in line:
157
                log['denied'] = 'True'
158
            #locale.setlocale(locale.LC_ALL,'fr_FR.UTF-8')
159
            log['date'] = strftime("%A %d %B %Y %X", localtime(float(line.split()[0])))
160
            yield log
161

    
162

    
163
# recuper le nom de domaine d'une url
164
def _get_domain(url):
165
    """ renvoie le nom de domaine d'une url choisie """
166
    return url.split('/')[2]
167

    
168
def _replace_cars(url):
169
    """ remplacement de caractères indésirables """
170
    return url.replace('%', '&#37;')
171

    
172
# repasse derrière pour controler le login
173
def _filter_login(datas, login):
174
    """ renvoie les logs de 'login' (filtre les urls contenant 'login) """
175
    return [a for a in datas if a['login'] == login]
176

    
177
### utilitaires pour le parsing par date
178
def _construct_datetime_range(date, beg, end):
179
    """ renvoie la liste des chaines "date heure:"
180
    qui correspondent à la beach hour (plage horaire) demandée
181
    """
182
    try:
183
        beg = int(beg)
184
        end = int(end)
185
    except:
186
        raise Exception, "Erreur : Le début ou la fin de la plage horaire demandée ne sont pas des entiers."
187
    if beg >= end:
188
        raise Exception, "Erreur : Le début de la plage horaire demandée est supérieure ou égale à la fin."
189
    #on initialise le time stamp a 0h
190
    tmp = ctime(float(date)).split()
191
    tmp[3] = '00:00:00'
192
    start_timestamp = mktime(strptime((' '.join(tmp))))
193
    plage = [float(start_timestamp)+float(beg*3600), float(start_timestamp)+float(end*3600)]
194
    return plage
195

    
196
def _get_file_date(filename):
197
    """ renvoie la date d'un fichier de log en
198
        parsant la première ligne
199
    """
200
    filepath = join(LOG_DIR, filename)
201
    if splitext(filename)[1] == '.gz':
202
        cmd = "zgrep -m1 . %s" % filepath
203
    else:
204
        cmd = "grep -m1 . %s" % filepath
205
    return_code, first_file_line = commands.getstatusoutput(cmd)#'grep -m1 , %s'%filepath)
206
    if return_code % 256:
207
        log.err("Erreur : logparsing.py %s pour la commande : %s, code retour: %s" % (first_file_line, cmd, return_code))
208
        raise Exception, "Une erreur s'est produite au parsing de %s : %s" % (filepath, first_file_line)
209
    if first_file_line:
210
        date = first_file_line.split()[0]
211
        return date
212
    else:
213
        raise Exception, "Une erreur s'est produite au parsing de %s : %s le fichier semble vide" % (filepath, first_file_line)
214

    
215
def reformate_date(date):
216
    """ renvoie une date à un nouveau format: (jour,mois,année)
217
    depuis une date au format : '"année:mois:jour' """
218
    elts = date.split()
219
    year = elts[4]
220
    month = elts[1]
221
    day = elts[0]
222
    return day, month, year