[text] add filtering support to the plain text view

Fixes #149 #148
pull/158/head
Timothy Stack 9 years ago
parent ef686b6932
commit 44d2e12403

@ -11,6 +11,7 @@ lnav v0.7.3:
* Added a 'relative-goto' command to move the current view relative
to its current position.
* Experimental support for linking with jemalloc.
* The plain text view now supports filtering.
Fixes:
* Autotools scripts overhaul.

@ -73,6 +73,7 @@ set(diag_STAT_SRCS
chunky_index.hh
concise_index.hh
column_namer.hh
filter_observer.hh
format-text-files.hh
grapher.hh
grep_highlighter.hh

@ -90,6 +90,7 @@ noinst_HEADERS = \
default-log-formats-json.hh \
db_sub_source.hh \
environ_vtab.hh \
filter_observer.hh \
format-text-files.hh \
grapher.hh \
grep_highlighter.hh \
@ -119,6 +120,7 @@ noinst_HEADERS = \
logfile_sub_source.hh \
pcrepp.hh \
piper_proc.hh \
pretty_printer.hh \
ptimec.hh \
readline_curses.hh \
readline_highlighters.hh \

@ -281,22 +281,24 @@ am__noinst_HEADERS_DIST = ansi_scrubber.hh auto_fd.hh auto_mem.hh \
auto_pid.hh bookmarks.hh bottom_status_source.hh byte_array.hh \
chunky_index.hh column_namer.hh concise_index.hh \
data_scanner.hh data_parser.hh default-log-formats-json.hh \
db_sub_source.hh environ_vtab.hh format-text-files.hh \
grapher.hh grep_highlighter.hh grep_proc.hh help.hh help.txt \
hist_source.hh init.sql init-sql.hh intern_string.hh \
json_op.hh json_ptr.hh k_merge_tree.h line_buffer.hh \
listview_curses.hh lnav.hh lnav_commands.hh lnav_config.hh \
lnav_log.hh lnav_util.hh log_accel.hh log_data_helper.hh \
log_data_table.hh log_format.hh log_format_loader.hh \
logfile.hh logfile_sub_source.hh pcrepp.hh piper_proc.hh \
ptimec.hh readline_curses.hh readline_highlighters.hh \
sequence_matcher.hh sequence_sink.hh session_data.hh \
shared_buffer.hh sql_util.hh sqlite-extension-func.h \
status_controllers.hh statusview_curses.hh strnatcmp.h \
strong_int.hh sysclip.hh termios_guard.hh term_extra.hh \
textfile_sub_source.hh textview_curses.hh time_T.hh \
top_status_source.hh view_curses.hh vt52_curses.hh \
log_vtab_impl.hh log_format_impls.cc xterm_mouse.hh yajlpp.hh \
db_sub_source.hh environ_vtab.hh filter_observer.hh \
format-text-files.hh grapher.hh grep_highlighter.hh \
grep_proc.hh help.hh help.txt hist_source.hh init.sql \
init-sql.hh intern_string.hh json_op.hh json_ptr.hh \
k_merge_tree.h line_buffer.hh listview_curses.hh lnav.hh \
lnav_commands.hh lnav_config.hh lnav_log.hh lnav_util.hh \
log_accel.hh log_data_helper.hh log_data_table.hh \
log_format.hh log_format_loader.hh logfile.hh \
logfile_sub_source.hh pcrepp.hh piper_proc.hh \
pretty_printer.hh ptimec.hh readline_curses.hh \
readline_highlighters.hh sequence_matcher.hh sequence_sink.hh \
session_data.hh shared_buffer.hh sql_util.hh \
sqlite-extension-func.h status_controllers.hh \
statusview_curses.hh strnatcmp.h strong_int.hh sysclip.hh \
termios_guard.hh term_extra.hh textfile_sub_source.hh \
textview_curses.hh time_T.hh top_status_source.hh \
view_curses.hh vt52_curses.hh log_vtab_impl.hh \
log_format_impls.cc xterm_mouse.hh yajlpp.hh \
spookyhash/SpookyV2.h yajl/api/yajl_common.h \
yajl/api/yajl_gen.h yajl/api/yajl_parse.h yajl/api/yajl_tree.h \
yajl/yajl_alloc.h yajl/yajl_buf.h yajl/yajl_bytestack.h \
@ -505,22 +507,24 @@ noinst_HEADERS = ansi_scrubber.hh auto_fd.hh auto_mem.hh auto_pid.hh \
bookmarks.hh bottom_status_source.hh byte_array.hh \
chunky_index.hh column_namer.hh concise_index.hh \
data_scanner.hh data_parser.hh default-log-formats-json.hh \
db_sub_source.hh environ_vtab.hh format-text-files.hh \
grapher.hh grep_highlighter.hh grep_proc.hh help.hh help.txt \
hist_source.hh init.sql init-sql.hh intern_string.hh \
json_op.hh json_ptr.hh k_merge_tree.h line_buffer.hh \
listview_curses.hh lnav.hh lnav_commands.hh lnav_config.hh \
lnav_log.hh lnav_util.hh log_accel.hh log_data_helper.hh \
log_data_table.hh log_format.hh log_format_loader.hh \
logfile.hh logfile_sub_source.hh pcrepp.hh piper_proc.hh \
ptimec.hh readline_curses.hh readline_highlighters.hh \
sequence_matcher.hh sequence_sink.hh session_data.hh \
shared_buffer.hh sql_util.hh sqlite-extension-func.h \
status_controllers.hh statusview_curses.hh strnatcmp.h \
strong_int.hh sysclip.hh termios_guard.hh term_extra.hh \
textfile_sub_source.hh textview_curses.hh time_T.hh \
top_status_source.hh view_curses.hh vt52_curses.hh \
log_vtab_impl.hh log_format_impls.cc xterm_mouse.hh yajlpp.hh \
db_sub_source.hh environ_vtab.hh filter_observer.hh \
format-text-files.hh grapher.hh grep_highlighter.hh \
grep_proc.hh help.hh help.txt hist_source.hh init.sql \
init-sql.hh intern_string.hh json_op.hh json_ptr.hh \
k_merge_tree.h line_buffer.hh listview_curses.hh lnav.hh \
lnav_commands.hh lnav_config.hh lnav_log.hh lnav_util.hh \
log_accel.hh log_data_helper.hh log_data_table.hh \
log_format.hh log_format_loader.hh logfile.hh \
logfile_sub_source.hh pcrepp.hh piper_proc.hh \
pretty_printer.hh ptimec.hh readline_curses.hh \
readline_highlighters.hh sequence_matcher.hh sequence_sink.hh \
session_data.hh shared_buffer.hh sql_util.hh \
sqlite-extension-func.h status_controllers.hh \
statusview_curses.hh strnatcmp.h strong_int.hh sysclip.hh \
termios_guard.hh term_extra.hh textfile_sub_source.hh \
textview_curses.hh time_T.hh top_status_source.hh \
view_curses.hh vt52_curses.hh log_vtab_impl.hh \
log_format_impls.cc xterm_mouse.hh yajlpp.hh \
spookyhash/SpookyV2.h $(am__append_1)
libdiag_a_SOURCES = ansi_scrubber.cc bookmarks.cc \
collation-functions.cc db_sub_source.cc environ_vtab.cc \

@ -227,17 +227,17 @@ public:
}
};
void update_filtered(logfile_sub_source &lss)
void update_filtered(text_sub_source *tss)
{
status_field &sf = this->bss_fields[BSF_FILTERED];
if (lss.get_filtered_count() == 0) {
if (tss == NULL || tss->get_filtered_count() == 0) {
sf.clear();
}
else {
ui_periodic_timer &timer = ui_periodic_timer::singleton();
if (lss.get_filtered_count() == this->bss_last_filtered_count) {
if (tss->get_filtered_count() == this->bss_last_filtered_count) {
if (timer.fade_diff(this->bss_filter_counter) == 0) {
this->bss_fields[BSF_FILTERED].set_role(
@ -247,10 +247,10 @@ public:
else {
this->bss_fields[BSF_FILTERED].set_role(
view_colors::VCR_ALERT_STATUS);
this->bss_last_filtered_count = lss.get_filtered_count();
this->bss_last_filtered_count = tss->get_filtered_count();
timer.start_fade(this->bss_filter_counter, 3);
}
sf.set_value("%'9d Not Shown", lss.get_filtered_count());
sf.set_value("%'9d Not Shown", tss->get_filtered_count());
}
};

@ -0,0 +1,106 @@
/**
* Copyright (c) 2015, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __filter_observer_hh
#define __filter_observer_hh
#include <sys/types.h>
#include "logfile.hh"
#include "textview_curses.hh"
class line_filter_observer : public logline_observer {
public:
line_filter_observer(filter_stack &fs, logfile *lf)
: lfo_filter_stack(fs), lfo_filter_state(lf) {
};
void logline_restart(const logfile &lf) {
for (filter_stack::iterator iter = this->lfo_filter_stack.begin();
iter != this->lfo_filter_stack.end();
++iter) {
(*iter)->revert_to_last(this->lfo_filter_state);
}
};
void logline_new_line(const logfile &lf, logfile::const_iterator ll, shared_buffer_ref &sbr) {
long offset = std::distance(lf.begin(), ll);
require(&lf == this->lfo_filter_state.tfs_logfile);
this->lfo_filter_state.resize(lf.size());
if (!this->lfo_filter_stack.empty()) {
if (lf.get_format() != NULL) {
lf.get_format()->get_subline(*ll, sbr);
}
for (filter_stack::iterator iter = this->lfo_filter_stack.begin();
iter != this->lfo_filter_stack.end();
++iter) {
if (offset >= this->lfo_filter_state.tfs_filter_count[(*iter)->get_index()]) {
(*iter)->add_line(this->lfo_filter_state, ll, sbr);
}
}
}
};
void logline_eof(const logfile &lf) {
for (filter_stack::iterator iter = this->lfo_filter_stack.begin();
iter != this->lfo_filter_stack.end();
++iter) {
(*iter)->end_of_message(this->lfo_filter_state);
}
};
bool excluded(uint32_t filter_in_mask, uint32_t filter_out_mask,
size_t offset) const {
bool filtered_in = (filter_in_mask == 0) || (
this->lfo_filter_state.tfs_mask[offset] & filter_in_mask) != 0;
bool filtered_out = (
this->lfo_filter_state.tfs_mask[offset] & filter_out_mask) != 0;
return !filtered_in || filtered_out;
};
size_t get_min_count(size_t max) const {
size_t retval = max;
for (filter_stack::iterator iter = this->lfo_filter_stack.begin();
iter != this->lfo_filter_stack.end();
++iter) {
retval = std::min(retval, this->lfo_filter_state.tfs_filter_count[(*iter)->get_index()]);
}
return retval;
};
filter_stack &lfo_filter_stack;
logfile_filter_state lfo_filter_state;
};
#endif

@ -386,7 +386,8 @@ COMMANDS
expression. This command can be used multiple
times to add more lines to the display. The number
of lines that are filtered out will be shown in the
bottom status bar as 'Not Shown'.
bottom status bar as 'Not Shown'. Note that filtering
only works in the log and plain text views.
filter-out <regex>
Do not display lines that match the given regular
@ -396,7 +397,8 @@ COMMANDS
priority and the filter-out will remove lines that
were matched by the 'filter-in'. The number
of lines that are filtered out will be shown in the
bottom status bar as 'Not Shown'.
bottom status bar as 'Not Shown'. Note that filtering
only works in the log and plain text views.
disable-filter <regex>
Disable an active 'filter-in' or 'filter-out'

@ -576,6 +576,28 @@ static void add_env_possibilities(int context)
}
}
static void add_filter_possibilities(textview_curses *tc)
{
readline_curses *rc = lnav_data.ld_rl_view;
text_sub_source *tss = tc->get_sub_source();
filter_stack &fs = tss->get_filters();
rc->clear_possibilities(LNM_COMMAND, "disabled-filter");
rc->clear_possibilities(LNM_COMMAND, "enabled-filter");
for (filter_stack::iterator iter = fs.begin();
iter != fs.end();
++iter) {
text_filter *tf = *iter;
if (tf->is_enabled()) {
rc->add_possibility(LNM_COMMAND, "enabled-filter", tf->get_id());
}
else {
rc->add_possibility(LNM_COMMAND, "disabled-filter", tf->get_id());
}
}
}
bool setup_logline_table()
{
// Hidden columns don't show up in the table_info pragma.
@ -751,6 +773,41 @@ static void rebuild_hist(size_t old_count, bool force)
hist_view.set_top(hs.row_for_value(old_time));
}
class textfile_callback {
public:
textfile_callback() : force(false), front_file(NULL), front_top(-1) { };
void closed_file(logfile *lf) {
lnav_data.ld_file_names.erase(make_pair(lf->get_filename(), lf->get_fd()));
lnav_data.ld_files.remove(lf);
delete lf;
};
void promote_file(logfile *lf) {
if (lnav_data.ld_log_source.insert_file(lf)) {
force = true;
}
else {
this->closed_file(lf);
}
};
void scanned_file(logfile *lf) {
if (!lnav_data.ld_files_to_front.empty() &&
lnav_data.ld_files_to_front.front().first ==
lf->get_filename()) {
front_file = lf;
front_top = lnav_data.ld_files_to_front.front().second;
lnav_data.ld_files_to_front.pop_front();
}
};
bool force;
logfile *front_file;
int front_top;
};
void rebuild_indexes(bool force)
{
logfile_sub_source &lss = lnav_data.ld_log_source;
@ -772,63 +829,22 @@ void rebuild_indexes(bool force)
{
textfile_sub_source * tss = &lnav_data.ld_text_source;
std::list<logfile *>::iterator iter;
logfile *front_file = NULL;
int front_top = -1;
bool new_data = false;
bool new_data;
old_bottom = text_view.get_top_for_last_row();
scroll_down = (text_view.get_top() >= old_bottom &&
!(lnav_data.ld_flags & LNF_HEADLESS));
for (iter = tss->tss_files.begin();
iter != tss->tss_files.end(); ) {
logfile *lf = (*iter);
if (!lf->exists() || lf->is_closed()) {
lnav_data.ld_file_names.erase(make_pair(lf->get_filename(), lf->get_fd()));
iter = tss->tss_files.erase(iter);
lnav_data.ld_files.remove(lf);
delete lf;
continue;
}
textfile_callback cb;
try {
bool new_text_data = (*iter)->rebuild_index();
new_data = tss->rescan_files(cb);
force = force || cb.force;
if ((*iter)->get_format() != NULL) {
if (lnav_data.ld_log_source.insert_file(lf)) {
iter = tss->tss_files.erase(iter);
force = true;
}
else {
++iter;
}
}
else {
new_data = new_data || new_text_data;
if (!lnav_data.ld_files_to_front.empty() &&
lnav_data.ld_files_to_front.front().first ==
(*iter)->get_filename()) {
front_file = *iter;
front_top = lnav_data.ld_files_to_front.front().second;
lnav_data.ld_files_to_front.pop_front();
}
++iter;
}
}
catch (const line_buffer::error &e) {
// TODO: log that we dropped this file.
iter = tss->tss_files.erase(iter);
}
}
if (front_file != NULL) {
if (cb.front_file != NULL) {
ensure_view(&text_view);
if (tss->current_file() != front_file) {
tss->tss_files.remove(front_file);
tss->tss_files.push_front(front_file);
if (tss->current_file() != cb.front_file) {
tss->to_front(cb.front_file);
redo_search(LNV_TEXT);
text_view.reload_data();
old_bottom = vis_line_t(-1);
@ -836,11 +852,11 @@ void rebuild_indexes(bool force)
new_data = false;
}
if (front_top < 0) {
front_top += text_view.get_inner_height();
if (cb.front_top < 0) {
cb.front_top += text_view.get_inner_height();
}
if (front_top < text_view.get_inner_height()) {
text_view.set_top(vis_line_t(front_top));
if (cb.front_top < text_view.get_inner_height()) {
text_view.set_top(vis_line_t(cb.front_top));
scroll_down = false;
}
}
@ -873,7 +889,7 @@ void rebuild_indexes(bool force)
if (!lf->exists() || lf->is_closed()) {
lnav_data.ld_file_names.erase(make_pair(lf->get_filename(), lf->get_fd()));
lnav_data.ld_text_source.tss_files.remove(lf);
lnav_data.ld_text_source.remove(lf);
lnav_data.ld_log_source.remove_file(lf);
file_iter = lnav_data.ld_files.erase(file_iter);
force = true;
@ -933,8 +949,9 @@ void rebuild_indexes(bool force)
}
}
lnav_data.ld_bottom_source.update_filtered(lss);
lnav_data.ld_scroll_broadcaster.invoke(lnav_data.ld_view_stack.top());
textview_curses *tc = lnav_data.ld_view_stack.top();
lnav_data.ld_bottom_source.update_filtered(tc->get_sub_source());
lnav_data.ld_scroll_broadcaster.invoke(tc);
}
class plain_text_source
@ -1177,9 +1194,13 @@ static void open_pretty_view(void)
bool toggle_view(textview_curses *toggle_tc)
{
textview_curses *tc = lnav_data.ld_view_stack.top();
textview_curses *tc = lnav_data.ld_view_stack.empty() ? NULL : lnav_data.ld_view_stack.top();
bool retval = false;
require(toggle_tc != NULL);
require(toggle_tc >= &lnav_data.ld_views[0]);
require(toggle_tc < &lnav_data.ld_views[LNV__MAX]);
if (tc == toggle_tc) {
lnav_data.ld_view_stack.pop();
}
@ -1222,15 +1243,18 @@ void redo_search(lnav_view_t view_index)
* Ensure that the view is on the top of the view stack.
*
* @param expected_tc The text view that should be on top.
* @return True if the view was already on the top of the stack.
*/
void ensure_view(textview_curses *expected_tc)
bool ensure_view(textview_curses *expected_tc)
{
textview_curses *tc = lnav_data.ld_view_stack.top();
textview_curses *tc = lnav_data.ld_view_stack.empty() ? NULL : lnav_data.ld_view_stack.top();
bool retval = true;
if (tc != expected_tc) {
toggle_view(expected_tc);
retval = false;
}
return retval;
}
static vis_line_t next_cluster(
@ -1509,9 +1533,8 @@ static void handle_paging_key(int ch)
else if (tc == &lnav_data.ld_views[LNV_TEXT]) {
textfile_sub_source &tss = lnav_data.ld_text_source;
if (!tss.tss_files.empty()) {
tss.tss_files.push_front(tss.tss_files.back());
tss.tss_files.pop_back();
if (!tss.empty()) {
tss.rotate_right();
redo_search(LNV_TEXT);
}
}
@ -1524,9 +1547,8 @@ static void handle_paging_key(int ch)
else if (tc == &lnav_data.ld_views[LNV_TEXT]) {
textfile_sub_source &tss = lnav_data.ld_text_source;
if (!tss.tss_files.empty()) {
tss.tss_files.push_back(tss.tss_files.front());
tss.tss_files.pop_front();
if (!tss.empty()) {
tss.rotate_left();
redo_search(LNV_TEXT);
}
}
@ -1956,15 +1978,13 @@ static void handle_paging_key(int ch)
"line-time",
buffer);
}
}
add_view_text_possibilities(LNM_COMMAND, "filter", tc);
lnav_data.ld_rl_view->
add_possibility(LNM_COMMAND, "filter",
lnav_data.ld_last_search[tc - lnav_data.ld_views]);
lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "levelname", logline::level_names);
add_view_text_possibilities(LNM_COMMAND, "filter", tc);
lnav_data.ld_rl_view->
add_possibility(LNM_COMMAND, "filter",
lnav_data.ld_last_search[tc - lnav_data.ld_views]);
add_filter_possibilities(tc);
lnav_data.ld_mode = LNM_COMMAND;
lnav_data.ld_rl_view->focus(LNM_COMMAND, ":");
lnav_data.ld_bottom_source.set_prompt("Enter an lnav command: "
@ -3133,7 +3153,7 @@ static void watch_logfile(string filename, int fd, bool required)
log_info("loading new file: %s", filename.c_str());
lf->set_logfile_observer(&obs);
lnav_data.ld_files.push_back(lf);
lnav_data.ld_text_source.tss_files.push_back(lf);
lnav_data.ld_text_source.push_back(lf);
break;
}
}
@ -3565,6 +3585,9 @@ static void looper(void)
lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "viewname", lnav_view_strings);
lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "levelname", logline::level_names);
(void)signal(SIGINT, sigint);
(void)signal(SIGTERM, sigint);
(void)signal(SIGWINCH, sigwinch);

@ -222,7 +222,7 @@ extern struct _lnav_data lnav_data;
void rebuild_indexes(bool force);
void ensure_view(textview_curses *expected_tc);
bool ensure_view(textview_curses *expected_tc);
bool toggle_view(textview_curses *toggle_tc);
std::string execute_command(std::string cmdline);

@ -871,12 +871,6 @@ static string com_enable_filter(string cmdline, vector<string> &args)
fs.set_filter_enabled(lf, true);
tss->text_filters_changed();
tc->reload_data();
if (lnav_data.ld_rl_view != NULL) {
lnav_data.ld_rl_view->rem_possibility(
LNM_COMMAND, "disabled-filter", args[1]);
lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "enabled-filter", args[1]);
}
retval = "info: filter enabled";
}
}
@ -909,12 +903,6 @@ static string com_disable_filter(string cmdline, vector<string> &args)
fs.set_filter_enabled(lf, false);
tss->text_filters_changed();
tc->reload_data();
if (lnav_data.ld_rl_view != NULL) {
lnav_data.ld_rl_view->rem_possibility(
LNM_COMMAND, "enabled-filter", args[1]);
lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "disabled-filter", args[1]);
}
retval = "info: filter disabled";
}
}
@ -1205,14 +1193,14 @@ static string com_close(string cmdline, vector<string> &args)
if (tc == &lnav_data.ld_views[LNV_TEXT]) {
textfile_sub_source &tss = lnav_data.ld_text_source;
if (tss.tss_files.empty()) {
if (tss.empty()) {
retval = "error: no text files are opened";
}
else {
fn = tss.current_file()->get_filename();
tss.current_file()->close();
if (tss.tss_files.size() == 1) {
if (tss.size() == 1) {
lnav_data.ld_view_stack.pop();
}
}

@ -423,7 +423,9 @@ void logfile::read_full_message(logfile::iterator ll,
void logfile::set_logline_observer(logline_observer *llo)
{
this->lf_logline_observer = llo;
this->reobserve_from(this->begin());
if (llo != NULL) {
this->reobserve_from(this->begin());
}
}
void logfile::reobserve_from(iterator iter)

@ -304,6 +304,10 @@ public:
void set_logline_observer(logline_observer *llo);
logline_observer *get_logline_observer() const {
return this->lf_logline_observer;
};
bool operator<(const logfile &rhs) const
{
bool retval;

@ -47,77 +47,10 @@
#include "bookmarks.hh"
#include "chunky_index.hh"
#include "textview_curses.hh"
#include "filter_observer.hh"
STRONG_INT_TYPE(uint64_t, content_line);
class line_filter_observer : public logline_observer {
public:
line_filter_observer(filter_stack &fs, logfile *lf)
: lfo_filter_stack(fs), lfo_filter_state(lf) {
};
void logline_restart(const logfile &lf) {
for (filter_stack::iterator iter = this->lfo_filter_stack.begin();
iter != this->lfo_filter_stack.end();
++iter) {
(*iter)->revert_to_last(this->lfo_filter_state);
}
};
void logline_new_line(const logfile &lf, logfile::const_iterator ll, shared_buffer_ref &sbr) {
long offset = std::distance(lf.begin(), ll);
require(&lf == this->lfo_filter_state.tfs_logfile);
this->lfo_filter_state.resize(lf.size());
if (!this->lfo_filter_stack.empty()) {
if (lf.get_format() != NULL) {
lf.get_format()->get_subline(*ll, sbr);
}
for (filter_stack::iterator iter = this->lfo_filter_stack.begin();
iter != this->lfo_filter_stack.end();
++iter) {
if (offset >= this->lfo_filter_state.tfs_filter_count[(*iter)->get_index()]) {
(*iter)->add_line(this->lfo_filter_state, ll, sbr);
}
}
}
};
void logline_eof(const logfile &lf) {
for (filter_stack::iterator iter = this->lfo_filter_stack.begin();
iter != this->lfo_filter_stack.end();
++iter) {
(*iter)->end_of_message(this->lfo_filter_state);
}
};
bool excluded(uint32_t filter_in_mask, uint32_t filter_out_mask,
size_t offset) const {
bool filtered_in = (filter_in_mask == 0) || (
this->lfo_filter_state.tfs_mask[offset] & filter_in_mask) != 0;
bool filtered_out = (
this->lfo_filter_state.tfs_mask[offset] & filter_out_mask) != 0;
return !filtered_in || filtered_out;
};
size_t get_min_count(size_t max) const {
size_t retval = max;
for (filter_stack::iterator iter = this->lfo_filter_stack.begin();
iter != this->lfo_filter_stack.end();
++iter) {
retval = std::min(retval, this->lfo_filter_state.tfs_filter_count[(*iter)->get_index()]);
}
return retval;
};
filter_stack &lfo_filter_stack;
logfile_filter_state lfo_filter_state;
};
/**
* Delegate class that merges the contents of multiple log files into a single
* source of data for a text view.

@ -753,20 +753,30 @@ static int read_word_wrap(yajlpp_parse_context *ypc, int value)
static int read_commands(yajlpp_parse_context *ypc, const unsigned char *str, size_t len)
{
std::string cmdline = std::string((const char *)str, len);
const char ** view_name;
int view_index;
view_name = find(lnav_view_strings,
lnav_view_strings + LNV__MAX,
ypc->get_path_fragment(-3));
view_index = view_name - lnav_view_strings;
bool active = ensure_view(&lnav_data.ld_views[view_index]);
execute_command(cmdline);
if (!active) {
lnav_data.ld_view_stack.pop();
}
return 1;
}
static struct json_path_handler view_info_handlers[] = {
json_path_handler("/save-time", read_save_time),
json_path_handler("/time-offset", read_time_offset),
json_path_handler("/files#", read_files),
json_path_handler("/views/([^/]+)/top_line", read_top_line),
json_path_handler("/views/([^/]+)/search", read_last_search),
json_path_handler("/views/([^/]+)/word_wrap", read_word_wrap),
json_path_handler("/commands#", read_commands),
json_path_handler("^/save-time", read_save_time),
json_path_handler("^/time-offset", read_time_offset),
json_path_handler("^/files#", read_files),
json_path_handler("^/views/([^/]+)/top_line", read_top_line),
json_path_handler("^/views/([^/]+)/search", read_last_search),
json_path_handler("^/views/([^/]+)/word_wrap", read_word_wrap),
json_path_handler("^/views/([^/]+)/commands#", read_commands),
json_path_handler()
};
@ -1257,22 +1267,22 @@ void save_session(void)
cmd_array.gen((*filter_iter)->to_command());
}
textview_curses::highlight_map_t &hmap =
lnav_data.ld_views[LNV_LOG].get_highlights();
textview_curses::highlight_map_t::iterator hl_iter;
for (hl_iter = hmap.begin();
hl_iter != hmap.end();
++hl_iter) {
if (hl_iter->first[0] == '$') {
continue;
if (lpc == LNV_LOG) {
textview_curses::highlight_map_t &hmap =
lnav_data.ld_views[LNV_LOG].get_highlights();
textview_curses::highlight_map_t::iterator hl_iter;
for (hl_iter = hmap.begin();
hl_iter != hmap.end();
++hl_iter) {
if (hl_iter->first[0] == '$') {
continue;
}
cmd_array.gen("highlight " + hl_iter->first);
}
cmd_array.gen("highlight " + hl_iter->first);
}
}
}
root_map.gen("commands");
}
yajl_gen_clear(handle);
@ -1281,6 +1291,8 @@ void save_session(void)
fclose(file.release());
rename(view_file_tmp_name.c_str(), view_file_name.c_str());
log_debug("Saved session: %s", view_file_name.c_str());
}
}

@ -34,6 +34,7 @@
#include "logfile.hh"
#include "textview_curses.hh"
#include "filter_observer.hh"
class textfile_sub_source : public text_sub_source {
public:
@ -41,12 +42,22 @@ public:
textfile_sub_source() { };
bool empty() const {
return this->tss_files.empty();
}
size_t size() const {
return this->tss_files.size();
}
size_t text_line_count()
{
size_t retval = 0;
if (!this->tss_files.empty()) {
retval = this->current_file()->size();
logfile *lf = this->current_file();
line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer();
retval = lfo->lfo_filter_state.tfs_index.size();
}
return retval;
@ -58,8 +69,9 @@ public:
bool raw = false)
{
if (!this->tss_files.empty()) {
this->current_file()->
read_line(this->current_file()->begin() + line, value_out);
logfile *lf = this->current_file();
line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer();
lf->read_line(lf->begin() + lfo->lfo_filter_state.tfs_index[line], value_out);
}
else {
value_out.clear();
@ -85,7 +97,9 @@ public:
size_t retval = 0;
if (!this->tss_files.empty()) {
retval = this->current_file()->line_length(this->current_file()->begin() + line);
logfile *lf = this->current_file();
line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer();
retval = lf->line_length(lf->begin() + lfo->lfo_filter_state.tfs_index[line]);
}
return retval;
@ -106,8 +120,135 @@ public:
}
return this->tss_files.front()->get_filename();
};
void to_front(logfile *lf) {
this->tss_files.remove(lf);
this->tss_files.push_front(lf);
};
void rotate_left() {
this->tss_files.push_back(this->tss_files.front());
this->tss_files.pop_front();
};
void rotate_right() {
this->tss_files.push_front(this->tss_files.back());
this->tss_files.pop_back();
};
void remove(logfile *lf) {
file_iterator iter = std::find(this->tss_files.begin(),
this->tss_files.end(), lf);
if (iter != this->tss_files.end()) {
detach_observer(lf);
this->tss_files.erase(iter);
}
};
void push_back(logfile *lf) {
line_filter_observer *lfo = new line_filter_observer(
this->get_filters(), lf);
lf->set_logline_observer(lfo);
this->tss_files.push_back(lf);
};
template<class T> bool rescan_files(T &callback) {
file_iterator iter;
bool retval = false;
for (iter = this->tss_files.begin(); iter != this->tss_files.end();) {
logfile *lf = (*iter);
if (!lf->exists() || lf->is_closed()) {
iter = this->tss_files.erase(iter);
this->detach_observer(lf);
callback.closed_file(lf);
continue;
}
try {
uint32_t old_size = lf->size();
bool new_text_data = lf->rebuild_index();
if (lf->get_format() != NULL) {
iter = this->tss_files.erase(iter);
this->detach_observer(lf);
callback.promote_file(lf);
continue;
}
retval = retval || new_text_data;
callback.scanned_file(lf);
uint32_t filter_in_mask, filter_out_mask;
this->get_filters().get_enabled_mask(filter_in_mask, filter_out_mask);
line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer();
for (uint32_t lpc = old_size; lpc < lf->size(); lpc++) {
if (lfo->excluded(filter_in_mask, filter_out_mask, lpc)) {
continue;
}
lfo->lfo_filter_state.tfs_index.push_back(lpc);
}
}
catch (const line_buffer::error &e) {
iter = this->tss_files.erase(iter);
lf->close();
this->detach_observer(lf);
callback.closed_file(lf);
continue;
}
++iter;
}
return retval;
};
virtual void text_filters_changed() {
logfile *lf = this->current_file();
if (lf == NULL) {
return;
}
line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer();
uint32_t filter_in_mask, filter_out_mask;
lf->reobserve_from(lf->begin() + lfo->get_min_count(lf->size()));
this->get_filters().get_enabled_mask(filter_in_mask, filter_out_mask);
lfo->lfo_filter_state.tfs_index.clear();
for (uint32_t lpc = 0; lpc < lf->size(); lpc++) {
if (lfo->excluded(filter_in_mask, filter_out_mask, lpc)) {
continue;
}
lfo->lfo_filter_state.tfs_index.push_back(lpc);
}
};
int get_filtered_count() const {
logfile *lf = this->current_file();
int retval = 0;
if (lf != NULL) {
line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer();
retval = lf->size() - lfo->lfo_filter_state.tfs_index.size();
}
return retval;
}
private:
void detach_observer(logfile *lf) {
line_filter_observer *lfo = (line_filter_observer *) lf->get_logline_observer();
lf->set_logline_observer(NULL);
if (lfo != NULL) {
delete lfo;
}
};
std::list<logfile *> tss_files;
};
#endif

@ -74,6 +74,7 @@ public:
logfile *tfs_logfile;
size_t tfs_filter_count[MAX_FILTERS];
std::vector<uint32_t> tfs_mask;
std::vector<uint32_t> tfs_index;
};
class text_filter {
@ -345,6 +346,10 @@ public:
};
virtual int get_filtered_count() const {
return 0;
};
private:
filter_stack tss_filters;
};
@ -356,7 +361,7 @@ public:
virtual void text_overlay(textview_curses &tc) { };
virtual bool text_handle_mouse(textview_curses &tc, mouse_event &me) {
return false;
return false;
};
};

@ -145,7 +145,9 @@ public:
}
else {
sf_format.clear();
sf_filename.set_value(lc->get_data_source()->listview_source_name(*lc));
if (lc->get_data_source() != NULL) {
sf_filename.set_value(lc->get_data_source()->listview_source_name(*lc));
}
}
sf_format.get_value().get_attrs().push_back(
string_attr(lr, &view_curses::VC_STYLE,

@ -226,6 +226,7 @@ dist_noinst_DATA = \
logfile_json.json \
logfile_multiline.0 \
logfile_openam.0 \
logfile_plain.0 \
logfile_strace_log.0 \
logfile_syslog.0 \
logfile_syslog.1 \

@ -821,6 +821,7 @@ dist_noinst_DATA = \
logfile_json.json \
logfile_multiline.0 \
logfile_openam.0 \
logfile_plain.0 \
logfile_strace_log.0 \
logfile_syslog.0 \
logfile_syslog.1 \

@ -0,0 +1,3 @@
Hello, World!
How are you?
Goodbye, World!

@ -142,6 +142,24 @@ Dec 6 13:01:34 ubu-mac dnsmasq-dhcp[1840]: read /var/lib/libvirt/dnsmasq/defaul
EOF
run_test ${lnav_test} -n \
-c ":switch-to-view text" \
-c ":filter-in World" \
${test_dir}/logfile_plain.0
check_output "plain text filter-in is not working" <<EOF
Hello, World!
Goodbye, World!
EOF
run_test ${lnav_test} -n \
-c ":switch-to-view text" \
-c ":filter-out World" \
${test_dir}/logfile_plain.0
check_output "plain text filter-out is not working" <<EOF
How are you?
EOF
run_test ${lnav_test} -n \
-c ":switch-to-view help" \
${test_dir}/logfile_access_log.0

Loading…
Cancel
Save