[cmds] add command for setting a default time zone

pull/1205/head
Tim Stack 9 months ago
parent 731066a644
commit eacbaa9d4f

@ -269,6 +269,12 @@ add_library(
) )
target_include_directories(cppfmt PUBLIC fmtlib) 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( add_library(
cppscnlib STATIC cppscnlib STATIC
third-party/scnlib/src/reader_float.cpp third-party/scnlib/src/reader_float.cpp
@ -389,6 +395,7 @@ add_library(
file_collection.cc file_collection.cc
file_converter_manager.cc file_converter_manager.cc
file_format.cc file_format.cc
file_options.cc
file_vtab.cc file_vtab.cc
files_sub_source.cc files_sub_source.cc
filter_observer.cc filter_observer.cc
@ -503,6 +510,7 @@ add_library(
file_collection.hh file_collection.hh
file_converter_manager.hh file_converter_manager.hh
file_format.hh file_format.hh
file_options.hh
files_sub_source.hh files_sub_source.hh
filter_observer.hh filter_observer.hh
filter_status_source.hh filter_status_source.hh
@ -665,6 +673,7 @@ target_include_directories(diag PUBLIC . fmtlib ${CMAKE_CURRENT_BINARY_DIR}
target_link_libraries( target_link_libraries(
diag diag
base base
datepp
lnavdt lnavdt
lnavfileio lnavfileio
pcrepp pcrepp
@ -676,7 +685,8 @@ target_link_libraries(
cppfmt cppfmt
base64 base64
spookyhash spookyhash
${lnav_LIBS}) ${lnav_LIBS}
)
target_compile_definitions(diag PRIVATE SQLITE_OMIT_LOAD_EXTENSION) target_compile_definitions(diag PRIVATE SQLITE_OMIT_LOAD_EXTENSION)
check_library_exists(util openpty "" HAVE_LIBUTIL) check_library_exists(util openpty "" HAVE_LIBUTIL)

@ -210,6 +210,7 @@ noinst_HEADERS = \
file_collection.hh \ file_collection.hh \
file_converter_manager.hh \ file_converter_manager.hh \
file_format.hh \ file_format.hh \
file_options.hh \
file_vtab.cfg.hh \ file_vtab.cfg.hh \
files_sub_source.hh \ files_sub_source.hh \
filter_observer.hh \ filter_observer.hh \
@ -407,6 +408,7 @@ libdiag_a_SOURCES = \
file_collection.cc \ file_collection.cc \
file_converter_manager.cc \ file_converter_manager.cc \
file_format.cc \ file_format.cc \
file_options.cc \
files_sub_source.cc \ files_sub_source.cc \
filter_observer.cc \ filter_observer.cc \
filter_status_source.cc \ filter_status_source.cc \

@ -73,7 +73,7 @@ add_library(
target_include_directories(base PUBLIC . .. ../third-party target_include_directories(base PUBLIC . .. ../third-party
${CMAKE_CURRENT_BINARY_DIR}/..) ${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( add_executable(
test_base test_base

@ -7,6 +7,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/src/ \ -I$(top_srcdir)/src/ \
-I$(top_srcdir)/src/third-party \ -I$(top_srcdir)/src/third-party \
-I$(top_srcdir)/src/fmtlib \ -I$(top_srcdir)/src/fmtlib \
-I$(top_srcdir)/src/third-party/date/include \
-I$(top_srcdir)/src/third-party/scnlib/include \ -I$(top_srcdir)/src/third-party/scnlib/include \
$(LIBARCHIVE_CFLAGS) \ $(LIBARCHIVE_CFLAGS) \
$(READLINE_CFLAGS) \ $(READLINE_CFLAGS) \

@ -157,11 +157,24 @@ date_time_scanner::scan(const char* time_dest,
if (convert_local if (convert_local
&& (this->dts_local_time && (this->dts_local_time
|| tm_out->et_flags & ETF_EPOCH_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))) && this->dts_zoned_to_local)))
{ {
time_t gmt = tm_out->to_timeval().tv_sec; 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<std::chrono::seconds>(
ztime.get_sys_time().time_since_epoch())
.count();
}
this->to_localtime(gmt, *tm_out); this->to_localtime(gmt, *tm_out);
} }
const auto& last_tm = this->dts_last_tm.et_tm; 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) if (t < this->dts_local_offset_valid || t >= this->dts_local_offset_expiry)
{ {
time_t new_gmt;
localtime_r(&t, &tm_out.et_tm); localtime_r(&t, &tm_out.et_tm);
// Clear the gmtoff set by localtime_r() otherwise tm2sec() will // Clear the gmtoff set by localtime_r() otherwise tm2sec() will
// convert the time back again. // 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; tm_out.et_tm.tm_zone = nullptr;
#endif #endif
tm_out.et_tm.tm_isdst = 0; tm_out.et_tm.tm_isdst = 0;
auto new_gmt = tm2sec(&tm_out.et_tm);
new_gmt = tm2sec(&tm_out.et_tm);
this->dts_local_offset_cache = new_gmt - t; this->dts_local_offset_cache = new_gmt - t;
this->dts_local_offset_valid = t; this->dts_local_offset_valid = t;
this->dts_local_offset_expiry = t + (EXPIRE_TIME - 1); this->dts_local_offset_expiry = t + (EXPIRE_TIME - 1);

@ -37,6 +37,7 @@
#include <sys/types.h> #include <sys/types.h>
#include "date/tz.h"
#include "time_util.hh" #include "time_util.hh"
/** /**
@ -105,6 +106,7 @@ struct date_time_scanner {
time_t dts_local_offset_cache{0}; time_t dts_local_offset_cache{0};
time_t dts_local_offset_valid{0}; time_t dts_local_offset_valid{0};
time_t dts_local_offset_expiry{0}; time_t dts_local_offset_expiry{0};
const date::time_zone* dts_default_zone{nullptr};
static const int EXPIRE_TIME = 15 * 60; static const int EXPIRE_TIME = 15 * 60;

@ -31,6 +31,8 @@
#define lnav_itertools_hh #define lnav_itertools_hh
#include <algorithm> #include <algorithm>
#include <deque>
#include <map>
#include <memory> #include <memory>
#include <set> #include <set>
#include <type_traits> #include <type_traits>
@ -138,8 +140,16 @@ struct max_with_init {
struct sum {}; struct sum {};
struct to_vector {};
} // namespace details } // namespace details
inline details::to_vector
to_vector()
{
return details::to_vector{};
}
template<typename T> template<typename T>
inline details::unwrap_or<T> inline details::unwrap_or<T>
unwrap_or(T value) unwrap_or(T value)
@ -619,12 +629,70 @@ operator|(nonstd::optional<T> in,
template<typename T, typename F> template<typename T, typename F>
auto auto
operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper) operator|(const std::set<T>& in,
-> std::vector<std::remove_const_t<std::remove_reference_t< const lnav::itertools::details::mapper<F>& mapper)
decltype(mapper.m_func(std::declval<typename T::value_type>()))>>> -> std::set<std::remove_const_t<
std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>
{ {
using return_type = std::vector<std::remove_const_t<std::remove_reference_t< using return_type = std::set<std::remove_const_t<
decltype(mapper.m_func(std::declval<typename T::value_type>()))>>>; std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>;
return_type retval;
std::transform(in.begin(),
in.end(),
std::inserter(retval, retval.begin()),
mapper.m_func);
return retval;
}
template<typename T, typename F>
auto
operator|(const std::vector<T>& in,
const lnav::itertools::details::mapper<F>& mapper)
-> std::vector<std::remove_const_t<
std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>
{
using return_type = std::vector<std::remove_const_t<
std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>;
return_type retval;
retval.reserve(in.size());
std::transform(
in.begin(), in.end(), std::back_inserter(retval), mapper.m_func);
return retval;
}
template<typename T, typename F>
auto
operator|(const std::deque<T>& in,
const lnav::itertools::details::mapper<F>& mapper)
-> std::vector<std::remove_const_t<
std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>
{
using return_type = std::vector<std::remove_const_t<
std::remove_reference_t<decltype(mapper.m_func(std::declval<T>()))>>>;
return_type retval;
retval.reserve(in.size());
std::transform(
in.begin(), in.end(), std::back_inserter(retval), mapper.m_func);
return retval;
}
template<typename K, typename V, typename F>
auto
operator|(const std::map<K, V>& in,
const lnav::itertools::details::mapper<F>& mapper)
-> std::vector<
std::remove_const_t<std::remove_reference_t<decltype(mapper.m_func(
std::declval<typename std::map<K, V>::value_type>()))>>>
{
using return_type = std::vector<
std::remove_const_t<std::remove_reference_t<decltype(mapper.m_func(
std::declval<typename std::map<K, V>::value_type>()))>>>;
return_type retval; return_type retval;
retval.reserve(in.size()); retval.reserve(in.size());
@ -782,4 +850,16 @@ operator|(nonstd::optional<T> in,
return in.value_or(unwrapper.uo_value); return in.value_or(unwrapper.uo_value);
} }
template<typename T>
std::vector<T>
operator|(std::set<T>&& in, lnav::itertools::details::to_vector tv)
{
std::vector<T> retval;
retval.reserve(in.size());
std::copy(in.begin(), in.end(), std::back_inserter(retval));
return retval;
}
#endif #endif

@ -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 <fnmatch.h>
#include "base/lnav_log.hh"
#include "yajlpp/yajlpp.hh"
#include "yajlpp/yajlpp_def.hh"
namespace lnav {
static const typed_json_path_container<file_options> options_handlers = {
yajlpp::property_handler("default-zone")
.with_synopsis("<zone>")
.with_description("The default zone")
.with_example("America/Los_Angeles"),
};
static const typed_json_path_container<file_options_collection>
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>
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>
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

@ -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 <map>
#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<std::string, file_options> foc_pattern_to_options;
nonstd::optional<file_options> match(const std::string& path) const;
};
struct file_options_hier {
std::map<ghc::filesystem::path, file_options_collection>
foh_path_to_collection;
size_t foh_generation{0};
nonstd::optional<file_options> match(
const ghc::filesystem::path& path) const;
};
using safe_file_options_hier = safe::Safe<file_options_hier>;
} // namespace lnav
#endif

@ -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:
:set-min-log-level *log-level* :set-min-log-level *log-level*

@ -87,6 +87,7 @@
#include "dump_internals.hh" #include "dump_internals.hh"
#include "environ_vtab.hh" #include "environ_vtab.hh"
#include "file_converter_manager.hh" #include "file_converter_manager.hh"
#include "file_options.hh"
#include "filter_sub_source.hh" #include "filter_sub_source.hh"
#include "fstat_vtab.hh" #include "fstat_vtab.hh"
#include "gantt_source.hh" #include "gantt_source.hh"
@ -248,6 +249,9 @@ static auto bound_tailer
static auto bound_main = injector::bind_multiple<static_service>() static auto bound_main = injector::bind_multiple<static_service>()
.add_singleton<main_looper, services::main_t>(); .add_singleton<main_looper, services::main_t>();
static auto bound_file_options_hier
= injector::bind<lnav::safe_file_options_hier>::to_singleton();
namespace injector { namespace injector {
template<> template<>
void void
@ -1834,7 +1838,7 @@ looper()
case ln_mode_t::PAGING: case ln_mode_t::PAGING:
case ln_mode_t::FILTER: case ln_mode_t::FILTER:
case ln_mode_t::FILES: 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; next_rebuild_time = next_rescan_time;
break; break;
default: default:
@ -1953,7 +1957,8 @@ looper()
lnav::session::restore_view_states(); lnav::session::restore_view_states();
if (lnav_data.ld_mode == ln_mode_t::FILES) { if (lnav_data.ld_mode == ln_mode_t::FILES) {
if (lnav_data.ld_log_source.text_line_count() == 0 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..."); log_debug("no logs, just text...");
ensure_view(&lnav_data.ld_views[LNV_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_HOME_DIR", lnav::paths::dotlnav().c_str(), 1);
setenv("LNAV_WORK_DIR", lnav::paths::workdir().c_str(), 1); setenv("LNAV_WORK_DIR", lnav::paths::workdir().c_str(), 1);
{
auto& safe_options_hier
= injector::get<lnav::safe_file_options_hier&>();
safe::WriteAccess<lnav::safe_file_options_hier> options_hier(
safe_options_hier);
options_hier->foh_generation += 1;
auto_mem<char> 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_sql_callback = sql_callback;
lnav_data.ld_exec_context.ec_pipe_callback = pipe_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 ") = attr_line_t("the ")
.append("-i"_symbol) .append("-i"_symbol)
.append( .append(
" option expects one or more log format definition " " option expects one or more log format "
"files to install in your lnav configuration " "definition "
"files to install in your lnav "
"configuration "
"directory"); "directory");
const auto install_help const auto install_help
= attr_line_t( = 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") "how to understand log files\n")
.append( .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::print(stderr,
lnav::console::user_message::error( 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); written = write(out_fd, buffer, rc);
if (written == -1) { if (written == -1) {
fprintf(stderr, fprintf(stderr,
"error: unable to install file -- %s\n", "error: unable to install file "
"-- %s\n",
strerror(errno)); strerror(errno));
exit(EXIT_FAILURE); 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- /* If we statically linked against an ncurses library that had a
* standard path to the terminfo database, we need to set this variable * non- standard path to the terminfo database, we need to set this
* so that it will try the default path. * variable so that it will try the default path.
*/ */
setenv("TERMINFO_DIRS", setenv("TERMINFO_DIRS",
"/usr/share/terminfo:/lib/terminfo:/usr/share/lib/terminfo", "/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()); file_loc = vis_line_t(scan_res.value());
} else { } else {
log_warning( 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()); 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); std::string partial_line(sbr.get_data(), partial_len);
fprintf(stderr, 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(), lf->get_filename().c_str(),
line_number, line_number,
fmt->get_pattern_path(line_number).c_str()); 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)) { } else if (S_ISREG(stdin_st.st_mode)) {
// The shell connected a file directly, just open it up and add it // The shell connected a file directly, just open it up
// in here. // and add it in here.
auto loo = logfile_open_options{} auto loo = logfile_open_options{}
.with_filename(STDIN_NAME) .with_filename(STDIN_NAME)
.with_include_in_session(false); .with_include_in_session(false);
@ -3190,11 +3225,10 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
stderr, stderr,
lnav::console::user_message::error("nothing to do") lnav::console::user_message::error("nothing to do")
.with_reason("no files given or default files found") .with_reason("no files given or default files found")
.with_help( .with_help(attr_line_t("use the ")
attr_line_t("use the ") .append_quoted(lnav::roles::keyword("-N"))
.append_quoted(lnav::roles::keyword("-N")) .append(" option to open lnav without "
.append( "loading any files")));
" option to open lnav without loading any files")));
retval = EXIT_FAILURE; 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()); fprintf(stderr, "error: %s\n", e.what());
} }
// When reading from stdin, tell the user where the capture file is // When reading from stdin, tell the user where the capture
// stored so they can look at it later. // file is stored so they can look at it later.
if (stdin_url && !(lnav_data.ld_flags & LNF_HEADLESS) if (stdin_url && !(lnav_data.ld_flags & LNF_HEADLESS)
&& verbosity != verbosity_t::quiet) && verbosity != verbosity_t::quiet)
{ {

@ -326,6 +326,70 @@ com_unix_time(exec_context& ec,
return Ok(retval); return Ok(retval);
} }
static Result<std::string, lnav::console::user_message>
com_set_file_timezone(exec_context& ec,
std::string cmdline,
std::vector<std::string>& 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<logfile_sub_source*>(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<lnav::safe_file_options_hier&>();
safe::WriteAccess<lnav::safe_file_options_hier> 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<std::string, lnav::console::user_message> static Result<std::string, lnav::console::user_message>
com_convert_time_to(exec_context& ec, com_convert_time_to(exec_context& ec,
std::string cmdline, std::string cmdline,
@ -5442,6 +5506,18 @@ readline_context::command_t STD_COMMANDS[] = {
.with_summary("Convert the focused timestamp to the given timezone") .with_summary("Convert the focused timestamp to the given timezone")
.with_parameter(help_text("zone", "The timezone name")), .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", {"current-time",
com_current_time, com_current_time,

@ -1061,6 +1061,16 @@ external_log_format::scan(logfile& lf,
shared_buffer_ref& sbr, shared_buffer_ref& sbr,
scan_batch_context& sbc) 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) { if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
logline ll(li.li_file_range.fr_offset, 0, 0, LEVEL_INFO); logline ll(li.li_file_range.fr_offset, 0, 0, LEVEL_INFO);
auto line_frag = sbr.to_string_fragment(); auto line_frag = sbr.to_string_fragment();

@ -107,6 +107,17 @@ class generic_log_format : public log_format {
nonstd::optional<string_fragment> level; nonstd::optional<string_fragment> level;
const char* last_pos; 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(), if ((last_pos = this->log_scanf(dst.size(),
sbr.to_string_fragment(), sbr.to_string_fragment(),
get_pcre_log_formats(), get_pcre_log_formats(),
@ -535,6 +546,17 @@ public:
static const auto SEP_RE static const auto SEP_RE
= lnav::pcre2pp::code::from_const(R"(^#separator\s+(.+))"); = 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()) { if (!this->blf_format_name.empty()) {
return this->scan_int(dst, li, sbr, sbc); return this->scan_int(dst, li, sbr, sbc);
} }
@ -1200,6 +1222,17 @@ public:
return scan_incomplete{}; 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()) { if (!this->wlf_format_name.empty()) {
return this->scan_int(dst, li, sbr); return this->scan_int(dst, li, sbr);
} }
@ -1713,6 +1746,17 @@ public:
bool done = false; bool done = false;
logfmt_pair_handler lph(this->lf_date_time); 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) { while (!done) {
auto parse_result = p.step(); auto parse_result = p.step();

@ -47,6 +47,7 @@
#include "base/injector.hh" #include "base/injector.hh"
#include "base/string_util.hh" #include "base/string_util.hh"
#include "config.h" #include "config.h"
#include "file_options.hh"
#include "hasher.hh" #include "hasher.hh"
#include "lnav_util.hh" #include "lnav_util.hh"
#include "log.watch.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()); ensure(lf->invariant());
return Ok(lf); return Ok(lf);
@ -172,6 +175,37 @@ logfile::~logfile()
log_info("destructing logfile: %s", this->lf_filename.c_str()); log_info("destructing logfile: %s", this->lf_filename.c_str());
} }
bool
logfile::file_options_have_changed()
{
static auto& safe_options_hier
= injector::get<lnav::safe_file_options_hier&>();
{
safe::ReadAccess<lnav::safe_file_options_hier> 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 bool
logfile::exists() const logfile::exists() const
{ {
@ -497,9 +531,10 @@ logfile::rebuild_index(nonstd::optional<ui_clock::time_point> deadline)
return rebuild_result_t::NO_NEW_LINES; return rebuild_result_t::NO_NEW_LINES;
} }
if (this->lf_format != nullptr if (this->file_options_have_changed()
&& (this->lf_zoned_to_local_state != dts_cfg.c_zoned_to_local || (this->lf_format != nullptr
|| this->lf_format->format_changed())) && (this->lf_zoned_to_local_state != dts_cfg.c_zoned_to_local
|| this->lf_format->format_changed())))
{ {
log_info("%s: format has changed, rebuilding", log_info("%s: format has changed, rebuilding",
this->lf_filename.c_str()); this->lf_filename.c_str());

@ -48,6 +48,7 @@
#include "base/result.h" #include "base/result.h"
#include "bookmarks.hh" #include "bookmarks.hh"
#include "byte_array.hh" #include "byte_array.hh"
#include "file_options.hh"
#include "ghc/filesystem.hpp" #include "ghc/filesystem.hpp"
#include "line_buffer.hh" #include "line_buffer.hh"
#include "log_format_fwd.hh" #include "log_format_fwd.hh"
@ -400,6 +401,11 @@ public:
return this->lf_embedded_metadata; return this->lf_embedded_metadata;
} }
nonstd::optional<lnav::file_options> get_file_options() const
{
return this->lf_file_options;
}
protected: protected:
/** /**
* Process a line from the file. * Process a line from the file.
@ -417,6 +423,8 @@ protected:
private: private:
logfile(std::string filename, const logfile_open_options& loo); logfile(std::string filename, const logfile_open_options& loo);
bool file_options_have_changed();
std::string lf_filename; std::string lf_filename;
logfile_open_options lf_options; logfile_open_options lf_options;
logfile_activity lf_activity; logfile_activity lf_activity;
@ -458,6 +466,8 @@ private:
std::vector<std::shared_ptr<format_tag_def>> lf_applicable_taggers; std::vector<std::shared_ptr<format_tag_def>> lf_applicable_taggers;
std::map<std::string, metadata> lf_embedded_metadata; std::map<std::string, metadata> lf_embedded_metadata;
size_t lf_file_options_generation{0};
nonstd::optional<lnav::file_options> lf_file_options;
}; };
class logline_observer { class logline_observer {

@ -275,7 +275,8 @@ logfile_sub_source::text_value_for_line(textview_curses& tc,
if (!this->lss_token_line->is_continued() if (!this->lss_token_line->is_continued()
&& (this->lss_token_file->is_time_adjusted() && (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_date_time.dts_zoned_to_local)
|| format->lf_timestamp_flags & ETF_MACHINE_ORIENTED || format->lf_timestamp_flags & ETF_MACHINE_ORIENTED
|| !(format->lf_timestamp_flags & ETF_DAY_SET) || !(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)); value_out.emplace_back(lr, VC_GRAPHIC.value(graph));
if (!(this->lss_token_flags & RF_FULL)) { if (!(this->lss_token_flags & RF_FULL)) {
bookmark_vector<vis_line_t>& bv_search auto& bv_search = bm[&textview_curses::BM_SEARCH];
= bm[&textview_curses::BM_SEARCH];
if (binary_search(std::begin(bv_search), if (binary_search(std::begin(bv_search),
std::end(bv_search), std::end(bv_search),
@ -2347,7 +2347,8 @@ logfile_sub_source::text_crumbs_for_line(int line,
return breadcrumb::possibility{ return breadcrumb::possibility{
elem.to_string(), elem.to_string(),
}; };
}); })
| lnav::itertools::to_vector();
}, },
[ec = this->lss_exec_context](const auto& format_name) { [ec = this->lss_exec_context](const auto& format_name) {
static const std::string MOVE_STMT = R"(;UPDATE lnav_views static const std::string MOVE_STMT = R"(;UPDATE lnav_views

@ -112,6 +112,21 @@ plain_text_source::replace_with(const std::vector<std::string>& text_lines)
return *this; return *this;
} }
plain_text_source&
plain_text_source::replace_with(const std::vector<attr_line_t>& 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 void
plain_text_source::clear() 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) text_sub_source::line_flags_t flags)
{ {
value_out = this->tds_lines[row].tl_value.get_string(); 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 void
@ -162,6 +189,18 @@ plain_text_source::text_attrs_for_line(textview_curses& tc,
value_out.emplace_back(line_range{0, -1}, value_out.emplace_back(line_range{0, -1},
VC_STYLE.value(text_attrs{A_REVERSE})); 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 size_t

@ -77,6 +77,8 @@ public:
plain_text_source& replace_with(const std::vector<std::string>& text_lines); plain_text_source& replace_with(const std::vector<std::string>& text_lines);
plain_text_source& replace_with(const std::vector<attr_line_t>& text_lines);
void clear(); void clear();
plain_text_source& truncate_to(size_t max_lines); 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}; text_format_t tds_text_format{text_format_t::TF_UNKNOWN};
size_t tds_longest_line{0}; size_t tds_longest_line{0};
bool tds_reverse_selection{false}; bool tds_reverse_selection{false};
size_t tds_line_indent_size{0};
lnav::document::metadata tds_doc_sections; lnav::document::metadata tds_doc_sections;
}; };

@ -171,11 +171,16 @@ pretty_printer::append_to(attr_line_t& al)
void void
pretty_printer::write_element(const pretty_printer::element& el) 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 if (this->pp_leading_indent == 0 && this->pp_line_length == 0
&& el.e_token == DT_WHITE) && el.e_token == DT_WHITE)
{ {
if (this->pp_depth == 0) { if (this->pp_depth == 0) {
this->pp_soft_indent += el.e_capture.length(); 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; return;
} }
@ -191,10 +196,10 @@ pretty_printer::write_element(const pretty_printer::element& el)
} }
return; return;
} }
int indent_size = 0;
if (this->pp_line_length == 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) { if (el.e_token == DT_QUOTED_STRING) {
auto unquoted_str = auto_mem<char>::malloc(el.e_capture.length() + 1); auto unquoted_str = auto_mem<char>::malloc(el.e_capture.length() + 1);
const char* start const char* start
@ -230,11 +235,9 @@ pretty_printer::write_element(const pretty_printer::element& el)
} }
} else { } else {
this->pp_stream << this->pp_scanner->to_string_fragment(el.e_capture); 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(); this->pp_line_length += el.e_capture.length();
if (el.e_token == DT_LINE) { if (el.e_token == DT_LINE) {
this->pp_line_length = 0; this->pp_line_length = 0;
@ -242,29 +245,23 @@ pretty_printer::write_element(const pretty_printer::element& el)
} }
} }
void int
pretty_printer::append_indent() pretty_printer::append_indent()
{ {
static const auto INDENT_GUIDELINE = block_elem_t{ auto start_size = this->pp_stream.tellp();
L'\u258f',
role_t::VCR_INDENT_GUIDE,
};
this->pp_stream << std::string( this->pp_stream << std::string(
this->pp_leading_indent + this->pp_soft_indent, ' '); this->pp_leading_indent + this->pp_soft_indent, ' ');
this->pp_soft_indent = 0; this->pp_soft_indent = 0;
if (this->pp_stream.tellp() == this->pp_leading_indent) { if (this->pp_stream.tellp() != this->pp_leading_indent) {
return; for (int lpc = 0; lpc < this->pp_depth; lpc++) {
} this->pp_stream << " ";
for (int lpc = 0; lpc < this->pp_depth; lpc++) { }
if (lpc > 0) { if (this->pp_depth > 0) {
int off = this->pp_stream.tellp(); this->pp_indents.insert(this->pp_leading_indent
this->pp_post_attrs.emplace_back( + 4 * this->pp_depth);
line_range{off, off + 1},
VC_BLOCK_ELEM.value(INDENT_GUIDELINE));
} }
this->pp_stream << " ";
} }
return (this->pp_stream.tellp() - start_size);
} }
bool bool
@ -305,7 +302,11 @@ pretty_printer::flush_values(bool start_on_depth)
&& (el.e_token == DT_LSQUARE || el.e_token == DT_LCURLY)) && (el.e_token == DT_LSQUARE || el.e_token == DT_LCURLY))
{ {
if (this->pp_line_length > 0) { if (this->pp_line_length > 0) {
ssize_t start_size = this->pp_stream.tellp();
this->pp_stream << std::endl; 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_line_length = 0;
} }
@ -321,13 +322,19 @@ pretty_printer::start_new_line()
{ {
bool has_output; bool has_output;
ssize_t start_size = this->pp_stream.tellp();
if (this->pp_line_length > 0) { if (this->pp_line_length > 0) {
this->pp_stream << std::endl; 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_line_length = 0;
} }
has_output = this->flush_values(); has_output = this->flush_values();
if (has_output && this->pp_line_length > 0) { if (has_output && this->pp_line_length > 0) {
start_size = this->pp_stream.tellp();
this->pp_stream << std::endl; 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_line_length = 0;
this->pp_body_lines.top() += 1; this->pp_body_lines.top() += 1;

@ -94,6 +94,8 @@ public:
return std::move(this->pp_hier_stage); return std::move(this->pp_hier_stage);
} }
std::set<size_t> take_indents() { return std::move(this->pp_indents); }
private: private:
void descend(); void descend();
@ -103,7 +105,7 @@ private:
bool flush_values(bool start_on_depth = false); bool flush_values(bool start_on_depth = false);
void append_indent(); int append_indent();
void write_element(const element& el); void write_element(const element& el);
@ -130,6 +132,7 @@ private:
std::vector<lnav::document::section_interval_t> pp_intervals; std::vector<lnav::document::section_interval_t> pp_intervals;
std::vector<std::unique_ptr<lnav::document::hier_node>> pp_hier_nodes; std::vector<std::unique_ptr<lnav::document::hier_node>> pp_hier_nodes;
std::unique_ptr<lnav::document::hier_node> pp_hier_stage; std::unique_ptr<lnav::document::hier_node> pp_hier_stage;
std::set<size_t> pp_indents;
}; };
#endif #endif

@ -43,6 +43,7 @@ libtailerpp_a_CPPFLAGS = \
-I$(srcdir)/.. \ -I$(srcdir)/.. \
-I$(srcdir)/../fmtlib \ -I$(srcdir)/../fmtlib \
-I$(srcdir)/../third-party \ -I$(srcdir)/../third-party \
-I$(top_srcdir)/src/third-party/date/include \
-I$(top_srcdir)/src/third-party/scnlib/include -I$(top_srcdir)/src/third-party/scnlib/include
libtailerpp_a_SOURCES = \ libtailerpp_a_SOURCES = \
@ -56,6 +57,7 @@ libtailerservice_a_CPPFLAGS = \
-I$(srcdir)/.. \ -I$(srcdir)/.. \
-I$(srcdir)/../fmtlib \ -I$(srcdir)/../fmtlib \
-I$(srcdir)/../third-party \ -I$(srcdir)/../third-party \
-I$(top_srcdir)/src/third-party/date/include \
-I$(top_srcdir)/src/third-party/scnlib/include -I$(top_srcdir)/src/third-party/scnlib/include
libtailerservice_a_SOURCES = \ libtailerservice_a_SOURCES = \

@ -244,6 +244,16 @@ setup_highlights(highlight_map_t& hm)
.with_text_format(text_format_t::TF_JAVA) .with_text_format(text_format_t::TF_JAVA)
.with_role(role_t::VCR_KEYWORD); .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"}] hm[{highlight_source_t::INTERNAL, "sql.0.comment"}]
= highlighter(xpcre_compile("(?:(?<=[\\s;])|^)--.*")) = highlighter(xpcre_compile("(?:(?<=[\\s;])|^)--.*"))
.with_text_format(text_format_t::TF_SQL) .with_text_format(text_format_t::TF_SQL)

@ -616,6 +616,7 @@ textfile_sub_source::rescan_files(
file_iterator iter; file_iterator iter;
rescan_result_t retval; rescan_result_t retval;
size_t files_scanned = 0;
if (this->tss_view == nullptr || this->tss_view->is_paused()) { if (this->tss_view == nullptr || this->tss_view->is_paused()) {
return retval; return retval;
@ -623,7 +624,8 @@ textfile_sub_source::rescan_files(
std::vector<std::shared_ptr<logfile>> closed_files; std::vector<std::shared_ptr<logfile>> closed_files;
for (iter = this->tss_files.begin(); iter != this->tss_files.end();) { 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..."); log_info("rescan_files() deadline reached, breaking...");
retval.rr_scan_completed = false; retval.rr_scan_completed = false;
break; break;
@ -644,6 +646,7 @@ textfile_sub_source::rescan_files(
++iter; ++iter;
continue; continue;
} }
files_scanned += 1;
try { try {
const auto& st = lf->get_stat(); const auto& st = lf->get_stat();

@ -139,6 +139,11 @@ open_gantt_view()
class pretty_sub_source : public plain_text_source { class pretty_sub_source : public plain_text_source {
public: public:
void set_indents(std::set<size_t>&& indents)
{
this->tds_doc_sections.m_indents = std::move(indents);
}
void text_crumbs_for_line(int line, void text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs) override std::vector<breadcrumb::crumb>& crumbs) override
{ {
@ -308,11 +313,18 @@ open_pretty_view()
auto* log_tc = &lnav_data.ld_views[LNV_LOG]; auto* log_tc = &lnav_data.ld_views[LNV_LOG];
auto* text_tc = &lnav_data.ld_views[LNV_TEXT]; 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) { if (top_tc != log_tc && top_tc != text_tc) {
return; return;
} }
attr_line_t full_text; std::vector<attr_line_t> full_text;
delete pretty_tc->get_sub_source(); delete pretty_tc->get_sub_source();
pretty_tc->set_sub_source(nullptr); pretty_tc->set_sub_source(nullptr);
@ -324,9 +336,11 @@ open_pretty_view()
std::vector<lnav::document::section_interval_t> all_intervals; std::vector<lnav::document::section_interval_t> all_intervals;
std::vector<std::unique_ptr<lnav::document::hier_node>> hier_nodes; std::vector<std::unique_ptr<lnav::document::hier_node>> hier_nodes;
std::vector<pretty_sub_source::hier_interval_t> hier_tree_vec; std::vector<pretty_sub_source::hier_interval_t> hier_tree_vec;
std::set<size_t> pretty_indents;
if (top_tc == log_tc) { if (top_tc == log_tc) {
auto& lss = lnav_data.ld_log_source; auto& lss = lnav_data.ld_log_source;
bool first_line = true; bool first_line = true;
auto start_off = size_t{0};
for (auto vl = log_tc->get_top(); vl <= log_tc->get_bottom(); ++vl) { for (auto vl = log_tc->get_top(); vl <= log_tc->get_bottom(); ++vl) {
content_line_t cl = lss.at(vl); content_line_t cl = lss.at(vl);
@ -365,7 +379,6 @@ open_pretty_view()
? body_lr.lr_start - orig_lr.lr_start ? body_lr.lr_start - orig_lr.lr_start
: orig_lr.lr_start); : orig_lr.lr_start);
pretty_printer pp(&ds, orig_al.get_attrs()); pretty_printer pp(&ds, orig_al.get_attrs());
auto start_off = full_text.length();
if (body_lr.is_valid()) { if (body_lr.is_valid()) {
// TODO: dump more details of the line in the output. // TODO: dump more details of the line in the output.
@ -375,9 +388,14 @@ open_pretty_view()
} }
pretty_al.split_lines(pretty_lines); pretty_al.split_lines(pretty_lines);
auto prefix_len = prefix_al.length();
auto curr_intervals = pp.take_intervals(); auto curr_intervals = pp.take_intervals();
auto line_hier_root = pp.take_hier_root(); 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; auto line_off = 0;
for (auto& pretty_line : pretty_lines) { for (auto& pretty_line : pretty_lines) {
if (pretty_line.empty() && &pretty_line == &pretty_lines.back()) if (pretty_line.empty() && &pretty_line == &pretty_lines.back())
@ -385,24 +403,23 @@ open_pretty_view()
break; break;
} }
pretty_line.insert(0, prefix_al); pretty_line.insert(0, prefix_al);
pretty_line.append("\n");
for (auto& interval : curr_intervals) { for (auto& interval : curr_intervals) {
if (line_off <= interval.start) { if (line_off <= interval.start) {
interval.start += prefix_al.length(); interval.start += prefix_len;
interval.stop += prefix_al.length(); interval.stop += prefix_len;
} else if (line_off < interval.stop) { } else if (line_off < interval.stop) {
interval.stop += prefix_al.length(); interval.stop += prefix_len;
} }
} }
lnav::document::hier_node::depth_first( lnav::document::hier_node::depth_first(
line_hier_root.get(), 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) { if (line_off <= hn->hn_start) {
hn->hn_start += prefix_len; hn->hn_start += prefix_len;
} }
}); });
line_off += pretty_line.length(); line_off += pretty_line.get_string().length();
full_text.append(pretty_line); full_text.emplace_back(pretty_line);
} }
first_line = false; first_line = false;
@ -415,15 +432,14 @@ open_pretty_view()
[start_off](auto* hn) { hn->hn_start += start_off; }); [start_off](auto* hn) { hn->hn_start += start_off; });
hier_nodes.emplace_back(std::move(line_hier_root)); hier_nodes.emplace_back(std::move(line_hier_root));
hier_tree_vec.emplace_back( 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.insert(
all_intervals.end(), all_intervals.end(),
std::make_move_iterator(curr_intervals.begin()), std::make_move_iterator(curr_intervals.begin()),
std::make_move_iterator(curr_intervals.end())); std::make_move_iterator(curr_intervals.end()));
} pretty_indents.insert(curr_indents.begin(), curr_indents.end());
if (!full_text.empty()) { start_off += line_off;
full_text.erase(full_text.length() - 1, 1);
} }
} else if (top_tc == text_tc) { } else if (top_tc == text_tc) {
if (text_tc->listview_rows(*text_tc)) { if (text_tc->listview_rows(*text_tc)) {
@ -433,19 +449,36 @@ open_pretty_view()
*text_tc, text_tc->get_top(), rows); *text_tc, text_tc->get_top(), rows);
attr_line_t orig_al; 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<role_t>();
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); orig_al.append(row);
} }
data_scanner ds(orig_al.get_string()); data_scanner ds(orig_al.get_string());
string_attrs_t sa;
pretty_printer pp(&ds, orig_al.get_attrs()); 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(); all_intervals = pp.take_intervals();
hier_nodes.emplace_back(pp.take_hier_root()); hier_nodes.emplace_back(pp.take_hier_root());
hier_tree_vec.emplace_back( 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(); auto* pts = new pretty_sub_source();
@ -454,6 +487,8 @@ open_pretty_view()
pts->pss_hier_nods = std::move(hier_nodes); pts->pss_hier_nods = std::move(hier_nodes);
pts->pss_hier_tree = std::make_shared<pretty_sub_source::hier_tree_t>( pts->pss_hier_tree = std::make_shared<pretty_sub_source::hier_tree_t>(
std::move(hier_tree_vec)); std::move(hier_tree_vec));
pts->set_indents(std::move(pretty_indents));
pts->replace_with(full_text); pts->replace_with(full_text);
pretty_tc->set_sub_source(pts); pretty_tc->set_sub_source(pts);
if (lnav_data.ld_last_pretty_print_top != log_tc->get_top()) { if (lnav_data.ld_last_pretty_print_top != log_tc->get_top()) {

@ -13,7 +13,7 @@ add_library(
target_include_directories(yajlpp PUBLIC . .. ../fmtlib target_include_directories(yajlpp PUBLIC . .. ../fmtlib
${CMAKE_CURRENT_BINARY_DIR}/..) ${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) add_executable(test_yajlpp test_yajlpp.cc)
target_link_libraries(test_yajlpp yajlpp base ${lnav_LIBS}) target_link_libraries(test_yajlpp yajlpp base ${lnav_LIBS})

@ -11,6 +11,7 @@ AM_CPPFLAGS = \
$(PCRE_CFLAGS) \ $(PCRE_CFLAGS) \
-I$(top_srcdir)/src/ \ -I$(top_srcdir)/src/ \
-I$(top_srcdir)/src/fmtlib \ -I$(top_srcdir)/src/fmtlib \
-I$(top_srcdir)/src/third-party/date/include \
-I$(top_srcdir)/src/third-party/scnlib/include -I$(top_srcdir)/src/third-party/scnlib/include
AM_LDFLAGS = \ AM_LDFLAGS = \

@ -17,6 +17,7 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/src \ -I$(top_srcdir)/src \
-I$(top_srcdir)/src/fmtlib \ -I$(top_srcdir)/src/fmtlib \
-I$(top_srcdir)/src/third-party \ -I$(top_srcdir)/src/third-party \
-I$(top_srcdir)/src/third-party/date/include \
-I$(top_srcdir)/src/third-party/scnlib/include \ -I$(top_srcdir)/src/third-party/scnlib/include \
$(CODE_COVERAGE_CPPFLAGS) \ $(CODE_COVERAGE_CPPFLAGS) \
$(LIBARCHIVE_CFLAGS) \ $(LIBARCHIVE_CFLAGS) \

@ -37,6 +37,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "base/injector.bind.hh"
#include "base/injector.hh" #include "base/injector.hh"
#include "config.h" #include "config.h"
#include "data_parser.hh" #include "data_parser.hh"
@ -51,6 +52,9 @@
const char* TMP_NAME = "scanned.tmp"; const char* TMP_NAME = "scanned.tmp";
static auto bound_file_options_hier
= injector::bind<lnav::safe_file_options_hier>::to_singleton();
int int
main(int argc, char* argv[]) main(int argc, char* argv[])
{ {

@ -37,6 +37,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include "base/injector.bind.hh"
#include "base/injector.hh" #include "base/injector.hh"
#include "base/opt_util.hh" #include "base/opt_util.hh"
#include "config.h" #include "config.h"
@ -54,6 +55,9 @@ typedef enum {
MODE_LEVELS, MODE_LEVELS,
} dl_mode_t; } dl_mode_t;
static auto bound_file_options_hier
= injector::bind<lnav::safe_file_options_hier>::to_singleton();
time_t time_t
time(time_t* _unused) time(time_t* _unused)
{ {

@ -378,6 +378,10 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_logfile.sh_c18e14a26d8261c9f72747118a469266121d5459.out \ $(srcdir)/%reldir%/test_logfile.sh_c18e14a26d8261c9f72747118a469266121d5459.out \
$(srcdir)/%reldir%/test_logfile.sh_ccb0d31813367c8d9dc5b5df383fac5b780711c1.err \ $(srcdir)/%reldir%/test_logfile.sh_ccb0d31813367c8d9dc5b5df383fac5b780711c1.err \
$(srcdir)/%reldir%/test_logfile.sh_ccb0d31813367c8d9dc5b5df383fac5b780711c1.out \ $(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.err \
$(srcdir)/%reldir%/test_logfile.sh_e840b674cd65936a72bd64b1dac1524d16fe44c3.out \ $(srcdir)/%reldir%/test_logfile.sh_e840b674cd65936a72bd64b1dac1524d16fe44c3.out \
$(srcdir)/%reldir%/test_logfile.sh_f171f265d8d45a2707e8b9f53e938f574c614d25.err \ $(srcdir)/%reldir%/test_logfile.sh_f171f265d8d45a2707e8b9f53e938f574c614d25.err \

@ -1,12 +1,12 @@
{ {
"foo bar": null, "foo bar": null,
"array": [ "array": [
1, 1,
2, 2,
3 3
], ],
"obj": { "obj": {
"one": 1, "one": 1,
"two": true "two": true
} }
} }

@ -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-min-log-level log-level
══════════════════════════════════════════════════════════════════════ ══════════════════════════════════════════════════════════════════════
Set the minimum log level to display in the log view Set the minimum log level to display in the log view

@ -1,12 +1,12 @@
{ {
 "foo bar" : null, "foo bar" : null,
 "array" : [ "array" : [
   1, 1,
   2, 2,
   3 3
 ], ],
 "obj" : { "obj" : {
"one" : 1, "one" : 1,
   "two" : true "two" : true
} }
} }

@ -1,29 +1,29 @@
[2013-09-06T20:00:48.124] ⋮ trace testbork bork bork [2013-09-06T20:00:48.124] ⋮ trace testbork bork bork
[2013-09-06T20:00:49.124] ⋮ Starting up servicebork bork bork [2013-09-06T20:00:49.124] ⋮ Starting up servicebork bork bork
[2013-09-06T22:00:49.124] ⋮ Shutting down servicebork bork bork [2013-09-06T22:00:49.124] ⋮ Shutting down servicebork bork bork
user: mailto:steve@example.com user: mailto:steve@example.com
[2013-09-06T22:00:59.124] ⋮ Details... [2013-09-06T22:00:59.124] ⋮ Details...
bork bork bork bork bork bork
[2013-09-06T22:00:59.124] ⋮ Details... [2013-09-06T22:00:59.124] ⋮ Details...
bork bork bork bork bork bork
[2013-09-06T22:00:59.124] ⋮ Details... [2013-09-06T22:00:59.124] ⋮ Details...
bork bork bork bork bork bork
[2013-09-06T22:00:59.124] ⋮ Details... [2013-09-06T22:00:59.124] ⋮ Details...
bork bork bork 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] ⋮ 1 beat per secondbork bork bork
[2013-09-06T22:01:49.124] ⋮ not looking goodbork 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] ⋮ looking badbork bork bork
[2013-09-06T22:01:49.124] ⋮ sooo badbork bork bork [2013-09-06T22:01:49.124] ⋮ sooo badbork bork bork

@ -1,8 +1,8 @@
{ {
 "wrapper": [ "wrapper": [
{"message":"" {"message":""
 select Id from Account where id = $sfid    select Id from Account where id = $sfid
 ^      ^
 ERROR at Row:1:Column:34    ERROR at Row:1:Column:34
 line 1:34 no viable alternative at character '$'    line 1:34 no viable alternative at character '$'
""}]} ""}]}

@ -1,5 +1,5 @@
2015-04-18T13:16:30.003 { 2015-04-18T13:16:30.003 {
"wrapper": {"msg": r"" "wrapper": {"msg": r""
Hello, Hello,
World!
""}} ""}}

@ -710,3 +710,11 @@ run_cap_test ${lnav_test} -n \
run_cap_test ${lnav_test} -n \ run_cap_test ${lnav_test} -n \
-c ':filter-in Air Mob' \ -c ':filter-in Air Mob' \
${test_dir}/logfile_ansi.1 ${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

Loading…
Cancel
Save