conference-stats/jitsi-log.py

274 lines
8.4 KiB
Python
Executable File

#!/bin/env python3
#
# simple log parser and status server for the jitsi meet video conference tool
#
# Reik Kaps <r31k@k4p5.de> 2020 for Leinelab Hannover
import re
import sys
import threading
from os.path import basename
from datetime import datetime
from argparse import ArgumentParser
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
try:
from sh import tail
from pybadges import badge
except ImportError as e:
print('[Error] Please install sh via pip! {}'.format(e))
sys.exit(1)
# active room dictionary
roomList = {}
# @see https://regex101.com/r/R1B1OL/1
# general regex for a jicofo log line
regex = r"(Jicofo)\s(\d+-\d+-\d+)\s(\d+\:\d+\:\d+\.\d+)\s(\w+):\s\[(\d+)\]\s(.*)\(\)\s(.*)"
prog = re.compile(regex)
##
# new jitsi room
#
room_regex = r"(^Joining\sthe\sroom):(.*)"
room = re.compile(room_regex)
# disposed room
disposed_regex = r"^Disposed\sconference\sfor\sroom\:\s(.*)@(.*)\sconference\scount:\s(\d+)"
disposed = re.compile(disposed_regex)
##
# get the room owner
owner_regex = r"Granted\sowner\sto\s([a-z0-9-]+)\@(.*)\/(.*)"
owner = re.compile(owner_regex)
# get member for room
member_regex = r"^Member\s(.*)@(.*)\/(.*)\s(joined)."
member = re.compile(member_regex)
# remove/leaving member
leaving_regex = r"^Member\s(.*)@(.*)\/(.*)\sis\s(leaving)"
leaving = re.compile(leaving_regex)
class MeetingRoom():
"""
represent a Jitsi Meetingroom
jid: eg room@conference.meet.leinelab.org
ctime: eg 2020-06-01 12:32:02 ..
"""
def __init__(self, jid, ctime):
self.jid = jid
self.ctime = ctime
self.members = []
def setOwner(self, name):
self.owner = name
def getOwner(self):
if hasattr(self, 'owner'):
return self.owner
else:
return None
def addMember(self, name):
self.members.append(name)
def delMember(self, name):
try:
self.members.remove(name)
except ValueError as e:
raise
def getMembers(self):
return self.members
def __str__(self):
return '{}: {}'.format(self.jid, len(self.members))
class logThread(threading.Thread):
def __init__(self, threadID, name, filename):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.filename = filename
def run(self):
while True:
getLogMsg(self.filename)
class MyServer(BaseHTTPRequestHandler):
def do_response(self, response_string, ctype="text/plain"):
self.send_response(200)
self.send_header("Content-type", ctype)
self.end_headers()
self.wfile.write(response_string)
def do_GET(self):
query_components = parse_qs(urlparse(self.path).query)
if "format" in query_components:
respones_format = query_components["format"][0]
else:
respones_format = 'text'
room_name = basename(self.path.split('?')[0])
print('{} - {}'.format(query_components, room_name))
if room_name in roomList:
if respones_format == 'json':
pass
elif respones_format == 'svg':
participants = len(roomList[room_name].members)
if participants > 1:
rtext = '{} participants'.format(str(participants))
else:
rtext = '{} participant'.format(str(participants))
s = badge(left_text='{}: {}'.format('Room', room_name),
right_text=rtext,
left_color='blue',
right_color='green')
response_string = bytes(s, "utf-8")
self.do_response(response_string, "image/svg+xml")
else:
# default text response
response_string = bytes(room_name
+ '|'
+ str(roomList[room_name].ctime)
+ '|'
+ str(len(roomList[room_name].members))
+ '|', "utf-8")
self.do_response(response_string)
else:
if respones_format == 'svg':
s = badge(left_text='{}: {}'.format('Room', room_name),
right_text='empty',
left_color='red',
right_color='darkgray')
response_string = bytes(s, "utf-8")
self.text_response(response_string, "image/svg+xml")
else:
# text or other formats
# return http code 404 (room) not found
self.send_response(404)
self.end_headers()
def process_line(line: str):
"""
process a logfile line, using global object and vars
globals: prog, disposed, owner, member, roomList
Attributes
----------
line: str
a line form the logfile
"""
erg = prog.match(line)
if erg:
# detect room creation
new_room = room.match(erg.group(7))
if new_room:
room_name = new_room.group(2).split("@")[0].strip()
room_date = datetime.fromisoformat('{} {}'.
format(erg.group(2),
erg.group(3)))
print('{} {} created'.format(room_date, room_name))
currentRoom = MeetingRoom(room_name, room_date)
if room_name in roomList:
# delete old room and recreate it
del roomList[room_name]
roomList[room_name] = currentRoom
else:
roomList[room_name] = currentRoom
disposed_room = disposed.match(erg.group(7))
if disposed_room:
room_name = disposed_room.group(1)
print('-> room disposed: {}'.format(room_name))
if room_name in roomList:
del roomList[room_name]
owner_match = owner.match(erg.group(7))
if owner_match:
room_name = owner_match.group(1).strip()
room_owner = owner_match.group(3).strip()
if room_name in roomList:
roomList[room_name].setOwner(room_owner)
member_match = member.match(erg.group(7))
if member_match:
room_name = member_match.group(1).strip()
room_member = member_match.group(3).strip()
print('{} enters {}'.format(room_member, room_name))
if room_name in roomList:
roomList[room_name].addMember(room_member)
# participant is leaving a the room
leaving_match = leaving.match(erg.group(7))
if leaving_match:
room_name = leaving_match.group(1).strip()
room_member = leaving_match.group(3).strip()
print('{} leaves {}'.format(room_member, room_name))
if room_name in roomList:
roomList[room_name].delMember(room_member)
def getLogMsg(logfile: str):
"""
get log messages and commit it to process_line
used tail from sh module
Attributes:
-----------
logfile: str
path as string to logfile
"""
for line in tail("-f", logfile, _iter=True):
process_line(line)
if __name__ == '__main__':
# start cmdline processing
parser = ArgumentParser(description='Jitsi Meet Conference Stats')
parser.add_argument('--log', '-l',
help='full path to jicofo logfile eg.' +
' /var/log/jitsi/jicofo.log')
parser.add_argument('--verbose', '-v',
help='be verbose eg. for debugging',
action='store_true')
parser.add_argument('--port', '-p',
help='set the http server port',
default=9999)
args = parser.parse_args()
if args.log:
try:
loggerThread = logThread(1, "Thread-Logger", args.log)
loggerThread.start()
except Exception as e:
print('Error: unable to start threads {}'.format(e))
hostName = 'localhost'
serverPort = args.port
webServer = HTTPServer((hostName, serverPort), MyServer)
try:
print("Server started http://%s:%s" % (hostName, serverPort))
webServer.serve_forever()
except:
pass