44Date: 03/12/2020
55"""
66from PyQt5 import QtWidgets , QtCore , QtGui , uic
7- import socketserver , datetime , re
7+ import socketserver , datetime , re , os
8+ import logging .handlers
89
910from main .ServerDialog import ServerDialog
1011from 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://'
8578class 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