I have fixed bugs in inserting new events, and also added real time rendering option in source dialog, and will be grateful for feedback.
diff -u -X ex or_src/audit-viewer.glade oav/src/audit-viewer.glade --- or_src/audit-viewer.glade 2012-09-22 04:04:40.000000000 +0400 +++ oav/src/audit-viewer.glade 2015-03-30 12:13:06.810004999 +0300 @@ -2664,6 +2664,16 @@ </widget> </child> <child> + <widget class="GtkCheckButton" id="source_with_updating"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Render updates in real time</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + </child> + <child> <widget class="GtkVBox" id="vbox25"> <property name="visible">True</property> <child> diff -u -X ex or_src/event_source.py oav/src/event_source.py --- or_src/event_source.py 2009-06-09 23:10:41.000000000 +0400 +++ oav/src/event_source.py 2015-03-30 12:41:11.028004422 +0300 @@ -19,7 +19,7 @@ import datetime import os.path import re - +import os import auparse __all__ = ('ClientEventSource', 'ClientWithRotatedEventSource', @@ -95,7 +95,7 @@ '''A source of audit events, reading from an auparse parser.''' def read_events(self, filters, wanted_fields, want_other_fields, - keep_raw_records): + keep_raw_records, direction = None, tab = None): '''Return a sequence of audit events read from parser. Use filters to select events. Store wanted_fields in event.fields, the @@ -265,6 +265,107 @@ def _create_parser(self): return auparse.AuParser(auparse.AUSOURCE_BUFFER, self.str) +class UpdatableEventSourceReader(): + + '''A separate reader of audit events, created for each tab of main window. + To be used with UpdatableEventSource. + + ''' + + def __init__(self, event_source, tab): + self.event_source = event_source + self.tab = tab + self.up_file = open(event_source.base) + self.bottom_file = open(event_source.base) + self.up_file.seek(0, 2) + self.up_pos = self.down_pos = self.up_file.tell() + self.bytes = self.event_source.avg_line_length * self.event_source.chunk_size + + def read_down(self): + ''' read older lines from file starting from self.down_pos''' + if os.path.exists(self.bottom_file.name): + if os.stat(self.bottom_file.name).st_ino != os.fstat(self.bottom_file.fileno()).st_ino: + self.bottom_file.close() + self.tab.want_read_down = False + return '' + if self.down_pos <= self.bytes: + self.bottom_file.seek(0) + lines = self.bottom_file.read(self.down_pos) + try: + files = os.listdir(os.path.dirname(self.event_source.base)) + files = sorted(files, key = lambda x : os.stat(x).st_mtime) + filename = self.bottom_file.name + self.bottom_file.close() + self.bottom_file = open(files[files.index(filename) + 1]) + self.bottom_file.seek(0, 2) + self.down_pos = self.bottom_file.tell() + except: + self.tab.want_read_down = False + else: + self.bottom_file.seek(self.down_pos - self.bytes) + lines = self.bottom_file.read(self.bytes) + lines = lines[lines.find('\n') + 1:] + self.down_pos -= len(lines) + return lines + + def read_up(self): + '''try to read new lines if there are any after the previous reads''' + if os.path.exists(self.up_file.name): + if os.stat(self.up_file.name).st_ino != os.fstat(self.up_file.fileno()).st_ino: + self.up_file.close() + self.up_file = open(self.event_source.base) + self.up_pos = 0 + else: + self.up_file.close() + self.tab.want_read_up = False + return '' + self.up_file.seek(0, 2) + file_end_pos = self.up_file.tell() + if self.up_pos != file_end_pos: + self.up_file.seek(self.up_pos) + if file_end_pos - self.up_pos > self.bytes*5: + lines = self.up_file.read(self.bytes) + file_end_pos = self.up_file.tell() + else: + lines = self.up_file.read() + if lines.endswith('\n'): + self.up_pos = file_end_pos + else: + lines = lines[:lines.rfind('\n') + 1] + self.up_pos += len(lines) + return lines + return '' + + +class UpdatableEventSource(_ParserEventSource): + + def __init__(self, base_file, chunk_size = 50, avg_line_length = 74): + self.chunk_size = chunk_size + self.avg_line_length = avg_line_length + self.readers = {} + self.base = base_file + self.current_lines = '' + + def add_tab(self, tab): + self.readers[tab] = UpdatableEventSourceReader(self, tab) + + def remove_tab(self, tab): + del self.readers[tab] + + def read_events(self, filters, wanted_fields, want_other_fields, + keep_raw_records, direction, tab): + if direction == 'up': + self.current_lines = self.readers[tab].read_up() + else: + self.current_lines = self.readers[tab].read_down() + return _ParserEventSource.read_events(self, filters, wanted_fields, want_other_fields, + keep_raw_records) + + + def _create_parser(self): + return auparse.AuParser(auparse.AUSOURCE_BUFFER, self.current_lines) + + def check_expression(expr): '''Check expr. diff -u -X ex or_src/list_tab.py oav/src/list_tab.py --- or_src/list_tab.py 2009-12-19 10:00:00.000000000 +0300 +++ oav/src/list_tab.py 2015-03-30 13:26:44.579005427 +0300 @@ -28,6 +28,7 @@ from search_entry import SearchEntry from tab import Tab import util +import event_source __all__ = ('ListTab') @@ -130,7 +131,7 @@ date_column_label = '__audit_viewer_date' __list_number = 1 - def __init__(self, filters, main_window, will_refresh = False): + def __init__(self, filters, main_window, will_refresh = True): Tab.__init__(self, filters, main_window, 'list_vbox') # date_column_label == event date, None == all other columns @@ -157,6 +158,13 @@ util.connect_and_run(self.selection, 'changed', self.__selection_changed) + if isinstance(self.main_window.event_source, event_source.UpdatableEventSource): + self.main_window.event_source.add_tab(self) + self.want_read_up = True + self.want_read_down = True + self.__refresh_dont_read_events = False + self.refresh(False, 'up') + return self.__refresh_dont_read_events = will_refresh self.refresh() self.__refresh_dont_read_events = False @@ -191,20 +199,25 @@ % (util.filename_to_utf8(filename), e.strerror)) - def refresh(self): - event_sequence = self.__refresh_get_event_sequence() + def refresh(self, updating = False, direction = None): + if isinstance(self.main_window.event_source, event_source.UpdatableEventSource) and not updating: + self.want_read_down = True + self.want_read_up = True + self.main_window.event_source.readers[self] = event_source.UpdatableEventSourceReader(self.main_window.event_source, self) + event_sequence = self.__refresh_get_event_sequence(direction, self) if event_sequence is None: return - - if self.filters: - t = _(', ').join(f.ui_text() for f in self.filters) - else: - t = _('None') - self.list_filter_label.set_text(t) - self.__refresh_update_tree_view() + if not updating: + if self.filters: + t = _(', ').join(f.ui_text() for f in self.filters) + else: + t = _('None') + self.list_filter_label.set_text(t) + self.__refresh_update_tree_view() events = self.__refresh_collect_events(event_sequence) - self.__refresh_update_store(events) + self.__refresh_update_store(events, updating) + def report_on_view(self): self.main_window.new_report_tab(self.filters) @@ -462,7 +475,7 @@ for record in event.records for (key, value) in record.fields])) - def __refresh_get_event_sequence(self): + def __refresh_get_event_sequence(self, direction = None, tab = None): '''Return an event sequence (as if from self.main_window.read_events()). Return None on error. @@ -480,7 +493,7 @@ elif title is not self.date_column_label: wanted_fields.add(title) return self.main_window.read_events(self.filters, wanted_fields, - want_other_fields, True) + want_other_fields, True, direction, tab) def __refresh_update_tree_view(self): '''Update self.list_tree_view for current configuration. @@ -560,7 +573,32 @@ events.sort(key = lambda event: event[0], reverse = self.sort_reverse) return events - def __refresh_update_store(self, events): + def __insert_row(self, event): + ''' insert new row with event into self.store preserving sort order''' + it = None + if not isinstance(self.main_window.event_source, event_source.UpdatableEventSource): + it = self.store.append(event[1]) + return it + if self.sort_by and self.sort_by not in self.columns: + self.sort_by = None + if self.sort_by: + sort_field = self.__field_columns.index(self.sort_by) + 1 + for i in range(len(self.store)): + if event[0] > self.store[i][sort_field] and self.sort_reverse or \ + event[0] <= self.store[i][sort_field] and not self.sort_reverse: + it = self.store.insert(i, event[1]) + break + else: + for i in range(len(self.store)): + if event[1][0].id.sec > self.store[i][0].id.sec and self.sort_reverse or \ + event[1][0].id.sec <= self.store[i][0].id.sec and not self.sort_reverse: + it = self.store.insert(i, event[1]) + break + if not it: + it = self.store.append(event[1]) + return it + + def __refresh_update_store(self, events, updating = False): '''Update self.store and related data. events is the result of self.__refresh_collect_events(). @@ -571,11 +609,12 @@ key = pos.event_key l = positions_for_event_key.setdefault(key, []) l.append(pos) - self.store.clear() + if not updating: + self.store.clear() if (self.text_filter is None and len(positions_for_event_key) == 0): # Fast path for event in events: - self.store.append(event[1]) + self.__insert_row(event) else: event_to_it = {} text_filter = self.text_filter @@ -604,7 +643,8 @@ or (self.__other_column_event_text(event_tuple[0]). find(self.text_filter) == -1))): continue - it = self.store.append(event_tuple) + + it = self.__insert_row(event) event_id = event_tuple[0].id key = (event_id.serial, event_id.sec, event_id.milli) if key in positions_for_event_key: diff -u -X ex or_src/main.py oav/src/main.py --- or_src/main.py 2008-06-26 00:17:59.000000000 +0400 +++ oav/src/main.py 2015-03-25 15:31:29.663932132 +0300 @@ -29,6 +29,7 @@ from main_window import MainWindow import settings import util +import event_source _ = gettext.gettext @@ -48,12 +49,20 @@ help = _('do not attempt to start the privileged backend ' 'for reading system audit logs')) parser.set_defaults(unprivileged = False) + parser.add_option('-p', '--updatable', action = 'store_true', + dest = 'updatable', + help = _('read new lines from log ')) + parser.set_defaults(updatable = False) + parser.add_option('-s', '--source', type = 'string', + dest = 'source', + help = _('path to log file ')) (options, args) = parser.parse_args() gnome.init(settings.gettext_domain, settings.version) gtk.glade.bindtextdomain(settings.gettext_domain, settings.localedir) gtk.glade.textdomain(settings.gettext_domain) + ev_source = None if options.unprivileged: cl = None else: @@ -66,7 +75,9 @@ sys.exit(1) except client.ClientNotAvailableError: cl = None + if options.updatable: + ev_source = event_source.UpdatableEventSource(options.source) - w = MainWindow(cl) + w = MainWindow(cl, ev_source) if w.setup_initial_window(args): gtk.main() diff -u -X ex or_src/main_window.py oav/src/main_window.py --- or_src/main_window.py 2008-08-19 14:38:16.000000000 +0400 +++ oav/src/main_window.py 2015-03-30 13:21:25.607005136 +0300 @@ -135,6 +135,8 @@ ''' try: + if isinstance(self.event_source, event_source.UpdatableEventSource): + self.updater = gobject.idle_add(self.update_tabs) if isinstance(self.event_source, event_source.EmptyEventSource): self.__event_error_report_only_one_push() if self.client is not None: @@ -246,7 +248,7 @@ return (filename, extension) def read_events(self, filters, wanted_fields, want_other_fields, - keep_raw_records): + keep_raw_records, direction = None, tab = None): '''Read audit events. Return a sequence of events, or None on error (without throwing @@ -262,7 +264,7 @@ try: return self.event_source.read_events(filters, wanted_fields, want_other_fields, - keep_raw_records) + keep_raw_records, direction, tab) except IOError, e: if (self.__event_error_report_only_one_depth == 0 or not self.__event_error_reported): @@ -381,16 +383,30 @@ '''End a region in which only one error message should be reported.''' self.__event_error_report_only_one_depth -= 1 - def __refresh_all_tabs(self): + def update_tabs(self): + self.__refresh_all_tabs(True) + return True + + def __refresh_all_tabs(self, updating = False): '''Refresh all tabs, taking care to report errors only once.''' self.__event_error_report_only_one_push() try: - for page_num in xrange(self.main_notebook.get_n_pages()): - tab = self.__tab_objects[self.main_notebook - .get_nth_page(page_num)] - tab.refresh() + if not updating: + for page_num in xrange(self.main_notebook.get_n_pages()): + tab = self.__tab_objects[self.main_notebook + .get_nth_page(page_num)] + tab.refresh() + else: + for page_num in xrange(self.main_notebook.get_n_pages()): + tab = self.__tab_objects[self.main_notebook + .get_nth_page(page_num)] + if tab.want_read_up: + tab.refresh(True, 'up') + if tab.want_read_down: + tab.refresh(True, 'down') finally: self.__event_error_report_only_one_pop() + return True def __menu_new_list_activate(self, *_): self.new_list_tab([]) Only in oav/src/: patch diff -u -X ex or_src/source_dialog.py oav/src/source_dialog.py --- or_src/source_dialog.py 2009-06-09 22:35:26.000000000 +0400 +++ oav/src/source_dialog.py 2015-03-30 12:46:15.983005127 +0300 @@ -39,7 +39,8 @@ 'source_path', 'source_path_browse', 'source_path_label', 'source_type_file', 'source_type_log', - 'source_with_rotated') + 'source_with_rotated', + 'source_with_updating') def __init__(self, parent, client): DialogBase.__init__(self, 'source_dialog', parent) @@ -58,6 +59,8 @@ self.__source_type_log_toggled) util.connect_and_run(self.source_type_file, 'toggled', self.__source_type_file_toggled) + util.connect_and_run(self.source_with_updating, 'toggled', + self.__source_with_updating_toggled) self._setup_browse_button(self.source_path_browse, self.source_path, _('Audit Log File'), gtk.FILE_CHOOSER_ACTION_OPEN) @@ -102,13 +105,20 @@ self.source_with_rotated.set_active(False) self.source_type_file.set_active(True) self.source_path.set_text(source.path) + elif isinstance(source, event_source.FileWithRotatedEventSource): + self.source_with_rotated.set_active(True) + self.source_type_file.set_active(True) + self.source_path.set_text(source.base) else: assert isinstance(source, - event_source.FileWithRotatedEventSource), \ + event_source.UpdatableEventSource), \ 'Unexpected event source' self.source_with_rotated.set_active(True) + self.source_with_rotated.set_sensitive(False) + self.source_type_log.set_sensitive(False) self.source_type_file.set_active(True) self.source_path.set_text(source.base) + self.source_with_updating.set_active(True) def save(self, main_window): '''Modify main_window to reflect dialog state.''' @@ -124,7 +134,10 @@ source = event_source.ClientEventSource(self.client, name) else: path = self.source_path.get_text() - if self.source_with_rotated.get_active(): + if self.source_with_updating.get_active(): + source = event_source.UpdatableEventSource(path) + main_window.updater = gobject.idle_add(main_window.update_tabs) + elif self.source_with_rotated.get_active(): source = event_source.FileWithRotatedEventSource(path) else: source = event_source.FileEventSource(path) @@ -175,11 +188,21 @@ if it is not None: self.source_log.set_active_iter(it) + def __source_with_updating_toggled(self, *_): + is_set = self.source_with_updating.get_active() + self.source_with_rotated.set_sensitive(not is_set) + self.source_type_log.set_sensitive(not is_set) + if is_set: + self.source_type_file.set_active(True) + self.source_type_log.set_active(False) + self.source_with_rotated.set_active(True) + def __source_type_file_toggled(self, *_): util.set_sensitive_all(self.source_type_file.get_active(), self.source_path_label, self.source_path, self.source_path_browse) + def __window_destroy(self, *_): self.emit('destroy') return False diff -u -X ex or_src/event_source.py oav/src/event_source.py --- or_src/event_source.py 2009-06-09 23:10:41.000000000 +0400 +++ oav/src/event_source.py 2015-03-30 12:41:11.028004422 +0300 @@ -19,7 +19,7 @@ import datetime import os.path import re - +import os import auparse __all__ = ('ClientEventSource', 'ClientWithRotatedEventSource', @@ -95,7 +95,7 @@ '''A source of audit events, reading from an auparse parser.''' def read_events(self, filters, wanted_fields, want_other_fields, - keep_raw_records): + keep_raw_records, direction = None, tab = None): '''Return a sequence of audit events read from parser. Use filters to select events. Store wanted_fields in event.fields, the @@ -265,6 +265,107 @@ def _create_parser(self): return auparse.AuParser(auparse.AUSOURCE_BUFFER, self.str) +class UpdatableEventSourceReader(): + + '''A separate reader of audit events, created for each tab of main window. + To be used with UpdatableEventSource. + + ''' + + def __init__(self, event_source, tab): + self.event_source = event_source + self.tab = tab + self.up_file = open(event_source.base) + self.bottom_file = open(event_source.base) + self.up_file.seek(0, 2) + self.up_pos = self.down_pos = self.up_file.tell() + self.bytes = self.event_source.avg_line_length * self.event_source.chunk_size + + def read_down(self): + ''' read older lines from file starting from self.down_pos''' + if os.path.exists(self.bottom_file.name): + if os.stat(self.bottom_file.name).st_ino != os.fstat(self.bottom_file.fileno()).st_ino: + self.bottom_file.close() + self.tab.want_read_down = False + return '' + if self.down_pos <= self.bytes: + self.bottom_file.seek(0) + lines = self.bottom_file.read(self.down_pos) + try: + files = os.listdir(os.path.dirname(self.event_source.base)) + files = sorted(files, key = lambda x : os.stat(x).st_mtime) + filename = self.bottom_file.name + self.bottom_file.close() + self.bottom_file = open(files[files.index(filename) + 1]) + self.bottom_file.seek(0, 2) + self.down_pos = self.bottom_file.tell() + except: + self.tab.want_read_down = False + else: + self.bottom_file.seek(self.down_pos - self.bytes) + lines = self.bottom_file.read(self.bytes) + lines = lines[lines.find('\n') + 1:] + self.down_pos -= len(lines) + return lines + + def read_up(self): + '''try to read new lines if there are any after the previous reads''' + if os.path.exists(self.up_file.name): + if os.stat(self.up_file.name).st_ino != os.fstat(self.up_file.fileno()).st_ino: + self.up_file.close() + self.up_file = open(self.event_source.base) + self.up_pos = 0 + else: + self.up_file.close() + self.tab.want_read_up = False + return '' + self.up_file.seek(0, 2) + file_end_pos = self.up_file.tell() + if self.up_pos != file_end_pos: + self.up_file.seek(self.up_pos) + if file_end_pos - self.up_pos > self.bytes*5: + lines = self.up_file.read(self.bytes) + file_end_pos = self.up_file.tell() + else: + lines = self.up_file.read() + if lines.endswith('\n'): + self.up_pos = file_end_pos + else: + lines = lines[:lines.rfind('\n') + 1] + self.up_pos += len(lines) + return lines + return '' + + +class UpdatableEventSource(_ParserEventSource): + + def __init__(self, base_file, chunk_size = 50, avg_line_length = 74): + self.chunk_size = chunk_size + self.avg_line_length = avg_line_length + self.readers = {} + self.base = base_file + self.current_lines = '' + + def add_tab(self, tab): + self.readers[tab] = UpdatableEventSourceReader(self, tab) + + def remove_tab(self, tab): + del self.readers[tab] + + def read_events(self, filters, wanted_fields, want_other_fields, + keep_raw_records, direction, tab): + if direction == 'up': + self.current_lines = self.readers[tab].read_up() + else: + self.current_lines = self.readers[tab].read_down() + return _ParserEventSource.read_events(self, filters, wanted_fields, want_other_fields, + keep_raw_records) + + + def _create_parser(self): + return auparse.AuParser(auparse.AUSOURCE_BUFFER, self.current_lines) + + def check_expression(expr): '''Check expr. diff -u -X ex or_src/list_tab.py oav/src/list_tab.py --- or_src/list_tab.py 2009-12-19 10:00:00.000000000 +0300 +++ oav/src/list_tab.py 2015-03-30 13:26:44.579005427 +0300 @@ -28,6 +28,7 @@ from search_entry import SearchEntry from tab import Tab import util +import event_source __all__ = ('ListTab') @@ -130,7 +131,7 @@ date_column_label = '__audit_viewer_date' __list_number = 1 - def __init__(self, filters, main_window, will_refresh = False): + def __init__(self, filters, main_window, will_refresh = True): Tab.__init__(self, filters, main_window, 'list_vbox') # date_column_label == event date, None == all other columns @@ -157,6 +158,13 @@ util.connect_and_run(self.selection, 'changed', self.__selection_changed) + if isinstance(self.main_window.event_source, event_source.UpdatableEventSource): + self.main_window.event_source.add_tab(self) + self.want_read_up = True + self.want_read_down = True + self.__refresh_dont_read_events = False + self.refresh(False, 'up') + return self.__refresh_dont_read_events = will_refresh self.refresh() self.__refresh_dont_read_events = False @@ -191,20 +199,25 @@ % (util.filename_to_utf8(filename), e.strerror)) - def refresh(self): - event_sequence = self.__refresh_get_event_sequence() + def refresh(self, updating = False, direction = None): + if isinstance(self.main_window.event_source, event_source.UpdatableEventSource) and not updating: + self.want_read_down = True + self.want_read_up = True + self.main_window.event_source.readers[self] = event_source.UpdatableEventSourceReader(self.main_window.event_source, self) + event_sequence = self.__refresh_get_event_sequence(direction, self) if event_sequence is None: return - - if self.filters: - t = _(', ').join(f.ui_text() for f in self.filters) - else: - t = _('None') - self.list_filter_label.set_text(t) - self.__refresh_update_tree_view() + if not updating: + if self.filters: + t = _(', ').join(f.ui_text() for f in self.filters) + else: + t = _('None') + self.list_filter_label.set_text(t) + self.__refresh_update_tree_view() events = self.__refresh_collect_events(event_sequence) - self.__refresh_update_store(events) + self.__refresh_update_store(events, updating) + def report_on_view(self): self.main_window.new_report_tab(self.filters) @@ -462,7 +475,7 @@ for record in event.records for (key, value) in record.fields])) - def __refresh_get_event_sequence(self): + def __refresh_get_event_sequence(self, direction = None, tab = None): '''Return an event sequence (as if from self.main_window.read_events()). Return None on error. @@ -480,7 +493,7 @@ elif title is not self.date_column_label: wanted_fields.add(title) return self.main_window.read_events(self.filters, wanted_fields, - want_other_fields, True) + want_other_fields, True, direction, tab) def __refresh_update_tree_view(self): '''Update self.list_tree_view for current configuration. @@ -560,7 +573,32 @@ events.sort(key = lambda event: event[0], reverse = self.sort_reverse) return events - def __refresh_update_store(self, events): + def __insert_row(self, event): + ''' insert new row with event into self.store preserving sort order''' + it = None + if not isinstance(self.main_window.event_source, event_source.UpdatableEventSource): + it = self.store.append(event[1]) + return it + if self.sort_by and self.sort_by not in self.columns: + self.sort_by = None + if self.sort_by: + sort_field = self.__field_columns.index(self.sort_by) + 1 + for i in range(len(self.store)): + if event[0] > self.store[i][sort_field] and self.sort_reverse or \ + event[0] <= self.store[i][sort_field] and not self.sort_reverse: + it = self.store.insert(i, event[1]) + break + else: + for i in range(len(self.store)): + if event[1][0].id.sec > self.store[i][0].id.sec and self.sort_reverse or \ + event[1][0].id.sec <= self.store[i][0].id.sec and not self.sort_reverse: + it = self.store.insert(i, event[1]) + break + if not it: + it = self.store.append(event[1]) + return it + + def __refresh_update_store(self, events, updating = False): '''Update self.store and related data. events is the result of self.__refresh_collect_events(). @@ -571,11 +609,12 @@ key = pos.event_key l = positions_for_event_key.setdefault(key, []) l.append(pos) - self.store.clear() + if not updating: + self.store.clear() if (self.text_filter is None and len(positions_for_event_key) == 0): # Fast path for event in events: - self.store.append(event[1]) + self.__insert_row(event) else: event_to_it = {} text_filter = self.text_filter @@ -604,7 +643,8 @@ or (self.__other_column_event_text(event_tuple[0]). find(self.text_filter) == -1))): continue - it = self.store.append(event_tuple) + + it = self.__insert_row(event) event_id = event_tuple[0].id key = (event_id.serial, event_id.sec, event_id.milli) if key in positions_for_event_key: diff -u -X ex or_src/main.py oav/src/main.py --- or_src/main.py 2008-06-26 00:17:59.000000000 +0400 +++ oav/src/main.py 2015-03-25 15:31:29.663932132 +0300 @@ -29,6 +29,7 @@ from main_window import MainWindow import settings import util +import event_source _ = gettext.gettext @@ -48,12 +49,20 @@ help = _('do not attempt to start the privileged backend ' 'for reading system audit logs')) parser.set_defaults(unprivileged = False) + parser.add_option('-p', '--updatable', action = 'store_true', + dest = 'updatable', + help = _('read new lines from log ')) + parser.set_defaults(updatable = False) + parser.add_option('-s', '--source', type = 'string', + dest = 'source', + help = _('path to log file ')) (options, args) = parser.parse_args() gnome.init(settings.gettext_domain, settings.version) gtk.glade.bindtextdomain(settings.gettext_domain, settings.localedir) gtk.glade.textdomain(settings.gettext_domain) + ev_source = None if options.unprivileged: cl = None else: @@ -66,7 +75,9 @@ sys.exit(1) except client.ClientNotAvailableError: cl = None + if options.updatable: + ev_source = event_source.UpdatableEventSource(options.source) - w = MainWindow(cl) + w = MainWindow(cl, ev_source) if w.setup_initial_window(args): gtk.main() diff -u -X ex or_src/main_window.py oav/src/main_window.py --- or_src/main_window.py 2008-08-19 14:38:16.000000000 +0400 +++ oav/src/main_window.py 2015-03-30 13:21:25.607005136 +0300 @@ -135,6 +135,8 @@ ''' try: + if isinstance(self.event_source, event_source.UpdatableEventSource): + self.updater = gobject.idle_add(self.update_tabs) if isinstance(self.event_source, event_source.EmptyEventSource): self.__event_error_report_only_one_push() if self.client is not None: @@ -246,7 +248,7 @@ return (filename, extension) def read_events(self, filters, wanted_fields, want_other_fields, - keep_raw_records): + keep_raw_records, direction = None, tab = None): '''Read audit events. Return a sequence of events, or None on error (without throwing @@ -262,7 +264,7 @@ try: return self.event_source.read_events(filters, wanted_fields, want_other_fields, - keep_raw_records) + keep_raw_records, direction, tab) except IOError, e: if (self.__event_error_report_only_one_depth == 0 or not self.__event_error_reported): @@ -381,16 +383,30 @@ '''End a region in which only one error message should be reported.''' self.__event_error_report_only_one_depth -= 1 - def __refresh_all_tabs(self): + def update_tabs(self): + self.__refresh_all_tabs(True) + return True + + def __refresh_all_tabs(self, updating = False): '''Refresh all tabs, taking care to report errors only once.''' self.__event_error_report_only_one_push() try: - for page_num in xrange(self.main_notebook.get_n_pages()): - tab = self.__tab_objects[self.main_notebook - .get_nth_page(page_num)] - tab.refresh() + if not updating: + for page_num in xrange(self.main_notebook.get_n_pages()): + tab = self.__tab_objects[self.main_notebook + .get_nth_page(page_num)] + tab.refresh() + else: + for page_num in xrange(self.main_notebook.get_n_pages()): + tab = self.__tab_objects[self.main_notebook + .get_nth_page(page_num)] + if tab.want_read_up: + tab.refresh(True, 'up') + if tab.want_read_down: + tab.refresh(True, 'down') finally: self.__event_error_report_only_one_pop() + return True def __menu_new_list_activate(self, *_): self.new_list_tab([]) ----- Исходное сообщение ----- От: "mitr" <m...@redhat.com> Кому: "Xeniya Muratova" <murat...@itsirius.su> Копия: "linux-audit" <linux-audit@redhat.com> Отправленные: Среда, 4 Март 2015 г 20:50:53 Тема: Re: log rendering in real time in audit-viewer Hello, > Hello Miloslav, and all the guys! > > We use audit-viewer for events monitoring. > Unfortunately, if some log is rather big it takes to much time for > audit-viewer to parse and render it. > Besides, we need to render log updates in real time, i.e. when a new line > appears in a log, it should appear in a viewer too. > Can you suggest the better way to extend audit-viewer to meet these > requirements? Well, write the code? Something like inotify could be useful. There isn’t any hidden switch to enable these features, if that is what you are asking. As for performance, I may have missed something but I think I have squeezed as much as can be done with Python; improving performance further would very likely require a C extension. (audit-viewer is a PyGtk2 application, and at I’m afraid I don’t currently have plans to port it to GTK+3/gobject-introspection or do any other non-trivial work on the project, at least in the near term.) Mirek -- Linux-audit mailing list Linux-audit@redhat.com https://www.redhat.com/mailman/listinfo/linux-audit