Merge branch 'multi_vserver'

This commit is contained in:
Thor77 2016-06-24 21:41:27 +02:00
commit 573838c35e
4 changed files with 113 additions and 27 deletions

View File

@ -69,8 +69,13 @@ def main(config=None, idmap=None, log=None,
if not log or not output: if not log or not output:
raise InvalidConfiguration('log or output missing') raise InvalidConfiguration('log or output missing')
clients = parse_logs(log, ident_map=identmap, online_dc=noonlinedc) sid_clients = parse_logs(log, ident_map=identmap, online_dc=noonlinedc)
render_template(clients, output=abspath(output)) for sid, clients in sid_clients.items():
if sid:
ext = '.{}'.format(sid)
else:
ext = ''
render_template(clients, output=abspath(output + ext))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -2,11 +2,15 @@
import logging import logging
import re import re
from collections import namedtuple
from datetime import datetime from datetime import datetime
from glob import glob from glob import glob
from os.path import basename
from tsstats.client import Client, Clients from tsstats.client import Client, Clients
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')
re_log_entry = re.compile('(?P<timestamp>\d{4}-\d\d-\d\d\ \d\d:\d\d:\d\d.\d+)' re_log_entry = re.compile('(?P<timestamp>\d{4}-\d\d-\d\d\ \d\d:\d\d:\d\d.\d+)'
'\|\ *(?P<level>\w+)\ *\|\ *(?P<component>\w+)\ *' '\|\ *(?P<level>\w+)\ *\|\ *(?P<component>\w+)\ *'
'\|\ *(?P<sid>\d+)\ *\|\ *(?P<message>.*)') '\|\ *(?P<sid>\d+)\ *\|\ *(?P<message>.*)')
@ -17,32 +21,83 @@ re_disconnect_invoker = re.compile(
log_timestamp_format = '%Y-%m-%d %H:%M:%S.%f' log_timestamp_format = '%Y-%m-%d %H:%M:%S.%f'
TimedLog = namedtuple('TimedLog', ['path', 'timestamp'])
logger = logging.getLogger('tsstats') logger = logging.getLogger('tsstats')
def parse_logs(log_glob, ident_map=None, *args, **kwargs): def parse_logs(log_glob, ident_map=None, *args, **kwargs):
''' '''
parse logs specified by globbing pattern `log_glob` parse logs from `log_glob`
:param log_glob: path to log-files (supports globbing) :param log_glob: path to server-logs (supports globbing)
:param ident_map: :doc:`identmap` :param ident_map: identmap used for Client-initializations
:type log_glob: str :type log_glob: str
:type ident_map: dict :type ident_map: dict
:return: parsed clients :return: clients bundled by virtual-server
:rtype: tsstats.client.Clients :rtype: dict
''' '''
vserver_clients = {}
for virtualserver_id, logs in\
_bundle_logs(log_file for log_file in glob(log_glob)).items():
clients = Clients(ident_map) clients = Clients(ident_map)
for log_file in sorted(log_file for log_file in glob(log_glob)): for log in logs:
clients = parse_log(log_file, ident_map, clients, *args, **kwargs) _parse_details(log.path, clients=clients, *args, **kwargs)
return clients if len(clients) >= 1:
vserver_clients[virtualserver_id] = clients
return vserver_clients
def parse_log(log_path, ident_map=None, clients=None, online_dc=True): def _bundle_logs(logs):
''' '''
parse log-file at `log_path` bundle `logs` by virtualserver-id
and sort by timestamp from filename (if exists)
:param logs: list of paths to logfiles
:type logs: list
:return: `logs` bundled by virtualserver-id and sorted by timestamp
:rtype: dict{str: [TimedLog]}
'''
vserver_logfiles = {} # sid: [/path/to/log1, ..., /path/to/logn]
for log in logs:
# try to get date and sid from filename
match = re_log_filename.match(basename(log))
if match:
match = match.groupdict()
timestamp = datetime.strptime('{0} {1}'.format(
match['date'], match['time'].replace('_', ':')),
log_timestamp_format)
tl = TimedLog(log, timestamp)
sid = match['sid']
if sid in vserver_logfiles:
# if already exists, keep list sorted by timestamp
vserver_logfiles[sid].append(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('', [])\
.append(TimedLog(log, None))
vserver_logfiles[''] =\
sorted(vserver_logfiles[''],
key=lambda tl: tl.path)
return vserver_logfiles
def _parse_details(log_path, ident_map=None, clients=None, online_dc=True):
'''
extract details from log-files
detailed parsing is done here: onlinetime, kicks, pkicks, bans, pbans
:param log_path: path to log-file :param log_path: path to log-file
:param ident_map: :doc:`identmap` :param ident_map: :doc:`identmap`
@ -57,7 +112,7 @@ def parse_log(log_path, ident_map=None, clients=None, online_dc=True):
:return: parsed clients :return: parsed clients
:rtype: tsstats.client.Clients :rtype: tsstats.client.Clients
''' '''
if not clients: if clients is None:
clients = Clients(ident_map) clients = Clients(ident_map)
log_file = open(log_path) log_file = open(log_path)
# process lines # process lines

View File

@ -1,17 +1,17 @@
from datetime import timedelta from datetime import datetime, timedelta
from time import sleep from time import sleep
import pytest import pytest
from tsstats.exceptions import InvalidLog from tsstats.exceptions import InvalidLog
from tsstats.log import parse_log, parse_logs from tsstats.log import TimedLog, _bundle_logs, _parse_details, parse_logs
testlog_path = 'tsstats/tests/res/test.log' testlog_path = 'tsstats/tests/res/test.log'
@pytest.fixture @pytest.fixture
def clients(): def clients():
return parse_log(testlog_path, online_dc=False) return _parse_details(testlog_path, online_dc=False)
def test_log_client_count(clients): def test_log_client_count(clients):
@ -39,20 +39,46 @@ def test_log_pbans(clients):
assert clients['2'].pbans == 1 assert clients['2'].pbans == 1
@pytest.mark.parametrize("logs,bundled", [
(
['l1.log', 'l2.log'],
{'': [TimedLog('l1.log', None), TimedLog('l2.log', None)]}
),
(
[
'ts3server_2016-06-06__14_22_09.527229_1.log',
'ts3server_2017-07-07__15_23_10.638340_1.log'
],
{
'1': [
TimedLog('ts3server_2016-06-06__14_22_09.527229_1.log',
datetime(year=2016, month=6, day=6, hour=14,
minute=22, second=9, microsecond=527229)),
TimedLog('ts3server_2017-07-07__15_23_10.638340_1.log',
datetime(year=2017, month=7, day=7, hour=15,
minute=23, second=10, microsecond=638340))
]
}
)
])
def test_log_bundle(logs, bundled):
assert _bundle_logs(logs) == bundled
def test_log_invalid(): def test_log_invalid():
with pytest.raises(InvalidLog): with pytest.raises(InvalidLog):
parse_log('tsstats/tests/res/test.log.broken') _parse_details('tsstats/tests/res/test.log.broken')
def test_log_multiple():
assert len(parse_log(testlog_path, online_dc=False)) == \
len(parse_logs(testlog_path, online_dc=False))
@pytest.mark.slowtest @pytest.mark.slowtest
def test_log_client_online(): def test_log_client_online():
clients = parse_log(testlog_path) clients = _parse_details(testlog_path)
old_onlinetime = int(clients['1'].onlinetime.total_seconds()) old_onlinetime = int(clients['1'].onlinetime.total_seconds())
sleep(2) sleep(2)
clients = parse_log(testlog_path) clients = _parse_details(testlog_path)
assert int(clients['1'].onlinetime.total_seconds()) == old_onlinetime + 2 assert int(clients['1'].onlinetime.total_seconds()) == old_onlinetime + 2
def test_parse_logs():
assert len(_parse_details(testlog_path)) ==\
len(parse_logs(testlog_path)[''])

View File

@ -5,12 +5,12 @@ from os import remove
import pytest import pytest
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from tsstats.log import parse_log from tsstats.log import _parse_details
from tsstats.template import render_template from tsstats.template import render_template
from tsstats.utils import seconds_to_text from tsstats.utils import seconds_to_text
output_path = 'tsstats/tests/res/output.html' output_path = 'tsstats/tests/res/output.html'
clients = parse_log('tsstats/tests/res/test.log') clients = _parse_details('tsstats/tests/res/test.log')
logger = logging.getLogger('tsstats') logger = logging.getLogger('tsstats')