diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a5afcd1f..d3e92e3e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -269,6 +269,12 @@ add_library( ) target_include_directories(cppfmt PUBLIC fmtlib) +add_library( + datepp STATIC + third-party/date/src/tz.cpp +) +target_include_directories(datepp PUBLIC third-party/date/include) + add_library( cppscnlib STATIC third-party/scnlib/src/reader_float.cpp @@ -389,6 +395,7 @@ add_library( file_collection.cc file_converter_manager.cc file_format.cc + file_options.cc file_vtab.cc files_sub_source.cc filter_observer.cc @@ -503,6 +510,7 @@ add_library( file_collection.hh file_converter_manager.hh file_format.hh + file_options.hh files_sub_source.hh filter_observer.hh filter_status_source.hh @@ -665,6 +673,7 @@ target_include_directories(diag PUBLIC . fmtlib ${CMAKE_CURRENT_BINARY_DIR} target_link_libraries( diag base + datepp lnavdt lnavfileio pcrepp @@ -676,7 +685,8 @@ target_link_libraries( cppfmt base64 spookyhash - ${lnav_LIBS}) + ${lnav_LIBS} +) target_compile_definitions(diag PRIVATE SQLITE_OMIT_LOAD_EXTENSION) check_library_exists(util openpty "" HAVE_LIBUTIL) diff --git a/src/Makefile.am b/src/Makefile.am index f57a1040..d859f441 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -210,6 +210,7 @@ noinst_HEADERS = \ file_collection.hh \ file_converter_manager.hh \ file_format.hh \ + file_options.hh \ file_vtab.cfg.hh \ files_sub_source.hh \ filter_observer.hh \ @@ -407,6 +408,7 @@ libdiag_a_SOURCES = \ file_collection.cc \ file_converter_manager.cc \ file_format.cc \ + file_options.cc \ files_sub_source.cc \ filter_observer.cc \ filter_status_source.cc \ diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 06b785cd..b9a21660 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -73,7 +73,7 @@ add_library( target_include_directories(base PUBLIC . .. ../third-party ${CMAKE_CURRENT_BINARY_DIR}/..) -target_link_libraries(base cppfmt cppscnlib pcrepp ncurses::libcurses pthread lnavdt) +target_link_libraries(base cppfmt cppscnlib pcrepp ncurses::libcurses pthread lnavdt datepp) add_executable( test_base diff --git a/src/base/Makefile.am b/src/base/Makefile.am index cb1be8d3..6ee19154 100644 --- a/src/base/Makefile.am +++ b/src/base/Makefile.am @@ -7,6 +7,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/ \ -I$(top_srcdir)/src/third-party \ -I$(top_srcdir)/src/fmtlib \ + -I$(top_srcdir)/src/third-party/date/include \ -I$(top_srcdir)/src/third-party/scnlib/include \ $(LIBARCHIVE_CFLAGS) \ $(READLINE_CFLAGS) \ diff --git a/src/base/date_time_scanner.cc b/src/base/date_time_scanner.cc index 844b6b48..b0bfde08 100644 --- a/src/base/date_time_scanner.cc +++ b/src/base/date_time_scanner.cc @@ -157,11 +157,24 @@ date_time_scanner::scan(const char* time_dest, if (convert_local && (this->dts_local_time || tm_out->et_flags & ETF_EPOCH_TIME - || (tm_out->et_flags & ETF_ZONE_SET + || ((tm_out->et_flags & ETF_ZONE_SET + || this->dts_default_zone != nullptr) && this->dts_zoned_to_local))) { time_t gmt = tm_out->to_timeval().tv_sec; + if (!(tm_out->et_flags & ETF_ZONE_SET) + && !(tm_out->et_flags & ETF_EPOCH_TIME) + && this->dts_default_zone != nullptr) + { + date::local_seconds stime; + stime += std::chrono::seconds{gmt}; + auto ztime + = date::make_zoned(this->dts_default_zone, stime); + gmt = std::chrono::duration_cast( + ztime.get_sys_time().time_since_epoch()) + .count(); + } this->to_localtime(gmt, *tm_out); } const auto& last_tm = this->dts_last_tm.et_tm; @@ -301,8 +314,6 @@ date_time_scanner::to_localtime(time_t t, exttm& tm_out) if (t < this->dts_local_offset_valid || t >= this->dts_local_offset_expiry) { - time_t new_gmt; - localtime_r(&t, &tm_out.et_tm); // Clear the gmtoff set by localtime_r() otherwise tm2sec() will // convert the time back again. @@ -311,8 +322,7 @@ date_time_scanner::to_localtime(time_t t, exttm& tm_out) tm_out.et_tm.tm_zone = nullptr; #endif tm_out.et_tm.tm_isdst = 0; - - new_gmt = tm2sec(&tm_out.et_tm); + auto new_gmt = tm2sec(&tm_out.et_tm); this->dts_local_offset_cache = new_gmt - t; this->dts_local_offset_valid = t; this->dts_local_offset_expiry = t + (EXPIRE_TIME - 1); diff --git a/src/base/date_time_scanner.hh b/src/base/date_time_scanner.hh index b5fe1998..cf8a0daf 100644 --- a/src/base/date_time_scanner.hh +++ b/src/base/date_time_scanner.hh @@ -37,6 +37,7 @@ #include +#include "date/tz.h" #include "time_util.hh" /** @@ -105,6 +106,7 @@ struct date_time_scanner { time_t dts_local_offset_cache{0}; time_t dts_local_offset_valid{0}; time_t dts_local_offset_expiry{0}; + const date::time_zone* dts_default_zone{nullptr}; static const int EXPIRE_TIME = 15 * 60; diff --git a/src/base/itertools.hh b/src/base/itertools.hh index 058ceb81..27a575f2 100644 --- a/src/base/itertools.hh +++ b/src/base/itertools.hh @@ -31,6 +31,8 @@ #define lnav_itertools_hh #include +#include +#include #include #include #include @@ -138,8 +140,16 @@ struct max_with_init { struct sum {}; +struct to_vector {}; + } // namespace details +inline details::to_vector +to_vector() +{ + return details::to_vector{}; +} + template inline details::unwrap_or unwrap_or(T value) @@ -619,12 +629,70 @@ operator|(nonstd::optional in, template auto -operator|(const T& in, const lnav::itertools::details::mapper& mapper) - -> std::vector()))>>> +operator|(const std::set& in, + const lnav::itertools::details::mapper& mapper) + -> std::set()))>>> { - using return_type = std::vector()))>>>; + using return_type = std::set()))>>>; + return_type retval; + + std::transform(in.begin(), + in.end(), + std::inserter(retval, retval.begin()), + mapper.m_func); + + return retval; +} + +template +auto +operator|(const std::vector& in, + const lnav::itertools::details::mapper& mapper) + -> std::vector()))>>> +{ + using return_type = std::vector()))>>>; + return_type retval; + + retval.reserve(in.size()); + std::transform( + in.begin(), in.end(), std::back_inserter(retval), mapper.m_func); + + return retval; +} + +template +auto +operator|(const std::deque& in, + const lnav::itertools::details::mapper& mapper) + -> std::vector()))>>> +{ + using return_type = std::vector()))>>>; + return_type retval; + + retval.reserve(in.size()); + std::transform( + in.begin(), in.end(), std::back_inserter(retval), mapper.m_func); + + return retval; +} + +template +auto +operator|(const std::map& in, + const lnav::itertools::details::mapper& mapper) + -> std::vector< + std::remove_const_t::value_type>()))>>> +{ + using return_type = std::vector< + std::remove_const_t::value_type>()))>>>; return_type retval; retval.reserve(in.size()); @@ -782,4 +850,16 @@ operator|(nonstd::optional in, return in.value_or(unwrapper.uo_value); } +template +std::vector +operator|(std::set&& in, lnav::itertools::details::to_vector tv) +{ + std::vector retval; + + retval.reserve(in.size()); + std::copy(in.begin(), in.end(), std::back_inserter(retval)); + + return retval; +} + #endif diff --git a/src/file_options.cc b/src/file_options.cc new file mode 100644 index 00000000..d5c38ac3 --- /dev/null +++ b/src/file_options.cc @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2023, 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 "file_options.hh" + +#include + +#include "base/lnav_log.hh" +#include "yajlpp/yajlpp.hh" +#include "yajlpp/yajlpp_def.hh" + +namespace lnav { + +static const typed_json_path_container options_handlers = { + yajlpp::property_handler("default-zone") + .with_synopsis("") + .with_description("The default zone") + .with_example("America/Los_Angeles"), +}; + +static const typed_json_path_container + collection_handlers = { + yajlpp::pattern_property_handler("(.*)") + .with_description("Path pattern") + .with_children(options_handlers), +}; + +bool +file_options::operator==(const lnav::file_options& rhs) const +{ + return this->fo_default_zone == rhs.fo_default_zone; +} + +nonstd::optional +file_options_collection::match(const std::string& path) const +{ + auto iter = this->foc_pattern_to_options.find(path); + if (iter != this->foc_pattern_to_options.end()) { + return iter->second; + } + + for (const auto& pair : this->foc_pattern_to_options) { + auto rc = fnmatch(pair.first.c_str(), path.c_str(), FNM_PATHNAME); + + if (rc == 0) { + return pair.second; + } + if (rc != FNM_NOMATCH) { + log_error("fnmatch('%s', '%s') failed -- %s", + pair.first.c_str(), + path.c_str(), + strerror(errno)); + } + } + + return nonstd::nullopt; +} + +nonstd::optional +file_options_hier::match(const ghc::filesystem::path& path) const +{ + auto lookup_path = path.parent_path(); + + while (true) { + const auto iter = this->foh_path_to_collection.find(lookup_path); + if (iter != this->foh_path_to_collection.end()) { + return iter->second.match(path.string()); + } + + auto next_lookup_path = lookup_path.parent_path(); + if (lookup_path == next_lookup_path) { + break; + } + lookup_path = next_lookup_path; + } + return nonstd::nullopt; +} + +} // namespace lnav diff --git a/src/file_options.hh b/src/file_options.hh new file mode 100644 index 00000000..d53ca262 --- /dev/null +++ b/src/file_options.hh @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2023, 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 lnav_file_options_hh +#define lnav_file_options_hh + +#include + +#include "base/lnav.console.hh" +#include "base/result.h" +#include "date/tz.h" +#include "ghc/filesystem.hpp" +#include "mapbox/variant.hpp" +#include "safe/safe.h" + +namespace lnav { + +struct file_options { + intern_string_t fo_default_zone_name; + const date::time_zone* fo_default_zone{nullptr}; + + bool operator==(const file_options& rhs) const; +}; + +struct file_options_collection { + std::map foc_pattern_to_options; + + nonstd::optional match(const std::string& path) const; +}; + +struct file_options_hier { + std::map + foh_path_to_collection; + size_t foh_generation{0}; + + nonstd::optional match( + const ghc::filesystem::path& path) const; +}; + +using safe_file_options_hier = safe::Safe; + +} // namespace lnav + +#endif diff --git a/src/internals/cmd-ref.rst b/src/internals/cmd-ref.rst index 1ce48bc1..af774759 100644 --- a/src/internals/cmd-ref.rst +++ b/src/internals/cmd-ref.rst @@ -1200,6 +1200,21 @@ ---- +.. _set_file_timezone: + +:set-file-timezone *zone* *pattern* +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Set the timezone to use for log messages that do not include a timezone. The timezone is applied to the focused file or the given glob pattern. + + **Parameters** + * **zone\*** --- The timezone name + * **pattern\*** --- The glob pattern to match against files that should use this timezone + + +---- + + .. _set_min_log_level: :set-min-log-level *log-level* diff --git a/src/lnav.cc b/src/lnav.cc index 3e8960cc..4553e948 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -87,6 +87,7 @@ #include "dump_internals.hh" #include "environ_vtab.hh" #include "file_converter_manager.hh" +#include "file_options.hh" #include "filter_sub_source.hh" #include "fstat_vtab.hh" #include "gantt_source.hh" @@ -248,6 +249,9 @@ static auto bound_tailer static auto bound_main = injector::bind_multiple() .add_singleton(); +static auto bound_file_options_hier + = injector::bind::to_singleton(); + namespace injector { template<> void @@ -1834,7 +1838,7 @@ looper() case ln_mode_t::PAGING: case ln_mode_t::FILTER: case ln_mode_t::FILES: - next_rescan_time = next_status_update_time + 1s; + next_rescan_time = next_status_update_time; next_rebuild_time = next_rescan_time; break; default: @@ -1953,7 +1957,8 @@ looper() lnav::session::restore_view_states(); if (lnav_data.ld_mode == ln_mode_t::FILES) { if (lnav_data.ld_log_source.text_line_count() == 0 - && lnav_data.ld_text_source.text_line_count() > 0) + && lnav_data.ld_text_source.text_line_count() > 0 + && lnav_data.ld_view_stack.size() == 1) { log_debug("no logs, just text..."); ensure_view(&lnav_data.ld_views[LNV_TEXT]); @@ -2197,6 +2202,28 @@ main(int argc, char* argv[]) setenv("LNAV_HOME_DIR", lnav::paths::dotlnav().c_str(), 1); setenv("LNAV_WORK_DIR", lnav::paths::workdir().c_str(), 1); + { + auto& safe_options_hier + = injector::get(); + safe::WriteAccess options_hier( + safe_options_hier); + + options_hier->foh_generation += 1; + auto_mem var_path; + + var_path = realpath("/var/log", nullptr); + auto curr_tz = date::get_tzdb().current_zone(); + auto options_coll = lnav::file_options_collection{}; + options_coll.foc_pattern_to_options[fmt::format(FMT_STRING("{}/*"), + var_path.in())] + = lnav::file_options{ + intern_string::lookup(curr_tz->name()), + curr_tz, + }; + options_hier->foh_path_to_collection.emplace(ghc::filesystem::path("/"), + options_coll); + } + lnav_data.ld_exec_context.ec_sql_callback = sql_callback; lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback; @@ -2544,15 +2571,20 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' = attr_line_t("the ") .append("-i"_symbol) .append( - " option expects one or more log format definition " - "files to install in your lnav configuration " + " option expects one or more log format " + "definition " + "files to install in your lnav " + "configuration " "directory"); const auto install_help = attr_line_t( - "log format definitions are JSON files that tell lnav " + "log format definitions are JSON files that " + "tell lnav " "how to understand log files\n") .append( - "See: https://docs.lnav.org/en/latest/formats.html"); + "See: " + "https://docs.lnav.org/en/latest/" + "formats.html"); lnav::console::print(stderr, lnav::console::user_message::error( @@ -2675,7 +2707,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' written = write(out_fd, buffer, rc); if (written == -1) { fprintf(stderr, - "error: unable to install file -- %s\n", + "error: unable to install file " + "-- %s\n", strerror(errno)); exit(EXIT_FAILURE); } @@ -2704,9 +2737,9 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' } } - /* If we statically linked against an ncurses library that had a non- - * standard path to the terminfo database, we need to set this variable - * so that it will try the default path. + /* If we statically linked against an ncurses library that had a + * non- standard path to the terminfo database, we need to set this + * variable so that it will try the default path. */ setenv("TERMINFO_DIRS", "/usr/share/terminfo:/lib/terminfo:/usr/share/lib/terminfo", @@ -2935,7 +2968,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' file_loc = vis_line_t(scan_res.value()); } else { log_warning( - "failed to parse line number from file path with colon: %s", + "failed to parse line number from file path " + "with colon: %s", file_path.c_str()); } } @@ -3073,7 +3107,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' std::string partial_line(sbr.get_data(), partial_len); fprintf(stderr, - "error:%s:%ld:line did not match format %s\n", + "error:%s:%ld:line did not match format " + "%s\n", lf->get_filename().c_str(), line_number, fmt->get_pattern_path(line_number).c_str()); @@ -3151,8 +3186,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' } } } else if (S_ISREG(stdin_st.st_mode)) { - // The shell connected a file directly, just open it up and add it - // in here. + // The shell connected a file directly, just open it up + // and add it in here. auto loo = logfile_open_options{} .with_filename(STDIN_NAME) .with_include_in_session(false); @@ -3190,11 +3225,10 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' stderr, lnav::console::user_message::error("nothing to do") .with_reason("no files given or default files found") - .with_help( - attr_line_t("use the ") - .append_quoted(lnav::roles::keyword("-N")) - .append( - " option to open lnav without loading any files"))); + .with_help(attr_line_t("use the ") + .append_quoted(lnav::roles::keyword("-N")) + .append(" option to open lnav without " + "loading any files"))); retval = EXIT_FAILURE; } @@ -3433,8 +3467,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' fprintf(stderr, "error: %s\n", e.what()); } - // When reading from stdin, tell the user where the capture file is - // stored so they can look at it later. + // When reading from stdin, tell the user where the capture + // file is stored so they can look at it later. if (stdin_url && !(lnav_data.ld_flags & LNF_HEADLESS) && verbosity != verbosity_t::quiet) { diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index 30824fe6..121ebba1 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -326,6 +326,70 @@ com_unix_time(exec_context& ec, return Ok(retval); } +static Result +com_set_file_timezone(exec_context& ec, + std::string cmdline, + std::vector& args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("timezone"); + return Ok(retval); + } + + if (args.size() == 1) { + return ec.make_error("expecting a timezone name"); + } + + auto* tc = *lnav_data.ld_view_stack.top(); + auto* lss = dynamic_cast(tc->get_sub_source()); + + if (lss != nullptr) { + if (lss->text_line_count() == 0) { + return ec.make_error("no log messages to examine"); + } + + auto line_pair = lss->find_line_with_file(lss->at(tc->get_selection())); + if (!line_pair) { + return ec.make_error(FMT_STRING("cannot find line: {}"), + (int) tc->get_selection()); + } + try { + auto* tz = date::locate_zone(args[1]); + + if (!ec.ec_dry_run) { + static auto& safe_options_hier + = injector::get(); + + safe::WriteAccess options_hier( + safe_options_hier); + + options_hier->foh_generation += 1; + auto& coll = options_hier->foh_path_to_collection["/"]; + + log_info("setting timezone for %s to %s", + line_pair->first->get_filename().c_str(), + args[1].c_str()); + coll.foc_pattern_to_options[line_pair->first->get_filename()] + = lnav::file_options{ + intern_string::lookup(args[1]), + tz, + }; + } + } catch (const std::runtime_error& e) { + return ec.make_error(FMT_STRING("Unable to get timezone: {} -- {}"), + args[1], + e.what()); + } + } else { + return ec.make_error( + ":set-file-timezone is only supported for the LOG view"); + } + + return Ok(retval); +} + static Result com_convert_time_to(exec_context& ec, std::string cmdline, @@ -5442,6 +5506,18 @@ readline_context::command_t STD_COMMANDS[] = { .with_summary("Convert the focused timestamp to the given timezone") .with_parameter(help_text("zone", "The timezone name")), }, + { + "set-file-timezone", + com_set_file_timezone, + help_text(":set-file-timezone") + .with_summary("Set the timezone to use for log messages that do " + "not include a timezone. The timezone is applied to " + "the focused file or the given glob pattern.") + .with_parameter({"zone", "The timezone name"}) + .with_parameter(help_text{"pattern", + "The glob pattern to match against " + "files that should use this timezone"}), + }, {"current-time", com_current_time, diff --git a/src/log_format.cc b/src/log_format.cc index da1a93fc..703042cb 100644 --- a/src/log_format.cc +++ b/src/log_format.cc @@ -1061,6 +1061,16 @@ external_log_format::scan(logfile& lf, shared_buffer_ref& sbr, scan_batch_context& sbc) { + if (dst.empty()) { + auto file_options = lf.get_file_options(); + + if (file_options) { + this->lf_date_time.dts_default_zone = file_options->fo_default_zone; + } else { + this->lf_date_time.dts_default_zone = nullptr; + } + } + if (this->elf_type == elf_type_t::ELF_TYPE_JSON) { logline ll(li.li_file_range.fr_offset, 0, 0, LEVEL_INFO); auto line_frag = sbr.to_string_fragment(); diff --git a/src/log_format_impls.cc b/src/log_format_impls.cc index bc2e43a9..ad105560 100644 --- a/src/log_format_impls.cc +++ b/src/log_format_impls.cc @@ -107,6 +107,17 @@ class generic_log_format : public log_format { nonstd::optional level; const char* last_pos; + if (dst.empty()) { + auto file_options = lf.get_file_options(); + + if (file_options) { + this->lf_date_time.dts_default_zone + = file_options->fo_default_zone; + } else { + this->lf_date_time.dts_default_zone = nullptr; + } + } + if ((last_pos = this->log_scanf(dst.size(), sbr.to_string_fragment(), get_pcre_log_formats(), @@ -535,6 +546,17 @@ public: static const auto SEP_RE = lnav::pcre2pp::code::from_const(R"(^#separator\s+(.+))"); + if (dst.empty()) { + auto file_options = lf.get_file_options(); + + if (file_options) { + this->lf_date_time.dts_default_zone + = file_options->fo_default_zone; + } else { + this->lf_date_time.dts_default_zone = nullptr; + } + } + if (!this->blf_format_name.empty()) { return this->scan_int(dst, li, sbr, sbc); } @@ -1200,6 +1222,17 @@ public: return scan_incomplete{}; } + if (dst.empty()) { + auto file_options = lf.get_file_options(); + + if (file_options) { + this->lf_date_time.dts_default_zone + = file_options->fo_default_zone; + } else { + this->lf_date_time.dts_default_zone = nullptr; + } + } + if (!this->wlf_format_name.empty()) { return this->scan_int(dst, li, sbr); } @@ -1713,6 +1746,17 @@ public: bool done = false; logfmt_pair_handler lph(this->lf_date_time); + if (dst.empty()) { + auto file_options = lf.get_file_options(); + + if (file_options) { + this->lf_date_time.dts_default_zone + = file_options->fo_default_zone; + } else { + this->lf_date_time.dts_default_zone = nullptr; + } + } + while (!done) { auto parse_result = p.step(); diff --git a/src/logfile.cc b/src/logfile.cc index c9f848b5..0e343613 100644 --- a/src/logfile.cc +++ b/src/logfile.cc @@ -47,6 +47,7 @@ #include "base/injector.hh" #include "base/string_util.hh" #include "config.h" +#include "file_options.hh" #include "hasher.hh" #include "lnav_util.hh" #include "log.watch.hh" @@ -156,6 +157,8 @@ logfile::open(std::string filename, const logfile_open_options& loo, auto_fd fd) }); } + lf->file_options_have_changed(); + ensure(lf->invariant()); return Ok(lf); @@ -172,6 +175,37 @@ logfile::~logfile() log_info("destructing logfile: %s", this->lf_filename.c_str()); } +bool +logfile::file_options_have_changed() +{ + static auto& safe_options_hier + = injector::get(); + + { + safe::ReadAccess options_hier( + safe_options_hier); + + if (this->lf_file_options_generation == options_hier->foh_generation) { + return false; + } + auto new_options = options_hier->match(this->get_filename()); + if (this->lf_file_options == new_options) { + this->lf_file_options_generation = options_hier->foh_generation; + return false; + } + + this->lf_file_options = new_options; + log_info("%s: file options have changed", this->lf_filename.c_str()); + if (this->lf_file_options) { + log_info(" tz=%s", + this->lf_file_options->fo_default_zone->name().c_str()); + } + this->lf_file_options_generation = options_hier->foh_generation; + } + + return true; +} + bool logfile::exists() const { @@ -497,9 +531,10 @@ logfile::rebuild_index(nonstd::optional deadline) return rebuild_result_t::NO_NEW_LINES; } - if (this->lf_format != nullptr - && (this->lf_zoned_to_local_state != dts_cfg.c_zoned_to_local - || this->lf_format->format_changed())) + if (this->file_options_have_changed() + || (this->lf_format != nullptr + && (this->lf_zoned_to_local_state != dts_cfg.c_zoned_to_local + || this->lf_format->format_changed()))) { log_info("%s: format has changed, rebuilding", this->lf_filename.c_str()); diff --git a/src/logfile.hh b/src/logfile.hh index 999f8788..2d98c62c 100644 --- a/src/logfile.hh +++ b/src/logfile.hh @@ -48,6 +48,7 @@ #include "base/result.h" #include "bookmarks.hh" #include "byte_array.hh" +#include "file_options.hh" #include "ghc/filesystem.hpp" #include "line_buffer.hh" #include "log_format_fwd.hh" @@ -400,6 +401,11 @@ public: return this->lf_embedded_metadata; } + nonstd::optional get_file_options() const + { + return this->lf_file_options; + } + protected: /** * Process a line from the file. @@ -417,6 +423,8 @@ protected: private: logfile(std::string filename, const logfile_open_options& loo); + bool file_options_have_changed(); + std::string lf_filename; logfile_open_options lf_options; logfile_activity lf_activity; @@ -458,6 +466,8 @@ private: std::vector> lf_applicable_taggers; std::map lf_embedded_metadata; + size_t lf_file_options_generation{0}; + nonstd::optional lf_file_options; }; class logline_observer { diff --git a/src/logfile_sub_source.cc b/src/logfile_sub_source.cc index e5faad54..7a83c13f 100644 --- a/src/logfile_sub_source.cc +++ b/src/logfile_sub_source.cc @@ -275,7 +275,8 @@ logfile_sub_source::text_value_for_line(textview_curses& tc, if (!this->lss_token_line->is_continued() && (this->lss_token_file->is_time_adjusted() - || (format->lf_timestamp_flags & ETF_ZONE_SET + || ((format->lf_timestamp_flags & ETF_ZONE_SET + || format->lf_date_time.dts_default_zone != nullptr) && format->lf_date_time.dts_zoned_to_local) || format->lf_timestamp_flags & ETF_MACHINE_ORIENTED || !(format->lf_timestamp_flags & ETF_DAY_SET) @@ -496,8 +497,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, value_out.emplace_back(lr, VC_GRAPHIC.value(graph)); if (!(this->lss_token_flags & RF_FULL)) { - bookmark_vector& bv_search - = bm[&textview_curses::BM_SEARCH]; + auto& bv_search = bm[&textview_curses::BM_SEARCH]; if (binary_search(std::begin(bv_search), std::end(bv_search), @@ -2347,7 +2347,8 @@ logfile_sub_source::text_crumbs_for_line(int line, return breadcrumb::possibility{ elem.to_string(), }; - }); + }) + | lnav::itertools::to_vector(); }, [ec = this->lss_exec_context](const auto& format_name) { static const std::string MOVE_STMT = R"(;UPDATE lnav_views diff --git a/src/plain_text_source.cc b/src/plain_text_source.cc index d931f640..94d856b2 100644 --- a/src/plain_text_source.cc +++ b/src/plain_text_source.cc @@ -112,6 +112,21 @@ plain_text_source::replace_with(const std::vector& text_lines) return *this; } +plain_text_source& +plain_text_source::replace_with(const std::vector& text_lines) +{ + file_off_t off = 0; + for (const auto& al : text_lines) { + this->tds_lines.emplace_back(off, al); + off += al.length() + 1; + } + this->tds_longest_line = this->compute_longest_line(); + if (this->tss_view != nullptr) { + this->tss_view->set_needs_update(); + } + return *this; +} + void plain_text_source::clear() { @@ -148,6 +163,18 @@ plain_text_source::text_value_for_line(textview_curses& tc, text_sub_source::line_flags_t flags) { value_out = this->tds_lines[row].tl_value.get_string(); + this->tds_line_indent_size = 0; + for (const auto& ch : value_out) { + if (ch == ' ') { + this->tds_line_indent_size += 1; + } else if (ch == '\t') { + do { + this->tds_line_indent_size += 1; + } while (this->tds_line_indent_size % 8); + } else { + break; + } + } } void @@ -162,6 +189,18 @@ plain_text_source::text_attrs_for_line(textview_curses& tc, value_out.emplace_back(line_range{0, -1}, VC_STYLE.value(text_attrs{A_REVERSE})); } + for (const auto& indent : this->tds_doc_sections.m_indents) { + if (indent < this->tds_line_indent_size) { + auto guide_lr = line_range{ + (int) indent, + (int) (indent + 1), + line_range::unit::codepoint, + }; + value_out.emplace_back(guide_lr, + VC_BLOCK_ELEM.value(block_elem_t{ + L'\u258f', role_t::VCR_INDENT_GUIDE})); + } + } } size_t diff --git a/src/plain_text_source.hh b/src/plain_text_source.hh index 3cddb1cb..8794dc17 100644 --- a/src/plain_text_source.hh +++ b/src/plain_text_source.hh @@ -77,6 +77,8 @@ public: plain_text_source& replace_with(const std::vector& text_lines); + plain_text_source& replace_with(const std::vector& text_lines); + void clear(); plain_text_source& truncate_to(size_t max_lines); @@ -131,6 +133,7 @@ protected: text_format_t tds_text_format{text_format_t::TF_UNKNOWN}; size_t tds_longest_line{0}; bool tds_reverse_selection{false}; + size_t tds_line_indent_size{0}; lnav::document::metadata tds_doc_sections; }; diff --git a/src/pretty_printer.cc b/src/pretty_printer.cc index 6fd90bc6..6b04af16 100644 --- a/src/pretty_printer.cc +++ b/src/pretty_printer.cc @@ -171,11 +171,16 @@ pretty_printer::append_to(attr_line_t& al) void pretty_printer::write_element(const pretty_printer::element& el) { + ssize_t start_size = this->pp_stream.tellp(); if (this->pp_leading_indent == 0 && this->pp_line_length == 0 && el.e_token == DT_WHITE) { if (this->pp_depth == 0) { this->pp_soft_indent += el.e_capture.length(); + } else { + auto shift_cover = line_range{(int) start_size, (int) start_size}; + shift_string_attrs( + this->pp_attrs, shift_cover, -el.e_capture.length()); } return; } @@ -191,10 +196,10 @@ pretty_printer::write_element(const pretty_printer::element& el) } return; } + int indent_size = 0; if (this->pp_line_length == 0) { - this->append_indent(); + indent_size = this->append_indent(); } - ssize_t start_size = this->pp_stream.tellp(); if (el.e_token == DT_QUOTED_STRING) { auto unquoted_str = auto_mem::malloc(el.e_capture.length() + 1); const char* start @@ -230,11 +235,9 @@ pretty_printer::write_element(const pretty_printer::element& el) } } else { this->pp_stream << this->pp_scanner->to_string_fragment(el.e_capture); - int shift_amount - = start_size - el.e_capture.c_begin - this->pp_shift_accum; - shift_string_attrs(this->pp_attrs, el.e_capture.c_begin, shift_amount); - this->pp_shift_accum = start_size - el.e_capture.c_begin; } + auto shift_cover = line_range{(int) start_size, (int) start_size}; + shift_string_attrs(this->pp_attrs, shift_cover, indent_size); this->pp_line_length += el.e_capture.length(); if (el.e_token == DT_LINE) { this->pp_line_length = 0; @@ -242,29 +245,23 @@ pretty_printer::write_element(const pretty_printer::element& el) } } -void +int pretty_printer::append_indent() { - static const auto INDENT_GUIDELINE = block_elem_t{ - L'\u258f', - role_t::VCR_INDENT_GUIDE, - }; - + auto start_size = this->pp_stream.tellp(); this->pp_stream << std::string( this->pp_leading_indent + this->pp_soft_indent, ' '); this->pp_soft_indent = 0; - if (this->pp_stream.tellp() == this->pp_leading_indent) { - return; - } - for (int lpc = 0; lpc < this->pp_depth; lpc++) { - if (lpc > 0) { - int off = this->pp_stream.tellp(); - this->pp_post_attrs.emplace_back( - line_range{off, off + 1}, - VC_BLOCK_ELEM.value(INDENT_GUIDELINE)); + if (this->pp_stream.tellp() != this->pp_leading_indent) { + for (int lpc = 0; lpc < this->pp_depth; lpc++) { + this->pp_stream << " "; + } + if (this->pp_depth > 0) { + this->pp_indents.insert(this->pp_leading_indent + + 4 * this->pp_depth); } - this->pp_stream << " "; } + return (this->pp_stream.tellp() - start_size); } bool @@ -305,7 +302,11 @@ pretty_printer::flush_values(bool start_on_depth) && (el.e_token == DT_LSQUARE || el.e_token == DT_LCURLY)) { if (this->pp_line_length > 0) { + ssize_t start_size = this->pp_stream.tellp(); this->pp_stream << std::endl; + auto shift_cover + = line_range{(int) start_size, (int) start_size}; + shift_string_attrs(this->pp_attrs, shift_cover, 1); } this->pp_line_length = 0; } @@ -321,13 +322,19 @@ pretty_printer::start_new_line() { bool has_output; + ssize_t start_size = this->pp_stream.tellp(); if (this->pp_line_length > 0) { this->pp_stream << std::endl; + auto shift_cover = line_range{(int) start_size, (int) start_size}; + shift_string_attrs(this->pp_attrs, shift_cover, 1); this->pp_line_length = 0; } has_output = this->flush_values(); if (has_output && this->pp_line_length > 0) { + start_size = this->pp_stream.tellp(); this->pp_stream << std::endl; + auto shift_cover = line_range{(int) start_size, (int) start_size}; + shift_string_attrs(this->pp_attrs, shift_cover, 1); } this->pp_line_length = 0; this->pp_body_lines.top() += 1; diff --git a/src/pretty_printer.hh b/src/pretty_printer.hh index bd356a41..92c1022c 100644 --- a/src/pretty_printer.hh +++ b/src/pretty_printer.hh @@ -94,6 +94,8 @@ public: return std::move(this->pp_hier_stage); } + std::set take_indents() { return std::move(this->pp_indents); } + private: void descend(); @@ -103,7 +105,7 @@ private: bool flush_values(bool start_on_depth = false); - void append_indent(); + int append_indent(); void write_element(const element& el); @@ -130,6 +132,7 @@ private: std::vector pp_intervals; std::vector> pp_hier_nodes; std::unique_ptr pp_hier_stage; + std::set pp_indents; }; #endif diff --git a/src/tailer/Makefile.am b/src/tailer/Makefile.am index bb8a39a4..78568629 100644 --- a/src/tailer/Makefile.am +++ b/src/tailer/Makefile.am @@ -43,6 +43,7 @@ libtailerpp_a_CPPFLAGS = \ -I$(srcdir)/.. \ -I$(srcdir)/../fmtlib \ -I$(srcdir)/../third-party \ + -I$(top_srcdir)/src/third-party/date/include \ -I$(top_srcdir)/src/third-party/scnlib/include libtailerpp_a_SOURCES = \ @@ -56,6 +57,7 @@ libtailerservice_a_CPPFLAGS = \ -I$(srcdir)/.. \ -I$(srcdir)/../fmtlib \ -I$(srcdir)/../third-party \ + -I$(top_srcdir)/src/third-party/date/include \ -I$(top_srcdir)/src/third-party/scnlib/include libtailerservice_a_SOURCES = \ diff --git a/src/textfile_highlighters.cc b/src/textfile_highlighters.cc index f025e191..ef120b1b 100644 --- a/src/textfile_highlighters.cc +++ b/src/textfile_highlighters.cc @@ -244,6 +244,16 @@ setup_highlights(highlight_map_t& hm) .with_text_format(text_format_t::TF_JAVA) .with_role(role_t::VCR_KEYWORD); + hm[{highlight_source_t::INTERNAL, "json.keyword"}] + = highlighter(xpcre_compile(R"((?:null|true|false))")) + .with_nestable(false) + .with_text_format(text_format_t::TF_JSON) + .with_role(role_t::VCR_KEYWORD); + hm[{highlight_source_t::INTERNAL, "json.number"}] + = highlighter(xpcre_compile(R"(-?\d+(?:\.\d+(?:[eE][+\-]?\d+)?)?)")) + .with_nestable(false) + .with_text_format(text_format_t::TF_JSON) + .with_role(role_t::VCR_NUMBER); hm[{highlight_source_t::INTERNAL, "sql.0.comment"}] = highlighter(xpcre_compile("(?:(?<=[\\s;])|^)--.*")) .with_text_format(text_format_t::TF_SQL) diff --git a/src/textfile_sub_source.cc b/src/textfile_sub_source.cc index c47174d3..e9ab926e 100644 --- a/src/textfile_sub_source.cc +++ b/src/textfile_sub_source.cc @@ -616,6 +616,7 @@ textfile_sub_source::rescan_files( file_iterator iter; rescan_result_t retval; + size_t files_scanned = 0; if (this->tss_view == nullptr || this->tss_view->is_paused()) { return retval; @@ -623,7 +624,8 @@ textfile_sub_source::rescan_files( std::vector> closed_files; for (iter = this->tss_files.begin(); iter != this->tss_files.end();) { - if (deadline && ui_clock::now() > deadline.value()) { + if (deadline && files_scanned > 0 && ui_clock::now() > deadline.value()) + { log_info("rescan_files() deadline reached, breaking..."); retval.rr_scan_completed = false; break; @@ -644,6 +646,7 @@ textfile_sub_source::rescan_files( ++iter; continue; } + files_scanned += 1; try { const auto& st = lf->get_stat(); diff --git a/src/view_helpers.cc b/src/view_helpers.cc index 318bc0ec..dfd9a9ee 100644 --- a/src/view_helpers.cc +++ b/src/view_helpers.cc @@ -139,6 +139,11 @@ open_gantt_view() class pretty_sub_source : public plain_text_source { public: + void set_indents(std::set&& indents) + { + this->tds_doc_sections.m_indents = std::move(indents); + } + void text_crumbs_for_line(int line, std::vector& crumbs) override { @@ -308,11 +313,18 @@ open_pretty_view() auto* log_tc = &lnav_data.ld_views[LNV_LOG]; auto* text_tc = &lnav_data.ld_views[LNV_TEXT]; + if (top_tc == log_tc && log_tc->get_inner_height() == 0 + && text_tc->get_inner_height() > 0) + { + lnav_data.ld_view_stack.push_back(text_tc); + top_tc = text_tc; + } + if (top_tc != log_tc && top_tc != text_tc) { return; } - attr_line_t full_text; + std::vector full_text; delete pretty_tc->get_sub_source(); pretty_tc->set_sub_source(nullptr); @@ -324,9 +336,11 @@ open_pretty_view() std::vector all_intervals; std::vector> hier_nodes; std::vector hier_tree_vec; + std::set pretty_indents; if (top_tc == log_tc) { auto& lss = lnav_data.ld_log_source; bool first_line = true; + auto start_off = size_t{0}; for (auto vl = log_tc->get_top(); vl <= log_tc->get_bottom(); ++vl) { content_line_t cl = lss.at(vl); @@ -365,7 +379,6 @@ open_pretty_view() ? body_lr.lr_start - orig_lr.lr_start : orig_lr.lr_start); pretty_printer pp(&ds, orig_al.get_attrs()); - auto start_off = full_text.length(); if (body_lr.is_valid()) { // TODO: dump more details of the line in the output. @@ -375,9 +388,14 @@ open_pretty_view() } pretty_al.split_lines(pretty_lines); + auto prefix_len = prefix_al.length(); auto curr_intervals = pp.take_intervals(); auto line_hier_root = pp.take_hier_root(); + auto curr_indents = pp.take_indents() + | lnav::itertools::map([&prefix_len](const auto& elem) { + return elem + prefix_len; + }); auto line_off = 0; for (auto& pretty_line : pretty_lines) { if (pretty_line.empty() && &pretty_line == &pretty_lines.back()) @@ -385,24 +403,23 @@ open_pretty_view() break; } pretty_line.insert(0, prefix_al); - pretty_line.append("\n"); for (auto& interval : curr_intervals) { if (line_off <= interval.start) { - interval.start += prefix_al.length(); - interval.stop += prefix_al.length(); + interval.start += prefix_len; + interval.stop += prefix_len; } else if (line_off < interval.stop) { - interval.stop += prefix_al.length(); + interval.stop += prefix_len; } } lnav::document::hier_node::depth_first( line_hier_root.get(), - [line_off, prefix_len = prefix_al.length()](auto* hn) { + [line_off, prefix_len = prefix_len](auto* hn) { if (line_off <= hn->hn_start) { hn->hn_start += prefix_len; } }); - line_off += pretty_line.length(); - full_text.append(pretty_line); + line_off += pretty_line.get_string().length(); + full_text.emplace_back(pretty_line); } first_line = false; @@ -415,15 +432,14 @@ open_pretty_view() [start_off](auto* hn) { hn->hn_start += start_off; }); hier_nodes.emplace_back(std::move(line_hier_root)); hier_tree_vec.emplace_back( - start_off, full_text.length(), hier_nodes.back().get()); + start_off, start_off + line_off, hier_nodes.back().get()); all_intervals.insert( all_intervals.end(), std::make_move_iterator(curr_intervals.begin()), std::make_move_iterator(curr_intervals.end())); - } + pretty_indents.insert(curr_indents.begin(), curr_indents.end()); - if (!full_text.empty()) { - full_text.erase(full_text.length() - 1, 1); + start_off += line_off; } } else if (top_tc == text_tc) { if (text_tc->listview_rows(*text_tc)) { @@ -433,19 +449,36 @@ open_pretty_view() *text_tc, text_tc->get_top(), rows); attr_line_t orig_al; - for (const auto& row : rows) { + for (auto& row : rows) { + remove_string_attr(row.get_attrs(), &VC_BLOCK_ELEM); + for (auto& attr : row.get_attrs()) { + if (attr.sa_type == &VC_ROLE) { + auto role = attr.sa_value.get(); + + if (role == text_tc->tc_cursor_role + || role == text_tc->tc_disabled_cursor_role) + { + attr.sa_range.lr_end = attr.sa_range.lr_start; + } + } + } orig_al.append(row); } data_scanner ds(orig_al.get_string()); - string_attrs_t sa; pretty_printer pp(&ds, orig_al.get_attrs()); + attr_line_t pretty_al; + + pp.append_to(pretty_al); + pretty_al.rtrim(); - pp.append_to(full_text); all_intervals = pp.take_intervals(); hier_nodes.emplace_back(pp.take_hier_root()); hier_tree_vec.emplace_back( - 0, full_text.length(), hier_nodes.back().get()); + 0, pretty_al.length(), hier_nodes.back().get()); + pretty_indents = pp.take_indents(); + + pretty_al.split_lines(full_text); } } auto* pts = new pretty_sub_source(); @@ -454,6 +487,8 @@ open_pretty_view() pts->pss_hier_nods = std::move(hier_nodes); pts->pss_hier_tree = std::make_shared( std::move(hier_tree_vec)); + pts->set_indents(std::move(pretty_indents)); + pts->replace_with(full_text); pretty_tc->set_sub_source(pts); if (lnav_data.ld_last_pretty_print_top != log_tc->get_top()) { diff --git a/src/yajlpp/CMakeLists.txt b/src/yajlpp/CMakeLists.txt index 73168ba9..260e6e1b 100644 --- a/src/yajlpp/CMakeLists.txt +++ b/src/yajlpp/CMakeLists.txt @@ -13,7 +13,7 @@ add_library( target_include_directories(yajlpp PUBLIC . .. ../fmtlib ${CMAKE_CURRENT_BINARY_DIR}/..) -target_link_libraries(yajlpp pcrepp yajl ncurses::libcurses) +target_link_libraries(yajlpp pcrepp yajl ncurses::libcurses datepp) add_executable(test_yajlpp test_yajlpp.cc) target_link_libraries(test_yajlpp yajlpp base ${lnav_LIBS}) diff --git a/src/yajlpp/Makefile.am b/src/yajlpp/Makefile.am index cc6bcf68..20911cb2 100644 --- a/src/yajlpp/Makefile.am +++ b/src/yajlpp/Makefile.am @@ -11,6 +11,7 @@ AM_CPPFLAGS = \ $(PCRE_CFLAGS) \ -I$(top_srcdir)/src/ \ -I$(top_srcdir)/src/fmtlib \ + -I$(top_srcdir)/src/third-party/date/include \ -I$(top_srcdir)/src/third-party/scnlib/include AM_LDFLAGS = \ diff --git a/test/Makefile.am b/test/Makefile.am index e4383374..02e88c7b 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -17,6 +17,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src \ -I$(top_srcdir)/src/fmtlib \ -I$(top_srcdir)/src/third-party \ + -I$(top_srcdir)/src/third-party/date/include \ -I$(top_srcdir)/src/third-party/scnlib/include \ $(CODE_COVERAGE_CPPFLAGS) \ $(LIBARCHIVE_CFLAGS) \ diff --git a/test/drive_data_scanner.cc b/test/drive_data_scanner.cc index a7a0fb71..c4170c94 100644 --- a/test/drive_data_scanner.cc +++ b/test/drive_data_scanner.cc @@ -37,6 +37,7 @@ #include #include +#include "base/injector.bind.hh" #include "base/injector.hh" #include "config.h" #include "data_parser.hh" @@ -51,6 +52,9 @@ const char* TMP_NAME = "scanned.tmp"; +static auto bound_file_options_hier + = injector::bind::to_singleton(); + int main(int argc, char* argv[]) { diff --git a/test/drive_logfile.cc b/test/drive_logfile.cc index 2da37dd8..52f8baa7 100644 --- a/test/drive_logfile.cc +++ b/test/drive_logfile.cc @@ -37,6 +37,7 @@ #include #include +#include "base/injector.bind.hh" #include "base/injector.hh" #include "base/opt_util.hh" #include "config.h" @@ -54,6 +55,9 @@ typedef enum { MODE_LEVELS, } dl_mode_t; +static auto bound_file_options_hier + = injector::bind::to_singleton(); + time_t time(time_t* _unused) { diff --git a/test/expected/expected.am b/test/expected/expected.am index 1bfd87a7..5cf73c6e 100644 --- a/test/expected/expected.am +++ b/test/expected/expected.am @@ -378,6 +378,10 @@ EXPECTED_FILES = \ $(srcdir)/%reldir%/test_logfile.sh_c18e14a26d8261c9f72747118a469266121d5459.out \ $(srcdir)/%reldir%/test_logfile.sh_ccb0d31813367c8d9dc5b5df383fac5b780711c1.err \ $(srcdir)/%reldir%/test_logfile.sh_ccb0d31813367c8d9dc5b5df383fac5b780711c1.out \ + $(srcdir)/%reldir%/test_logfile.sh_d14f6d8888652321206549df8a9535399f0fd372.err \ + $(srcdir)/%reldir%/test_logfile.sh_d14f6d8888652321206549df8a9535399f0fd372.out \ + $(srcdir)/%reldir%/test_logfile.sh_de8d59879fe6aa5a012b0748ff77ae26c07aea89.err \ + $(srcdir)/%reldir%/test_logfile.sh_de8d59879fe6aa5a012b0748ff77ae26c07aea89.out \ $(srcdir)/%reldir%/test_logfile.sh_e840b674cd65936a72bd64b1dac1524d16fe44c3.err \ $(srcdir)/%reldir%/test_logfile.sh_e840b674cd65936a72bd64b1dac1524d16fe44c3.out \ $(srcdir)/%reldir%/test_logfile.sh_f171f265d8d45a2707e8b9f53e938f574c614d25.err \ diff --git a/test/expected/test_cmds.sh_ac45fb0f8f9578c3ded0855f694698ec38ce31ad.out b/test/expected/test_cmds.sh_ac45fb0f8f9578c3ded0855f694698ec38ce31ad.out index fa6a3199..877b9672 100644 --- a/test/expected/test_cmds.sh_ac45fb0f8f9578c3ded0855f694698ec38ce31ad.out +++ b/test/expected/test_cmds.sh_ac45fb0f8f9578c3ded0855f694698ec38ce31ad.out @@ -1,12 +1,12 @@ { - "foo bar": null, + "foo bar": null, "array": [ - 1, - 2, - 3 + 1, + 2, + 3 ], "obj": { - "one": 1, - "two": true + "one": 1, + "two": true } } diff --git a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out index 60ba28d8..1c47008d 100644 --- a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out +++ b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out @@ -1446,6 +1446,17 @@ For support questions, email: +:set-file-timezone zone pattern +══════════════════════════════════════════════════════════════════════ + Set the timezone to use for log messages that do not include a + timezone. The timezone is applied to the focused file or the given + glob pattern. +Parameters + zone The timezone name + pattern The glob pattern to match against files that + should use this timezone + + :set-min-log-level log-level ══════════════════════════════════════════════════════════════════════ Set the minimum log level to display in the log view diff --git a/test/expected/test_cmds.sh_e7e8244fac65bc51dbd5af31be476fe3b8776bfc.out b/test/expected/test_cmds.sh_e7e8244fac65bc51dbd5af31be476fe3b8776bfc.out index a5c8a6e9..012e2ef3 100644 --- a/test/expected/test_cmds.sh_e7e8244fac65bc51dbd5af31be476fe3b8776bfc.out +++ b/test/expected/test_cmds.sh_e7e8244fac65bc51dbd5af31be476fe3b8776bfc.out @@ -1,12 +1,12 @@ { - "foo bar" : null, - "array" : [ -   1, -   2, -   3 - ], - "obj" : { - "one" : 1, -    "two" : true + "foo bar" : null, + "array" : [ + 1, + 2, + 3 + ], + "obj" : { + "one" : 1, + "two" : true } } diff --git a/test/expected/test_json_format.sh_a06b3cdd46b387e72d6faa4cce648b8b11ae870b.out b/test/expected/test_json_format.sh_a06b3cdd46b387e72d6faa4cce648b8b11ae870b.out index 672d8548..fbb731b7 100644 --- a/test/expected/test_json_format.sh_a06b3cdd46b387e72d6faa4cce648b8b11ae870b.out +++ b/test/expected/test_json_format.sh_a06b3cdd46b387e72d6faa4cce648b8b11ae870b.out @@ -1,29 +1,29 @@ - + [2013-09-06T20:00:48.124] ⋮ trace testbork bork bork - + [2013-09-06T20:00:49.124] ⋮ Starting up servicebork bork bork - + [2013-09-06T22:00:49.124] ⋮ Shutting down servicebork bork bork user: mailto:steve@example.com - + [2013-09-06T22:00:59.124] ⋮ Details... bork bork bork - + [2013-09-06T22:00:59.124] ⋮ Details... bork bork bork - + [2013-09-06T22:00:59.124] ⋮ Details... bork bork bork - + [2013-09-06T22:00:59.124] ⋮ Details... bork bork bork - + [2013-09-06T22:00:59.124] ⋮ Details...bork bork bork - + [2013-09-06T22:01:49.124] ⋮ 1 beat per secondbork bork bork - + [2013-09-06T22:01:49.124] ⋮ not looking goodbork bork bork - + [2013-09-06T22:01:49.124] ⋮ looking badbork bork bork - + [2013-09-06T22:01:49.124] ⋮ sooo badbork bork bork diff --git a/test/expected/test_pretty_print.sh_3c255c3c8b28df9d694b329a265e8b8140dae4a2.out b/test/expected/test_pretty_print.sh_3c255c3c8b28df9d694b329a265e8b8140dae4a2.out index 35fadf89..974a0b40 100644 --- a/test/expected/test_pretty_print.sh_3c255c3c8b28df9d694b329a265e8b8140dae4a2.out +++ b/test/expected/test_pretty_print.sh_3c255c3c8b28df9d694b329a265e8b8140dae4a2.out @@ -1,8 +1,8 @@ { - "wrapper": [ + "wrapper": [ {"message":"" - select Id from Account where id = $sfid - ^ - ERROR at Row:1:Column:34 - line 1:34 no viable alternative at character '$' -""}]} +   select Id from Account where id = $sfid +     ^ +   ERROR at Row:1:Column:34 +   line 1:34 no viable alternative at character '$' +""}]} diff --git a/test/expected/test_pretty_print.sh_7192f8f68adb14705c8a60e73ff8248c61c7fd03.out b/test/expected/test_pretty_print.sh_7192f8f68adb14705c8a60e73ff8248c61c7fd03.out index 613cc3ac..18f1d8ae 100644 --- a/test/expected/test_pretty_print.sh_7192f8f68adb14705c8a60e73ff8248c61c7fd03.out +++ b/test/expected/test_pretty_print.sh_7192f8f68adb14705c8a60e73ff8248c61c7fd03.out @@ -1,5 +1,5 @@ 2015-04-18T13:16:30.003 { "wrapper": {"msg": r"" Hello, - World! -""}} + + ""}} diff --git a/test/test_logfile.sh b/test/test_logfile.sh index 3ce81569..082710a4 100644 --- a/test/test_logfile.sh +++ b/test/test_logfile.sh @@ -710,3 +710,11 @@ run_cap_test ${lnav_test} -n \ run_cap_test ${lnav_test} -n \ -c ':filter-in Air Mob' \ ${test_dir}/logfile_ansi.1 + +run_cap_test ${lnav_test} -n \ + -c ':set-file-timezone America/Los_Angeles' \ + ${test_dir}/logfile_syslog.0 + +run_cap_test ${lnav_test} -n \ + -c ':set-file-timezone America/New_York' \ + ${test_dir}/logfile_syslog.0