diff options
Diffstat (limited to '')
-rw-r--r-- | src/lnav.management_cli.cc | 468 |
1 files changed, 457 insertions, 11 deletions
diff --git a/src/lnav.management_cli.cc b/src/lnav.management_cli.cc index 71d3994..48828d4 100644 --- a/src/lnav.management_cli.cc +++ b/src/lnav.management_cli.cc @@ -27,18 +27,25 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include <queue> - #include "lnav.management_cli.hh" +#include "base/fs_util.hh" +#include "base/humanize.hh" +#include "base/humanize.time.hh" #include "base/itertools.hh" +#include "base/paths.hh" #include "base/result.h" #include "base/string_util.hh" +#include "file_options.hh" +#include "fmt/chrono.h" #include "fmt/format.h" #include "itertools.similar.hh" +#include "lnav.hh" +#include "lnav_config.hh" #include "log_format.hh" #include "log_format_ext.hh" #include "mapbox/variant.hpp" +#include "piper.looper.hh" #include "regex101.import.hh" #include "session_data.hh" @@ -61,12 +68,117 @@ symbol_reducer(const std::string& elem, attr_line_t& accum) inline attr_line_t& subcmd_reducer(const CLI::App* app, attr_line_t& accum) { - return accum.append("\n \u2022 ") + return accum.append("\n ") + .append("\u2022"_list_glyph) + .append(" ") .append(lnav::roles::keyword(app->get_name())) .append(": ") .append(app->get_description()); } +struct subcmd_config_t { + using action_t = std::function<perform_result_t(const subcmd_config_t&)>; + + CLI::App* sc_config_app{nullptr}; + action_t sc_action; + std::string sc_path; + + static perform_result_t default_action(const subcmd_config_t& sc) + { + auto um = console::user_message::error( + "expecting an operation related to the regex101.com integration"); + um.with_help( + sc.sc_config_app->get_subcommands({}) + | lnav::itertools::fold( + subcmd_reducer, attr_line_t{"the available operations are:"})); + + return {um}; + } + + static perform_result_t get_action(const subcmd_config_t&) + { + auto config_str = dump_config(); + auto um = console::user_message::raw(config_str); + + return {um}; + } + + static perform_result_t blame_action(const subcmd_config_t&) + { + auto blame = attr_line_t(); + + for (const auto& pair : lnav_config_locations) { + blame.appendf(FMT_STRING("{} -> {}:{}\n"), + pair.first, + pair.second.sl_source, + pair.second.sl_line_number); + } + + auto um = console::user_message::raw(blame.rtrim()); + + return {um}; + } + + static perform_result_t file_options_action(const subcmd_config_t& sc) + { + auto& safe_options_hier + = injector::get<lnav::safe_file_options_hier&>(); + + if (sc.sc_path.empty()) { + auto um = lnav::console::user_message::error( + "Expecting a file path to check for options"); + + return {um}; + } + + safe::ReadAccess<lnav::safe_file_options_hier> options_hier( + safe_options_hier); + + auto realpath_res = lnav::filesystem::realpath(sc.sc_path); + if (realpath_res.isErr()) { + auto um = lnav::console::user_message::error( + attr_line_t("Unable to get full path for file: ") + .append(lnav::roles::file(sc.sc_path))) + .with_reason(realpath_res.unwrapErr()); + + return {um}; + } + auto full_path = realpath_res.unwrap(); + auto file_opts = options_hier->match(full_path); + if (file_opts) { + auto content = attr_line_t().append( + file_opts->second.to_json_string().to_string_fragment()); + auto um = lnav::console::user_message::raw(content); + perform_result_t retval; + + retval.emplace_back(um); + + return retval; + } + + auto um + = lnav::console::user_message::info( + attr_line_t("no options found for file: ") + .append(lnav::roles::file(full_path.string()))) + .with_help( + attr_line_t("Use the ") + .append(":set-file-timezone"_symbol) + .append( + " command to set the zone for messages in files " + "that do not include a zone in the timestamp")); + + return {um}; + } + + subcmd_config_t& set_action(action_t act) + { + if (!this->sc_action) { + this->sc_action = std::move(act); + } + return *this; + } +}; + struct subcmd_format_t { using action_t = std::function<perform_result_t(const subcmd_format_t&)>; @@ -622,6 +734,283 @@ struct subcmd_format_t { } }; +struct subcmd_piper_t { + using action_t = std::function<perform_result_t(const subcmd_piper_t&)>; + + CLI::App* sp_app{nullptr}; + action_t sp_action; + + subcmd_piper_t& set_action(action_t act) + { + if (!this->sp_action) { + this->sp_action = std::move(act); + } + return *this; + } + + static perform_result_t default_action(const subcmd_piper_t& sp) + { + auto um = console::user_message::error( + "expecting an operation related to piper storage"); + um.with_help( + sp.sp_app->get_subcommands({}) + | lnav::itertools::fold( + subcmd_reducer, attr_line_t{"the available operations are:"})); + + return {um}; + } + + static perform_result_t list_action(const subcmd_piper_t&) + { + static const intern_string_t SRC = intern_string::lookup("piper"); + static const auto DOT_HEADER = ghc::filesystem::path(".header"); + + struct item { + lnav::piper::header i_header; + std::string i_url; + file_size_t i_total_size{0}; + }; + + file_size_t grand_total{0}; + std::vector<item> items; + std::error_code ec; + + for (const auto& instance_dir : ghc::filesystem::directory_iterator( + lnav::piper::storage_path(), ec)) + { + if (!instance_dir.is_directory()) { + log_warning("piper directory entry is not a directory: %s", + instance_dir.path().c_str()); + continue; + } + + nonstd::optional<lnav::piper::header> hdr_opt; + auto url = fmt::format(FMT_STRING("piper://{}"), + instance_dir.path().filename().string()); + file_size_t total_size{0}; + auto hdr_path = instance_dir / DOT_HEADER; + if (ghc::filesystem::exists(hdr_path)) { + auto hdr_read_res = lnav::filesystem::read_file(hdr_path); + if (hdr_read_res.isOk()) { + auto parse_res + = lnav::piper::header_handlers.parser_for(SRC).of( + hdr_read_res.unwrap()); + if (parse_res.isOk()) { + hdr_opt = parse_res.unwrap(); + } else { + log_error("failed to parse header: %s -- %s", + hdr_path.c_str(), + parse_res.unwrapErr()[0] + .to_attr_line() + .get_string() + .c_str()); + } + } else { + log_error("failed to read header file: %s -- %s", + hdr_path.c_str(), + hdr_read_res.unwrapErr().c_str()); + } + } + + for (const auto& entry : + ghc::filesystem::directory_iterator(instance_dir.path())) + { + if (entry.path().filename() == DOT_HEADER) { + continue; + } + + total_size += entry.file_size(); + char buffer[lnav::piper::HEADER_SIZE]; + + auto entry_open_res + = lnav::filesystem::open_file(entry.path(), O_RDONLY); + if (entry_open_res.isErr()) { + log_warning("unable to open piper file: %s -- %s", + entry.path().c_str(), + entry_open_res.unwrapErr().c_str()); + continue; + } + + auto entry_fd = entry_open_res.unwrap(); + if (read(entry_fd, buffer, sizeof(buffer)) != sizeof(buffer)) { + log_warning("piper file is too small: %s", + entry.path().c_str()); + continue; + } + auto hdr_bits_opt = lnav::piper::read_header(entry_fd, buffer); + if (!hdr_bits_opt) { + log_warning("could not read piper header: %s", + entry.path().c_str()); + continue; + } + + auto hdr_buf = std::move(hdr_bits_opt.value()); + + total_size -= hdr_buf.size(); + auto hdr_sf + = string_fragment::from_bytes(hdr_buf.in(), hdr_buf.size()); + auto hdr_parse_res + = lnav::piper::header_handlers.parser_for(SRC).of(hdr_sf); + if (hdr_parse_res.isErr()) { + log_error("failed to parse piper header: %s", + hdr_parse_res.unwrapErr()[0] + .to_attr_line() + .get_string() + .c_str()); + continue; + } + + auto hdr = hdr_parse_res.unwrap(); + + if (!hdr_opt || hdr < hdr_opt.value()) { + hdr_opt = hdr; + } + } + + if (hdr_opt) { + items.emplace_back(item{hdr_opt.value(), url, total_size}); + } + + grand_total += total_size; + } + + if (ec && ec.value() != ENOENT) { + auto um = lnav::console::user_message::error( + attr_line_t("unable to access piper directory: ") + .append(lnav::roles::file( + lnav::piper::storage_path().string()))) + .with_reason(ec.message()); + return {um}; + } + + if (items.empty()) { + if (verbosity != verbosity_t::quiet) { + auto um + = lnav::console::user_message::info( + attr_line_t("no piper captures were found in:\n\t") + .append(lnav::roles::file( + lnav::piper::storage_path().string()))) + .with_help( + attr_line_t("You can create a capture by " + "piping data into ") + .append(lnav::roles::file("lnav")) + .append(" or using the ") + .append_quoted(lnav::roles::symbol(":sh")) + .append(" command")); + return {um}; + } + + return {}; + } + + auto txt + = items + | lnav::itertools::sort_with([](const item& lhs, const item& rhs) { + if (lhs.i_header < rhs.i_header) { + return true; + } + + if (rhs.i_header < lhs.i_header) { + return false; + } + + return lhs.i_url < rhs.i_url; + }) + | lnav::itertools::map([](const item& it) { + auto ago = humanize::time::point::from_tv(it.i_header.h_ctime) + .as_time_ago(); + auto retval = attr_line_t() + .append(lnav::roles::list_glyph( + fmt::format(FMT_STRING("{:>18}"), ago))) + .append(" ") + .append(lnav::roles::file(it.i_url)) + .append(" ") + .append(lnav::roles::number(fmt::format( + FMT_STRING("{:>8}"), + humanize::file_size( + it.i_total_size, + humanize::alignment::columnar)))) + .append(" ") + .append_quoted(lnav::roles::comment( + it.i_header.h_name)) + .append("\n"); + if (verbosity == verbosity_t::verbose) { + auto env_al + = it.i_header.h_env + | lnav::itertools::map([](const auto& pair) { + return attr_line_t() + .append(lnav::roles::identifier(pair.first)) + .append("=") + .append(pair.second) + .append("\n"); + }) + | lnav::itertools::fold( + [](const auto& elem, auto& accum) { + if (!accum.empty()) { + accum.append(28, ' '); + } + return accum.append(elem); + }, + attr_line_t()); + + retval.append(23, ' ') + .append("cwd: ") + .append(lnav::roles::file(it.i_header.h_cwd)) + .append("\n") + .append(23, ' ') + .append("env: ") + .append(env_al); + } + return retval; + }) + | lnav::itertools::fold( + [](const auto& elem, auto& accum) { + return accum.append(elem); + }, + attr_line_t{}); + txt.rtrim(); + + perform_result_t retval; + if (verbosity != verbosity_t::quiet) { + auto extra_um + = lnav::console::user_message::info( + attr_line_t( + "the following piper captures were found in:\n\t") + .append(lnav::roles::file( + lnav::piper::storage_path().string()))) + .with_note( + attr_line_t("The captures currently consume ") + .append(lnav::roles::number(humanize::file_size( + grand_total, humanize::alignment::none))) + .append(" of disk space. File sizes include " + "associated metadata.")) + .with_help( + "You can reopen a capture by passing the piper URL " + "to lnav"); + retval.emplace_back(extra_um); + } + retval.emplace_back(lnav::console::user_message::raw(txt)); + + return retval; + } + + static perform_result_t clean_action(const subcmd_piper_t&) + { + std::error_code ec; + + ghc::filesystem::remove_all(lnav::piper::storage_path(), ec); + if (ec) { + return { + lnav::console::user_message::error( + "unable to remove piper storage directory") + .with_reason(ec.message()), + }; + } + + return {}; + } +}; + struct subcmd_regex101_t { using action_t = std::function<perform_result_t(const subcmd_regex101_t&)>; @@ -663,18 +1052,18 @@ struct subcmd_regex101_t { }; } - auto entries - = get_res.unwrap() | lnav::itertools::map([](const auto& elem) { + auto entries = get_res.unwrap() + | lnav::itertools::map([](const auto& elem) { return fmt::format( FMT_STRING(" format {} regex {} regex101\n"), elem.re_format_name, elem.re_regex_name); }) | lnav::itertools::fold( - [](const auto& elem, auto& accum) { - return accum.append(elem); - }, - attr_line_t{}); + [](const auto& elem, auto& accum) { + return accum.append(elem); + }, + attr_line_t{}); auto um = console::user_message::ok( entries.add_header("the following regex101 entries were found:\n") @@ -711,8 +1100,11 @@ struct subcmd_regex101_t { } }; -using operations_v - = mapbox::util::variant<no_subcmd_t, subcmd_format_t, subcmd_regex101_t>; +using operations_v = mapbox::util::variant<no_subcmd_t, + subcmd_config_t, + subcmd_format_t, + subcmd_piper_t, + subcmd_regex101_t>; class operations { public: @@ -730,10 +1122,43 @@ describe_cli(CLI::App& app, int argc, char* argv[]) app.add_flag("-m", "Switch to the management CLI mode."); + subcmd_config_t config_args; subcmd_format_t format_args; + subcmd_piper_t piper_args; subcmd_regex101_t regex101_args; { + auto* subcmd_config + = app.add_subcommand("config", + "perform operations on the lnav configuration") + ->callback([&]() { + config_args.set_action(subcmd_config_t::default_action); + retval->o_ops = config_args; + }); + config_args.sc_config_app = subcmd_config; + + subcmd_config->add_subcommand("get", "print the current configuration") + ->callback( + [&]() { config_args.set_action(subcmd_config_t::get_action); }); + + subcmd_config + ->add_subcommand("blame", + "print the configuration options and their source") + ->callback([&]() { + config_args.set_action(subcmd_config_t::blame_action); + }); + + auto* sub_file_options = subcmd_config->add_subcommand( + "file-options", "print the options applied to specific files"); + + sub_file_options->add_option( + "path", config_args.sc_path, "the path to the file"); + sub_file_options->callback([&]() { + config_args.set_action(subcmd_config_t::file_options_action); + }); + } + + { auto* subcmd_format = app.add_subcommand("format", "perform operations on log file formats") @@ -837,6 +1262,25 @@ describe_cli(CLI::App& app, int argc, char* argv[]) } { + auto* subcmd_piper + = app.add_subcommand("piper", "perform operations on piper storage") + ->callback([&]() { + piper_args.set_action(subcmd_piper_t::default_action); + retval->o_ops = piper_args; + }); + piper_args.sp_app = subcmd_piper; + + subcmd_piper + ->add_subcommand("list", "print the available piper captures") + ->callback( + [&]() { piper_args.set_action(subcmd_piper_t::list_action); }); + + subcmd_piper->add_subcommand("clean", "remove all piper captures") + ->callback( + [&]() { piper_args.set_action(subcmd_piper_t::clean_action); }); + } + + { auto* subcmd_regex101 = app.add_subcommand("regex101", "create and edit log message regular " @@ -902,7 +1346,9 @@ perform(std::shared_ptr<operations> opts) return {um}; }, + [](const subcmd_config_t& sc) { return sc.sc_action(sc); }, [](const subcmd_format_t& sf) { return sf.sf_action(sf); }, + [](const subcmd_piper_t& sp) { return sp.sp_action(sp); }, [](const subcmd_regex101_t& sr) { return sr.sr_action(sr); }); } |