From ff44e37f7e9951cc2ea4f6f1ea41d38fc9a40748 Mon Sep 17 00:00:00 2001 From: Timothy Stack Date: Fri, 22 Jul 2022 23:37:25 -0700 Subject: [PATCH] [ui] make the cylon status mode actually cylon-like --- TESTS_ENVIRONMENT.in | 2 +- src/bottom_status_source.cc | 13 +- src/statusview_curses.cc | 30 ++-- test/expected/expected.am | 4 + ...51b55dff7332c5bee2c9b797c401c5614d574a.err | 0 ...51b55dff7332c5bee2c9b797c401c5614d574a.out | 159 ++++++++++++++++++ ...943c6be50d701a03e901f16493314c839af1ab.err | 0 ...943c6be50d701a03e901f16493314c839af1ab.out | 111 ++++++++++++ 8 files changed, 297 insertions(+), 22 deletions(-) create mode 100644 test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.err create mode 100644 test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out create mode 100644 test/expected/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.err create mode 100644 test/expected/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.out diff --git a/TESTS_ENVIRONMENT.in b/TESTS_ENVIRONMENT.in index 0022da4d..5c9b0d50 100644 --- a/TESTS_ENVIRONMENT.in +++ b/TESTS_ENVIRONMENT.in @@ -91,7 +91,7 @@ run_test() { run_cap_test() { LAST_CAP_TEST=("test: $@") - local full_cmd=$(echo "${LAST_CAP_TEST[@]}" | sed -e "s;${test_dir};{test_dir};g") + local full_cmd=$(echo "${LAST_CAP_TEST[@]}" | sed -e "s;${test_dir};{test_dir};g" -e "s;${top_srcdir};{top_srcdir};g") export test_hash=$(echo "${full_cmd}" | shasum | cut -f 1 -d ' ') echo "${full_cmd}" > ${test_file_base}_${test_hash}.cmd "$@" > ${test_file_base}_${test_hash}.out 2> ${test_file_base}_${test_hash}.err diff --git a/src/bottom_status_source.cc b/src/bottom_status_source.cc index 6765a630..1f87268b 100644 --- a/src/bottom_status_source.cc +++ b/src/bottom_status_source.cc @@ -43,7 +43,6 @@ bottom_status_source::bottom_status_source() this->bss_fields[BSF_SEARCH_TERM].set_min_width(10); this->bss_fields[BSF_SEARCH_TERM].set_share(1); this->bss_fields[BSF_LOADING].set_width(13); - this->bss_fields[BSF_LOADING].set_cylon(true); this->bss_fields[BSF_LOADING].right_justify(true); this->bss_fields[BSF_HELP].set_width(14); this->bss_fields[BSF_HELP].set_value("?:View Help"); @@ -198,18 +197,18 @@ bottom_status_source::update_loading(file_off_t off, file_size_t total) } } else if ((size_t) off == total) { static const std::vector DOTS = { - "", - ".", - "..", + " ", + ". ", + ".. ", "...", - "..", - ".", + ".. ", + ". ", }; this->bss_load_percent += 1; sf.set_cylon(true); sf.set_role(role_t::VCR_ACTIVE_STATUS2); - sf.set_value(" Working%s ", + sf.set_value(" Working%s ", DOTS[this->bss_load_percent % DOTS.size()].c_str()); } else { int pct = (int) (((double) off / (double) total) * 100.0); diff --git a/src/statusview_curses.cc b/src/statusview_curses.cc index eba4ca7a..be5e1fb5 100644 --- a/src/statusview_curses.cc +++ b/src/statusview_curses.cc @@ -40,7 +40,7 @@ void status_field::set_value(std::string value) { - string_attrs_t& sa = this->sf_value.get_attrs(); + auto& sa = this->sf_value.get_attrs(); sa.clear(); @@ -55,29 +55,31 @@ status_field::set_value(std::string value) void status_field::do_cylon() { - string_attrs_t& sa = this->sf_value.get_attrs(); + auto& sa = this->sf_value.get_attrs(); remove_string_attr(sa, &VC_STYLE); - struct line_range lr(this->sf_cylon_pos, this->sf_width); - view_colors& vc = view_colors::singleton(); + auto cycle_pos = (this->sf_cylon_pos % (4 + this->sf_width * 2)) - 2; + auto start = cycle_pos < this->sf_width + ? cycle_pos + : (this->sf_width - (cycle_pos - this->sf_width)); + auto stop = std::min(start + 3, this->sf_width); + struct line_range lr(std::max(start, 0L), stop); + log_debug("cylon %d:%d %d", lr.lr_start, lr.lr_end, this->sf_width); + auto& vc = view_colors::singleton(); - sa.emplace_back( - lr, - VC_STYLE.value( - vc.attrs_for_role(role_t::VCR_ACTIVE_STATUS) | A_REVERSE)); + sa.emplace_back(lr, + VC_STYLE.value(vc.attrs_for_role(role_t::VCR_ACTIVE_STATUS) + | A_REVERSE)); this->sf_cylon_pos += 1; - if (this->sf_cylon_pos > this->sf_width) { - this->sf_cylon_pos = 0; - } } void status_field::set_stitch_value(role_t left, role_t right) { - string_attrs_t& sa = this->sf_value.get_attrs(); + auto& sa = this->sf_value.get_attrs(); struct line_range lr(0, 1); this->sf_value.get_string() = "::"; @@ -92,7 +94,7 @@ void statusview_curses::do_update() { int top, attrs, field, field_count, left = 0, right; - view_colors& vc = view_colors::singleton(); + auto& vc = view_colors::singleton(); unsigned long width, height; if (!this->vc_visible) { @@ -218,7 +220,7 @@ statusview_curses::window_change() std::stable_sort(begin(resizable), end(resizable), [](auto l, auto r) { return r->get_share() < l->get_share(); }); - for (auto sf : resizable) { + for (auto* sf : resizable) { double divisor = total_shares / sf->get_share(); int available = remaining / divisor; int actual_width; diff --git a/test/expected/expected.am b/test/expected/expected.am index 6cc03012..d6f239d5 100644 --- a/test/expected/expected.am +++ b/test/expected/expected.am @@ -922,8 +922,12 @@ EXPECTED_FILES = \ $(srcdir)/%reldir%/test_sql_xml_func.sh_b036c73528a446cba46625767517cdac868aba72.out \ $(srcdir)/%reldir%/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.err \ $(srcdir)/%reldir%/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.out \ + $(srcdir)/%reldir%/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.err \ + $(srcdir)/%reldir%/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out \ $(srcdir)/%reldir%/test_text_file.sh_801414c6bb6d3f9225973eafa3c6dfa49cd2081d.err \ $(srcdir)/%reldir%/test_text_file.sh_801414c6bb6d3f9225973eafa3c6dfa49cd2081d.out \ + $(srcdir)/%reldir%/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.err \ + $(srcdir)/%reldir%/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.out \ $(srcdir)/%reldir%/test_text_file.sh_c21295f131c221861568bda5014b76ef99bdd11f.err \ $(srcdir)/%reldir%/test_text_file.sh_c21295f131c221861568bda5014b76ef99bdd11f.out \ $() diff --git a/test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.err b/test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out b/test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out new file mode 100644 index 00000000..59a0aa9c --- /dev/null +++ b/test/expected/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out @@ -0,0 +1,159 @@ +Build[1][2] Docs[3][4] Coverage Status[5][6] lnav[7][8] + + ▌[1] - https://github.com/tstack/lnav/workflows/ci-build/badge.svg + ▌[2] - https://github.com/tstack/lnav/actions?query=workflow%3Aci-build + ▌[3] - https://readthedocs.org/projects/lnav/badge/?version=latest&style=plastic + ▌[4] - https://docs.lnav.org + ▌[5] - https://coveralls.io/repos/github/tstack/lnav/badge.svg?branch=master + ▌[6] - https://coveralls.io/github/tstack/lnav?branch=master + ▌[7] - https://snapcraft.io//lnav/badge.svg + ▌[8] - https://snapcraft.io/lnav + +This is the source repository for lnav, visit https://lnav.org[1] for +a high level overview. + + ▌[1] - https://lnav.org + +LNAV – The Logfile Navigator + +The Log File Navigator, lnav for short, is an advanced log file viewer +for the small-scale. It is a terminal application that can understand +your log files and make it easy for you to find problems with little +to no setup. + +Screenshot + +The following screenshot shows a syslog file. Log lines are displayed +with highlights. Errors are red and warnings are yellow. + +Screenshot[1][2] + + ▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png + ▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png + +Features + + • Log messages from different files are collated together + into a single view + • Automatic detection of log format + • Automatic decompression of GZip and BZip2 files + • Filter log messages based on regular expressions + • Use SQL to analyze your logs + • And more... + +Installation + +Download a statically-linked binary for Linux/MacOS from the release +page[1] + + ▌[1] - https://github.com/tstack/lnav/releases/latest#release-artifacts + +Usage + +The only file installed is the executable,  lnav . You can execute it +with no arguments to view the default set of files: + + ▌$ lnav  + +You can view all the syslog messages by running: + + ▌$ lnav /var/log/messages*  + +Usage with  systemd-journald  + +On systems running  systemd-journald , you can use  lnav  as the +pager: + + ▌$ journalctl | lnav  + +or in follow mode: + + ▌$ journalctl -f | lnav  + +Since  journalctl 's default output format omits the year, if you are +viewing logs which span multiple years you will need to change the +output format to include the year, otherwise  lnav  gets confused: + + ▌$ journalctl -o short-iso | lnav  + +It is also possible to use  journalctl 's json output format and  lnav +will make use of additional fields such as PRIORITY and _SYSTEMD_UNIT: + + ▌$ journalctl -o json | lnav  + +In case some MESSAGE fields contain special characters such as ANSI +color codes which are considered as unprintable by journalctl, +specifying  journalctl 's  -a  option might be preferable in order to +output those messages still in a non binary representation: + + ▌$ journalctl -a -o json | lnav  + +If using systemd v236 or newer, the output fields can be limited to +the ones actually recognized by  lnav  for increased efficiency: + + ▌$ journalctl -o json --output-fields=MESSAGE,PRIORITY,_PID,SYSLOG_IDENTIFIER,_SYSTEMD_UNIT | lnav  + +If your system has been running for a long time, for increased +efficiency you may want to limit the number of log lines fed into  lnav +, e.g. via  journalctl 's  -n  or  --since=...  options. + +In case of a persistent journal, you may want to limit the number of +log lines fed into  lnav  via  journalctl 's  -b  option. + +Links + + • Main Site[1] + • Documentation[2] on Read the Docs + • Internal Architecture[3] + + ▌[1] - https://lnav.org + ▌[2] - https://docs.lnav.org + ▌[3] - file://{top_srcdir}/ARCHITECTURE.md + +Contributing + + • Become a Sponsor on GitHub[1] + + ▌[1] - https://github.com/sponsors/tstack + +Building From Source + +Prerequisites + +The following software packages are required to build lnav: + + • gcc/clang - A C++14-compatible compiler. + • libpcre - The Perl Compatible Regular Expression + (PCRE) library. + • sqlite - The SQLite database engine. Version 3.9.0 + or higher is required. + • ncurses - The ncurses text UI library. + • readline - The readline line editing library. + • zlib - The zlib compression library. + • bz2 - The bzip2 compression library. + • libcurl - The cURL library for downloading files + from URLs. Version 7.23.0 or higher is required. + • libarchive - The libarchive library for opening archive + files, like zip/tgz. + • wireshark - The 'tshark' program is used to interpret + pcap files. + +Build + +Lnav follows the usual GNU style for configuring and installing +software: + +Run  ./autogen.sh  if compiling from a cloned repository. + + ▌$ ./configure  + ▌$ make  + ▌$ sudo make install  + +See Also + +Angle-grinder[1] is a tool to slice and dice log files on the +command-line. If you're familiar with the SumoLogic query language, +you might find this tool more comfortable to work with. + + ▌[1] - https://github.com/rcoh/angle-grinder + diff --git a/test/expected/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.err b/test/expected/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.out b/test/expected/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.out new file mode 100644 index 00000000..a7ed7404 --- /dev/null +++ b/test/expected/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.out @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2018, 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. + */ + +#include "log_level.hh" + +#include <ctype.h> + +#include "config.h" + +const char* level_names[LEVEL__MAX + 1] = { + "unknown", + "trace", + "debug5", + "debug4", + "debug3", + "debug2", + "debug", + "info", + "stats", + "notice", + "warning", + "error", + "critical", + "fatal", + "invalid", + + nullptr, +}; + +log_level_t +abbrev2level(const char* levelstr, ssize_t len) +{ + if (len == 0 || levelstr[0] == '\0') { + return LEVEL_UNKNOWN; + } + + switch (toupper(levelstr[0])) { + case 'T': + return LEVEL_TRACE; + case 'D': + case 'V': + if (len > 1) { + switch (levelstr[len - 1]) { + case '2': + return LEVEL_DEBUG2; + case '3': + return LEVEL_DEBUG3; + case '4': + return LEVEL_DEBUG4; + case '5': + return LEVEL_DEBUG5; + } + } + return LEVEL_DEBUG; + case 'I': + if (len == 7 && toupper(levelstr[1]) == 'N' + && toupper(levelstr[2]) == 'V' && toupper(levelstr[3]) == 'A' + && toupper(levelstr[4]) == 'L' && toupper(levelstr[5]) == 'I' + && toupper(levelstr[6]) == 'D') + { + return LEVEL_INVALID; + } + return LEVEL_INFO; + case 'S': + return LEVEL_STATS; + case 'N': + return LEVEL_NOTICE; + case 'W': + return LEVEL_WARNING; + case 'E': + return LEVEL_ERROR; + case 'C': + return LEVEL_CRITICAL; + case 'F': + return LEVEL_FATAL; + default: + return LEVEL_UNKNOWN; + } +} + +int +levelcmp(const char* l1, ssize_t l1_len, const char* l2, ssize_t l2_len) +{ + return abbrev2level(l1, l1_len) - abbrev2level(l2, l2_len); +}