2016-06-07 11:42:53 -04:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2016-05-08 15:32:37 -04:00
|
|
|
import logging
|
|
|
|
import re
|
2016-06-19 16:10:35 -04:00
|
|
|
from collections import namedtuple
|
2016-05-08 15:32:37 -04:00
|
|
|
from datetime import datetime
|
|
|
|
from glob import glob
|
2016-06-19 16:10:35 -04:00
|
|
|
from os.path import basename
|
2016-05-08 15:32:37 -04:00
|
|
|
|
2016-05-18 16:42:42 -04:00
|
|
|
from tsstats.client import Client, Clients
|
2016-05-08 15:32:37 -04:00
|
|
|
|
2016-06-19 16:10:35 -04:00
|
|
|
re_log_filename = re.compile(r'ts3server_(?P<date>\d{4}-\d\d-\d\d)'
|
|
|
|
'__(?P<time>\d\d_\d\d_\d\d.\d+)_(?P<sid>\d).log')
|
2016-05-24 16:30:16 -04:00
|
|
|
re_log_entry = re.compile('(?P<timestamp>\d{4}-\d\d-\d\d\ \d\d:\d\d:\d\d.\d+)'
|
2016-06-07 10:51:55 -04:00
|
|
|
'\|\ *(?P<level>\w+)\ *\|\ *(?P<component>\w+)\ *'
|
|
|
|
'\|\ *(?P<sid>\d+)\ *\|\ *(?P<message>.*)')
|
2016-05-08 15:32:37 -04:00
|
|
|
re_dis_connect = re.compile(r"'(.*)'\(id:(\d*)\)")
|
|
|
|
re_disconnect_invoker = re.compile(
|
|
|
|
r'invokername=(.*)\ invokeruid=(.*)\ reasonmsg'
|
|
|
|
)
|
|
|
|
|
2016-05-25 14:08:48 -04:00
|
|
|
log_timestamp_format = '%Y-%m-%d %H:%M:%S.%f'
|
|
|
|
|
2016-06-19 16:10:35 -04:00
|
|
|
TimedLog = namedtuple('TimedLog', ['path', 'timestamp'])
|
|
|
|
|
2016-05-08 15:32:37 -04:00
|
|
|
|
2016-05-10 16:50:34 -04:00
|
|
|
logger = logging.getLogger('tsstats')
|
|
|
|
|
|
|
|
|
2016-06-19 16:26:19 -04:00
|
|
|
def parse_logs(log_glob):
|
|
|
|
'''
|
|
|
|
parse logs from `log_glob`
|
|
|
|
'''
|
|
|
|
vserver_clients = {}
|
|
|
|
for virtualserver_id, logs in _sort_logfiles(log_glob):
|
|
|
|
clients = Clients()
|
|
|
|
for log in logs:
|
|
|
|
_parse_details(clients=clients)
|
|
|
|
if len(clients) >= 1:
|
|
|
|
vserver_clients[virtualserver_id] = clients
|
|
|
|
return vserver_clients
|
|
|
|
|
|
|
|
|
2016-06-20 15:31:59 -04:00
|
|
|
def _bundle_logs(logs):
|
2016-05-30 14:23:03 -04:00
|
|
|
'''
|
2016-06-20 15:31:59 -04:00
|
|
|
bundle `logs` by virtualserver-id
|
|
|
|
and sort by timestamp from filename (if exists)
|
2016-06-19 15:37:00 -04:00
|
|
|
|
2016-06-20 15:31:59 -04:00
|
|
|
:param logs: list of paths to logfiles
|
2016-05-30 14:23:03 -04:00
|
|
|
|
2016-06-20 15:31:59 -04:00
|
|
|
:type logs: list
|
2016-05-30 14:23:03 -04:00
|
|
|
|
2016-06-20 15:31:59 -04:00
|
|
|
:return: `logs` bundled by virtualserver-id and sorted by timestamp
|
2016-06-19 16:21:02 -04:00
|
|
|
:rtype: dict{str: [TimedLog]}
|
2016-05-30 14:23:03 -04:00
|
|
|
'''
|
2016-06-19 16:10:35 -04:00
|
|
|
vserver_logfiles = {} # sid: [/path/to/log1, ..., /path/to/logn]
|
2016-06-20 15:31:59 -04:00
|
|
|
for log in logs:
|
2016-06-19 16:10:35 -04:00
|
|
|
# try to get date and sid from filename
|
2016-06-20 15:31:59 -04:00
|
|
|
match = re_log_filename.match(basename(log))
|
2016-06-19 16:10:35 -04:00
|
|
|
if match:
|
|
|
|
match = match.groupdict()
|
|
|
|
timestamp = datetime.strptime('{0} {1}'.format(
|
|
|
|
match['date'], match['time'].replace('_', ':')))
|
2016-06-20 15:31:59 -04:00
|
|
|
tl = TimedLog(log, timestamp)
|
2016-06-19 16:10:35 -04:00
|
|
|
sid = match['sid']
|
|
|
|
if sid in vserver_logfiles:
|
|
|
|
# if already exists, keep list sorted by timestamp
|
|
|
|
vserver_logfiles[sid].apppend(tl)
|
|
|
|
vserver_logfiles[sid] =\
|
|
|
|
sorted(vserver_logfiles[sid],
|
|
|
|
key=lambda tl: tl.timestamp)
|
|
|
|
else:
|
|
|
|
# if not exists, just create a list
|
|
|
|
vserver_logfiles[match['sid']] = [tl]
|
|
|
|
else:
|
|
|
|
# fallback to plain sorting
|
|
|
|
vserver_logfiles.setdefault('', [])\
|
2016-06-20 15:31:59 -04:00
|
|
|
.append(TimedLog(log, None))
|
2016-06-20 15:38:57 -04:00
|
|
|
vserver_logfiles[''] =\
|
|
|
|
sorted(vserver_logfiles[''],
|
|
|
|
key=lambda tl: tl.path)
|
2016-06-19 16:10:35 -04:00
|
|
|
return vserver_logfiles
|
2016-05-23 15:50:10 -04:00
|
|
|
|
2016-05-08 15:32:37 -04:00
|
|
|
|
2016-06-19 15:44:23 -04:00
|
|
|
def _parse_details(log_path, ident_map=None, clients=None, online_dc=True):
|
2016-05-30 14:23:03 -04:00
|
|
|
'''
|
2016-06-19 15:37:00 -04:00
|
|
|
extract details from log-files
|
|
|
|
|
|
|
|
detailed parsing is done here: onlinetime, kicks, pkicks, bans, pbans
|
2016-05-30 14:23:03 -04:00
|
|
|
|
|
|
|
:param log_path: path to log-file
|
2016-06-08 16:11:37 -04:00
|
|
|
:param ident_map: :doc:`identmap`
|
2016-05-30 14:23:03 -04:00
|
|
|
:param clients: clients-object to add parsing-results to
|
2016-06-12 10:54:49 -04:00
|
|
|
:param online_cd: disconnect online clients after parsing
|
2016-05-30 14:23:03 -04:00
|
|
|
|
|
|
|
:type log_path: str
|
|
|
|
:type ident_map: dict
|
|
|
|
:type clients: tsstats.client.Clients
|
2016-06-12 10:54:49 -04:00
|
|
|
:type online_cd: bool
|
2016-05-30 14:23:03 -04:00
|
|
|
|
|
|
|
:return: parsed clients
|
|
|
|
:rtype: tsstats.client.Clients
|
|
|
|
'''
|
2016-05-24 15:48:11 -04:00
|
|
|
if not clients:
|
|
|
|
clients = Clients(ident_map)
|
2016-05-23 15:50:10 -04:00
|
|
|
log_file = open(log_path)
|
|
|
|
# process lines
|
|
|
|
logger.debug('Started parsing of %s', log_file.name)
|
|
|
|
for line in log_file:
|
2016-05-24 16:30:16 -04:00
|
|
|
match = re_log_entry.match(line)
|
|
|
|
if not match:
|
|
|
|
logger.debug('No match: "%s"', line)
|
|
|
|
continue
|
|
|
|
match = match.groupdict()
|
2016-06-12 11:52:44 -04:00
|
|
|
logdatetime = datetime.strptime(match['timestamp'],
|
|
|
|
log_timestamp_format)
|
2016-05-24 16:30:16 -04:00
|
|
|
message = match['message']
|
|
|
|
if message.startswith('client'):
|
|
|
|
nick, clid = re_dis_connect.findall(message)[0]
|
2016-05-23 15:50:10 -04:00
|
|
|
client = clients.setdefault(clid, Client(clid, nick))
|
|
|
|
client.nick = nick # set nick to display changes
|
2016-05-24 16:30:16 -04:00
|
|
|
if message.startswith('client connected'):
|
2016-05-23 15:50:10 -04:00
|
|
|
client.connect(logdatetime)
|
2016-05-24 16:30:16 -04:00
|
|
|
elif message.startswith('client disconnected'):
|
2016-05-23 15:50:10 -04:00
|
|
|
client.disconnect(logdatetime)
|
2016-05-24 16:30:16 -04:00
|
|
|
if 'invokeruid' in message:
|
2016-05-23 15:50:10 -04:00
|
|
|
re_disconnect_data = re_disconnect_invoker.findall(
|
2016-05-24 16:30:16 -04:00
|
|
|
message)
|
2016-05-23 15:50:10 -04:00
|
|
|
invokernick, invokeruid = re_disconnect_data[0]
|
|
|
|
invoker = clients.setdefault(invokeruid,
|
|
|
|
Client(invokeruid))
|
|
|
|
invoker.nick = invokernick
|
2016-05-24 16:30:16 -04:00
|
|
|
if 'bantime' in message:
|
2016-05-23 15:50:10 -04:00
|
|
|
invoker.ban(client)
|
|
|
|
else:
|
|
|
|
invoker.kick(client)
|
2016-06-12 10:54:49 -04:00
|
|
|
if online_dc:
|
|
|
|
for client in clients:
|
|
|
|
if client.connected:
|
2016-06-12 11:52:44 -04:00
|
|
|
client.disconnect(datetime.utcnow())
|
2016-06-12 10:54:49 -04:00
|
|
|
client.connected += 1
|
2016-05-23 15:50:10 -04:00
|
|
|
logger.debug('Finished parsing of %s', log_file.name)
|
2016-05-08 15:32:37 -04:00
|
|
|
return clients
|