Improve session viewer. There were some off-by-one errors.
[fpdb-dooglus.git] / pyfpdb / GuiLogView.py
blobeb04c9f501415a1ee835f98c2bcf41b90b7995c8
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 #Copyright 2008-2011 Carl Gherardi
5 #This program is free software: you can redistribute it and/or modify
6 #it under the terms of the GNU Affero General Public License as published by
7 #the Free Software Foundation, version 3 of the License.
9 #This program is distributed in the hope that it will be useful,
10 #but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 #GNU General Public License for more details.
14 #You should have received a copy of the GNU Affero General Public License
15 #along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #In the "official" distribution you can find the license in agpl-3.0.txt.
18 import L10n
19 _ = L10n.get_translation()
21 import os
22 import Queue
24 import pygtk
25 pygtk.require('2.0')
26 import gtk
27 import gobject
28 import pango
30 import os
31 import traceback
32 import logging
33 # logging has been set up in fpdb.py or HUD_main.py, use their settings:
34 log = logging.getLogger("logview")
36 MAX_LINES = 100000 # max lines to display in window
37 EST_CHARS_PER_LINE = 150 # used to guesstimate number of lines in log file
38 LOGFILES = [ [ _('Fpdb Errors'), 'fpdb-errors.txt', False ] # label, filename, start value
39 , [ _('Fpdb Log'), 'fpdb-log.txt', True ]
40 , [ _('HUD Errors'), 'HUD-errors.txt', False ]
41 , [ _('HUD Log'), 'HUD-log.txt', False ]
44 class GuiLogView:
46 def __init__(self, config, mainwin, closeq):
47 self.config = config
48 self.main_window = mainwin
49 self.closeq = closeq
51 self.logfile = os.path.join(self.config.dir_log, LOGFILES[1][1])
52 self.dia = gtk.Dialog(title=_("Log Messages")
53 ,parent=None
54 ,flags=gtk.DIALOG_DESTROY_WITH_PARENT
55 ,buttons=(gtk.STOCK_CLOSE,gtk.RESPONSE_OK))
56 self.dia.set_modal(False)
58 self.vbox = self.dia.vbox
59 gtk.Widget.set_size_request(self.vbox, 700, 400);
61 self.liststore = gtk.ListStore(str, str, str, str, gobject.TYPE_BOOLEAN) # date, module, level, text
62 # this is how to add a filter:
64 # # Creation of the filter, from the model
65 # filter = self.liststore.filter_new()
66 # filter.set_visible_column(1)
68 # # The TreeView gets the filter as model
69 # self.listview = gtk.TreeView(filter)
70 self.listview = gtk.TreeView(model=self.liststore)
71 self.listview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_NONE)
72 self.listcols = []
74 scrolledwindow = gtk.ScrolledWindow()
75 scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
76 scrolledwindow.add(self.listview)
77 self.vbox.pack_start(scrolledwindow, expand=True, fill=True, padding=0)
79 hb = gtk.HBox(False, 0)
80 grp = None
81 for logf in LOGFILES:
82 rb = gtk.RadioButton(group=grp, label=logf[0], use_underline=True)
83 if grp is None: grp = rb
84 rb.set_active(logf[2])
85 rb.connect('clicked', self.__set_logfile, logf[0])
86 hb.pack_start(rb, False, False, 3)
87 refreshbutton = gtk.Button(_("Refresh"))
88 refreshbutton.connect("clicked", self.refresh, None)
89 hb.pack_start(refreshbutton, False, False, 3)
90 refreshbutton.show()
91 self.vbox.pack_start(hb, False, False, 0)
93 self.listview.show()
94 scrolledwindow.show()
95 self.vbox.show()
96 self.dia.set_focus(self.listview)
98 col = self.addColumn(_("Date/Time"), 0)
99 col = self.addColumn(_("Module"), 1)
100 col = self.addColumn(_("Level"), 2)
101 col = self.addColumn(_("Text"), 3)
103 self.loadLog()
104 self.vbox.show_all()
105 self.dia.show()
107 self.dia.connect('response', self.dialog_response_cb)
109 def __set_logfile(self, w, file):
110 #print "w is", w, "file is", file, "active is", w.get_active()
111 if w.get_active():
112 for logf in LOGFILES:
113 if logf[0] == file:
114 self.logfile = os.path.join(self.config.dir_log, logf[1])
115 self.refresh(w, file) # params are not used
117 def dialog_response_cb(self, dialog, response_id):
118 # this is called whether close button is pressed or window is closed
119 self.closeq.put(self.__class__)
120 dialog.destroy()
122 def get_dialog(self):
123 return self.dia
125 def addColumn(self, title, n):
126 col = gtk.TreeViewColumn(title)
127 self.listview.append_column(col)
128 cRender = gtk.CellRendererText()
129 cRender.set_property("wrap-mode", pango.WRAP_WORD_CHAR)
130 col.pack_start(cRender, True)
131 col.add_attribute(cRender, 'text', n)
132 col.set_max_width(1000)
133 col.set_spacing(0) # no effect
134 self.listcols.append(col)
135 col.set_clickable(True)
136 col.connect("clicked", self.sortCols, n)
137 return(col)
139 def loadLog(self):
141 self.liststore.clear()
142 # self.listcols = [] blanking listcols causes sortcols() to fail with index out of range
144 # guesstimate number of lines in file
145 if os.path.exists(self.logfile):
146 stat_info = os.stat(self.logfile)
147 lines = stat_info.st_size / EST_CHARS_PER_LINE
148 #print "logview: size =", stat_info.st_size, "lines =", lines
150 # set startline to line number to start display from
151 startline = 0
152 if lines > MAX_LINES:
153 # only display from startline if log file is large
154 startline = lines - MAX_LINES
156 l = 0
157 for line in open(self.logfile):
158 # example line in logfile format:
159 # 2009-12-02 15:23:21,716 - config DEBUG config logger initialised
160 l = l + 1
161 if l > startline:
162 # NOTE selecting a sort column and then switching to a log file
163 # with several thousand rows will send cpu 100% for a prolonged period.
164 # reason is that the append() method seems to sort every record as it goes, rather than
165 # pulling in the whole file and sorting at the end.
166 # one fix is to check if a column sort has been selected, reset to date/time asc
167 # append all the rows and then reselect the required sort order.
168 # Note: there is no easy method available to revert the list to an "unsorted" state.
169 # always defaulting to date/time asc doesn't work, because some rows do not have date/time info
170 # and would end up sorted out of context.
171 if len(line) > 49 and line[23:26] == ' - ' and line[34:39] == ' ':
172 iter = self.liststore.append( (line[0:23], line[26:32], line[39:46], line[48:].strip(), True) )
173 else:
174 iter = self.liststore.append( ('', '', '', line.strip(), True) )
176 def sortCols(self, col, n):
177 try:
178 if not col.get_sort_indicator() or col.get_sort_order() == gtk.SORT_ASCENDING:
179 col.set_sort_order(gtk.SORT_DESCENDING)
180 else:
181 col.set_sort_order(gtk.SORT_ASCENDING)
182 self.liststore.set_sort_column_id(n, col.get_sort_order())
183 #self.liststore.set_sort_func(n, self.sortnums, (n,grid))
184 for i in xrange(len(self.listcols)):
185 self.listcols[i].set_sort_indicator(False)
186 self.listcols[n].set_sort_indicator(True)
187 # use this listcols[col].set_sort_indicator(True)
188 # to turn indicator off for other cols
189 except:
190 err = traceback.extract_tb(sys.exc_info()[2])
191 print _("***sortCols error: ") + str(sys.exc_info()[1])
192 print "\n".join( [e[0]+':'+str(e[1])+" "+e[2] for e in err] )
194 def refresh(self, widget, data):
195 self.loadLog()
199 if __name__=="__main__":
201 config = Configuration.Config()
203 win = gtk.Window(gtk.WINDOW_TOPLEVEL)
204 win.set_title(_("Log Viewer"))
205 win.set_border_width(1)
206 win.set_default_size(600, 500)
207 win.set_resizable(True)
209 dia = gtk.Dialog(_("Log Viewer"),
210 win,
211 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
212 (gtk.STOCK_CLOSE, gtk.RESPONSE_OK))
213 dia.set_default_size(500, 500)
214 log = GuiLogView(config, win, dia.vbox)
215 response = dia.run()
216 if response == gtk.RESPONSE_ACCEPT:
217 pass
218 dia.destroy()