Skip to content

Commit 5aa8eb8

Browse files
committed
Added A Lot
- Open/Save - Dummy (Test) Messages - Actual RFC 5424 Parsing - More Column Headers - Toggleable Column Headers
1 parent 627dd92 commit 5aa8eb8

File tree

5 files changed

+591
-83
lines changed

5 files changed

+591
-83
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# PySysLogQt
2-
Simple `syslog` viewer written in `Python 3`.
2+
Extremely simple `syslog` viewer written in `Python 3`.
33

44
**Note: It is nothing too fancy at this point in time. Currently, there are
5-
no intentions of making this too fancy. Features may be requested in the
5+
no intentions of making this quite extensive. Features may be requested in the
66
_Issues_ page.**
77

88
## Execution

main/MainWindow.py

Lines changed: 207 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,98 +4,122 @@
44
Date: 03/12/2020
55
"""
66
from PyQt5 import QtWidgets, QtCore, QtGui, uic
7-
import socketserver, datetime, re
7+
import socketserver, datetime, re, os
8+
import logging.handlers
89

910
from main.ServerDialog import ServerDialog
1011
from main.Threading import WorkerThread
1112

12-
TABLE = None
13+
WINDOW = None
1314

14-
class SyslogUDPHandler(socketserver.BaseRequestHandler):
15-
levels = {
16-
0: "EMERGENCY",
17-
1: "ALERT",
18-
2: "CRITICAL",
19-
3: "ERROR",
20-
4: "WARNING",
21-
5: "NOTICE",
22-
6: "INFO",
23-
7: "DEBUG"
24-
}
25-
26-
facs = {
27-
0: "Kernel",
28-
1: "User-Level",
29-
2: "Mail System",
30-
3: "System Daemons",
31-
4: "Security 1",
32-
5: "Internal",
33-
6: "Line Printer",
34-
7: "Network News",
35-
8: "UUCP",
36-
9: "Clock Daemon",
37-
10: "Security 2",
38-
11: "FTP Daemon",
39-
12: "NTP",
40-
13: "Log Audit",
41-
14: "Log Alert",
42-
15: "Scheduling",
43-
16: "Local 0",
44-
17: "Local 1",
45-
18: "Local 2",
46-
19: "Local 3",
47-
20: "Local 4",
48-
21: "Local 5",
49-
22: "Local 6",
50-
23: "Local 7"
51-
}
52-
53-
colors = {
54-
"EMERGENCY": (255, 130, 130),
55-
"ALERT": (255, 130, 130),
56-
"CRITICAL": (255, 130, 130),
57-
"ERROR": (255, 153, 94),
58-
"WARNING": (255, 222, 130),
59-
"NOTICE": (130, 255, 150),
60-
"INFO": (130, 220, 255),
61-
"DEBUG": (212, 212, 212)
62-
}
15+
logging.addLevelName(25, "NOTICE")
16+
logging.addLevelName(60, "ALERT")
17+
logging.addLevelName(70, "EMERG")
18+
19+
LEVELS = {
20+
0: "EMERGENCY",
21+
1: "ALERT",
22+
2: "CRITICAL",
23+
3: "ERROR",
24+
4: "WARNING",
25+
5: "NOTICE",
26+
6: "INFO",
27+
7: "DEBUG"
28+
}
29+
30+
FACS = {
31+
0: "Kernel",
32+
1: "User-Level",
33+
2: "Mail System",
34+
3: "System Daemons",
35+
4: "Security 1",
36+
5: "Internal",
37+
6: "Line Printer",
38+
7: "Network News",
39+
8: "UUCP",
40+
9: "Clock Daemon",
41+
10: "Security 2",
42+
11: "FTP Daemon",
43+
12: "NTP",
44+
13: "Log Audit",
45+
14: "Log Alert",
46+
15: "Scheduling",
47+
16: "Local 0",
48+
17: "Local 1",
49+
18: "Local 2",
50+
19: "Local 3",
51+
20: "Local 4",
52+
21: "Local 5",
53+
22: "Local 6",
54+
23: "Local 7"
55+
}
56+
57+
COLORS = {
58+
"EMERGENCY": (255, 130, 130),
59+
"ALERT": (255, 130, 130),
60+
"CRITICAL": (255, 130, 130),
61+
"ERROR": (255, 153, 94),
62+
"WARNING": (255, 222, 130),
63+
"NOTICE": (130, 255, 150),
64+
"INFO": (130, 220, 255),
65+
"DEBUG": (212, 212, 212)
66+
}
6367

68+
class SyslogUDPHandler(socketserver.BaseRequestHandler):
6469
def handle(self):
6570
data = bytes.decode(self.request[0].strip())
66-
match = re.search(r"^<\d+>", data)
67-
prio = int(match.group(0)[1:-1])
6871

69-
f = prio // 8
70-
level = self.levels[prio % 8]
72+
global WINDOW
73+
WINDOW.add(data)
7174

72-
global TABLE
73-
row = TABLE.rowCount()
74-
TABLE.insertRow(row)
75-
TABLE.setItem(row, 0, QtWidgets.QTableWidgetItem(str(datetime.date.today())))
76-
TABLE.setItem(row, 1, QtWidgets.QTableWidgetItem(str(datetime.datetime.now().time())))
77-
TABLE.setItem(row, 2, QtWidgets.QTableWidgetItem(self.facs[f]))
78-
TABLE.setItem(row, 3, QtWidgets.QTableWidgetItem(level))
79-
TABLE.setItem(row, 4, QtWidgets.QTableWidgetItem(data[match.span(0)[1]:-1]))
80-
for i in range(5):
81-
if i in [2, 3]:
82-
TABLE.item(row, i).setTextAlignment(QtCore.Qt.AlignCenter)
83-
TABLE.item(row, i).setBackground(QtGui.QColor(*self.colors[level]))
8475

76+
from main.parser import parse
77+
FILE_PREFIX = 'file://'
8578
class MainWindow(QtWidgets.QMainWindow):
8679
def __init__(self):
8780
super(MainWindow, self).__init__(flags=QtCore.Qt.WindowFlags())
8881
uic.loadUi("main/MainWindow.ui", self)
82+
self.messages.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
83+
8984
self.server = None
90-
self.pb_change.clicked.connect(self.change)
85+
self.running = False
86+
self.contents = []
9187
self.search.textChanged.connect(self.searchTable)
9288

93-
global TABLE
94-
TABLE = self.messages
89+
self.logger = logging.getLogger('PySysLogQt-TestLogger')
90+
self.logger.setLevel(logging.DEBUG)
91+
self.handler = None
9592

9693
self.thread = WorkerThread(self.run)
9794
self.change()
9895

96+
global WINDOW
97+
WINDOW = self
98+
99+
self.action_Open.triggered.connect(self.open)
100+
self.action_Save.triggered.connect(self.save)
101+
self.action_Clear.triggered.connect(self.clear)
102+
self.action_Change.triggered.connect(self.change)
103+
self.action_Quit.triggered.connect(self.close)
104+
105+
self.action_Emergency.triggered.connect(lambda: self.test("EMERG"))
106+
self.action_Alert.triggered.connect(lambda: self.test("ALERT"))
107+
self.action_Critical.triggered.connect(lambda: self.test("CRITICAL"))
108+
self.action_Error.triggered.connect(lambda: self.test("ERROR"))
109+
self.action_Warning.triggered.connect(lambda: self.test("WARNING"))
110+
self.action_Notice.triggered.connect(lambda: self.test("NOTICE"))
111+
self.action_Info.triggered.connect(lambda: self.test("INFO"))
112+
self.action_Debug.triggered.connect(lambda: self.test("DEBUG"))
113+
114+
self.action_Id.triggered.connect(lambda b: self.messages.setColumnHidden(0, not b))
115+
self.action_Date.triggered.connect(lambda b: self.messages.setColumnHidden(1, not b))
116+
self.action_Time.triggered.connect(lambda b: self.messages.setColumnHidden(2, not b))
117+
self.action_Host.triggered.connect(lambda b: self.messages.setColumnHidden(3, not b))
118+
self.action_Application.triggered.connect(lambda b: self.messages.setColumnHidden(4, not b))
119+
self.action_Facility.triggered.connect(lambda b: self.messages.setColumnHidden(5, not b))
120+
self.action_Level.triggered.connect(lambda b: self.messages.setColumnHidden(6, not b))
121+
self.action_Message.triggered.connect(lambda b: self.messages.setColumnHidden(7, not b))
122+
99123
def searchTable(self, text):
100124
# TODO: fancy search
101125
# - Wildcards
@@ -110,24 +134,49 @@ def searchTable(self, text):
110134
TABLE.showRow(r)
111135

112136
def change(self):
113-
self.end()
114-
115137
def accept():
138+
self.end()
139+
self.clear()
116140
self.host.setText(dialog.host.text())
117141
self.port.setValue(dialog.port.value())
118142
self.start()
119143

120144
def reject():
121-
self.deleteLater()
145+
if not self.running:
146+
self.deleteLater()
122147

123148
dialog = ServerDialog(self)
124149
dialog.accepted.connect(accept)
125150
dialog.rejected.connect(reject)
126151
dialog.exec_()
127152

153+
def clear(self):
154+
self.messages.setRowCount(0)
155+
self.contents.clear()
156+
157+
def getAddress(self):
158+
return self.host.text(), self.port.value()
159+
128160
def start(self):
161+
address = self.getAddress()
162+
if address[0].startswith(FILE_PREFIX):
163+
self.openFile(address[0][len(FILE_PREFIX):])
164+
return
129165
try:
130-
self.server = socketserver.UDPServer((self.host.text(), self.port.value()), SyslogUDPHandler)
166+
address = self.getAddress()
167+
self.server = socketserver.UDPServer(address, SyslogUDPHandler)
168+
self.handler = logging.handlers.SysLogHandler(address, facility=19)
169+
self.handler.priority_map = {
170+
"DEBUG" : "debug",
171+
"INFO" : "info",
172+
"NOTICE" : "notice",
173+
"WARNING" : "warning",
174+
"ERROR" : "error",
175+
"CRITICAL" : "critical",
176+
"ALERT" : "alert",
177+
"EMERG": "emerg"
178+
}
179+
self.logger.addHandler(self.handler)
131180
self.thread.start()
132181
return
133182
except PermissionError as e:
@@ -137,17 +186,97 @@ def start(self):
137186
QtWidgets.QMessageBox.warning(self, str(e), "The address '%s:%i' is already in use." %
138187
(self.host.text(), self.port.value()))
139188

140-
self.change()
189+
# self.change()
141190

142191
def run(self):
143-
# print("STARTED")
192+
self.running = True
144193
self.server.serve_forever(poll_interval=0.5)
145194

146195
def end(self):
147196
if self.server:
197+
self.logger.removeHandler(self.handler)
198+
self.handler = None
148199
self.server.shutdown()
149-
# print("ENDED")
200+
self.running = False
150201

151202
def closeEvent(self, QCloseEvent):
152203
self.end()
153-
super(MainWindow, self).closeEvent(QCloseEvent)
204+
super(MainWindow, self).closeEvent(QCloseEvent)
205+
206+
def add(self, data):
207+
self.contents.append(data)
208+
try:
209+
message = parse(data)
210+
prio = message.header.pri
211+
timestamp = message.header.timestamp
212+
date, time = timestamp.split("T")
213+
msg = message.message
214+
host = message.header.hostname
215+
application = message.header.appname
216+
except:
217+
match = re.search(r"^<\d+>", data)
218+
prio = int(match.group(0)[1:-1])
219+
date = datetime.date.today()
220+
time = datetime.datetime.now().time()
221+
host = application = "unknown"
222+
msg = data[match.span(0)[1]:-1]
223+
224+
f = prio // 8
225+
level = LEVELS[prio % 8]
226+
227+
row = self.messages.rowCount()
228+
self.messages.insertRow(row)
229+
self.messages.setItem(row, 0, QtWidgets.QTableWidgetItem(str(row)))
230+
self.messages.setItem(row, 1, QtWidgets.QTableWidgetItem(str(date)))
231+
self.messages.setItem(row, 2, QtWidgets.QTableWidgetItem(str(time)))
232+
self.messages.setItem(row, 3, QtWidgets.QTableWidgetItem(host))
233+
self.messages.setItem(row, 4, QtWidgets.QTableWidgetItem(application))
234+
self.messages.setItem(row, 5, QtWidgets.QTableWidgetItem(FACS[f]))
235+
self.messages.setItem(row, 6, QtWidgets.QTableWidgetItem(level))
236+
self.messages.setItem(row, 7, QtWidgets.QTableWidgetItem(msg))
237+
for i in range(8):
238+
if i not in [7]:
239+
self.messages.item(row, i).setTextAlignment(QtCore.Qt.AlignCenter)
240+
self.messages.item(row, i).setBackground(QtGui.QColor(*COLORS[level]))
241+
242+
def io(self):
243+
folder = os.getcwd()
244+
options = QtWidgets.QFileDialog.Options()
245+
options |= QtWidgets.QFileDialog.DontUseNativeDialog
246+
return options, folder
247+
248+
def openFile(self, filename):
249+
self.end()
250+
self.clear()
251+
self.host.setText("file://" + filename)
252+
with open(filename) as file:
253+
for data in file:
254+
data = data.rstrip()
255+
if len(data) > 0:
256+
self.add(data.rstrip())
257+
258+
# TODO: store timestamp somehow?
259+
def open(self):
260+
options, folder = self.io()
261+
fileName, _ = QtWidgets.QFileDialog \
262+
.getOpenFileName(self, "Open a File", folder, "All Files (*);;Log Files (*.log);;Text Files (*.txt)",
263+
options=options)
264+
if fileName != "":
265+
self.openFile(fileName)
266+
267+
def save(self):
268+
options, folder = self.io()
269+
fileName, t = QtWidgets.QFileDialog \
270+
.getSaveFileName(self, "Save a File", folder, "Log Files (*.log);;Text Files (*.txt);;All Files (*)",
271+
options=options)
272+
if fileName:
273+
if fileName.count(".") == 0:
274+
ext = ".log"
275+
if t.count(".") == 1:
276+
ext = t[t.index("."):-1]
277+
fileName += ext
278+
with open(fileName, 'w') as file:
279+
file.write("\n".join(self.contents))
280+
281+
def test(self, level):
282+
self.logger.log(logging._nameToLevel[level], "TESTING " + level)

0 commit comments

Comments
 (0)