conference-stats/jitsi-log.py

271 lines
8.3 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
import re
import sys
import threading
from time import sleep
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
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:
pass
def getMembers(self):
return self.members
def __str__(self):
return '{}: {}'.format(self.jid, self.owner)
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 badge_respones(self, response_string):
self.send_response(200)
self.send_header("Content-type", "image/svg+xml")
self.end_headers()
self.wfile.write(response_string)
def text_response(self, response_string):
self.send_response(200)
self.send_header("Content-type", "text/plain")
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':
s = badge(left_text='{}: {}'.format('Room', room_name),
right_text=str(len(roomList[room_name].members)),
left_color='blue',
right_color='green')
response_string = bytes(s, "utf-8")
self.badge_respones(response_string)
else:
# default text response
response_string = bytes(room_name
+ '|'
+ str(roomList[room_name].ctime)
+ '|'
+ str(len(roomList[room_name].members))
+ '|', "utf-8")
self.text_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.badge_respones(response_string)
else:
self.send_response(404)
# self.send_header("Content-type", "text/plain")
self.end_headers()
def getRoomName(jid):
return jid.split("@")[0].strip()
def mkDatetime(str_date, str_time):
return datetime.fromisoformat('{} {}'.format(str_date, str_time))
def process_line(line):
"""
process a logfile line, using global object and vars
globals: prog, disposed, owner, member, roomList
line: string
"""
erg = prog.match(line)
if erg:
# detect room creation
new_room = room.match(erg.group(7))
if new_room:
room_name = getRoomName(new_room.group(2))
room_date = mkDatetime(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:
try:
room_name = leaving_match.group(1).strip()
room_member = leaving_match.group(3).strip()
except:
room_name = 'undefined'
room_member = 'undefined'
print('{} leaves {}'.format(room_member, room_name))
if room_name in roomList:
roomList[room_name].delMember(room_member)
def getLogMsg(logfile):
"""
get log messages and commit it to process_line
used pytail module
"""
# for line in Pygtail(logfile, full_lines=True):
# process_line(line)
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