mirror of
https://github.com/tstack/lnav.git
synced 2024-08-17 00:40:30 +03:00
parent
ef686b6932
commit
44d2e12403
1
NEWS
1
NEWS
@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
106
src/filter_observer.hh
Normal file
106
src/filter_observer.hh
Normal file
@ -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'
|
||||
|
161
src/lnav.cc
161
src/lnav.cc
@ -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);
|
||||
textfile_callback cb;
|
||||
|
||||
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;
|
||||
}
|
||||
new_data = tss->rescan_files(cb);
|
||||
force = force || cb.force;
|
||||
|
||||
try {
|
||||
bool new_text_data = (*iter)->rebuild_index();
|
||||
|
||||
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_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,8 +423,10 @@ void logfile::read_full_message(logfile::iterator ll,
|
||||
void logfile::set_logline_observer(logline_observer *llo)
|
||||
{
|
||||
this->lf_logline_observer = llo;
|
||||
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,6 +1267,7 @@ void save_session(void)
|
||||
cmd_array.gen((*filter_iter)->to_command());
|
||||
}
|
||||
|
||||
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;
|
||||
@ -1271,8 +1282,7 @@ void save_session(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
@ -145,8 +145,10 @@ public:
|
||||
}
|
||||
else {
|
||||
sf_format.clear();
|
||||
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,
|
||||
A_REVERSE | view_colors::ansi_color_pair(COLOR_CYAN, COLOR_BLACK)));
|
||||
|
@ -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 \
|
||||
|
3
test/logfile_plain.0
Normal file
3
test/logfile_plain.0
Normal file
@ -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…
Reference in New Issue
Block a user