lnav/src/session.export.cc

344 lines
12 KiB
C++

/**
* Copyright (c) 2022, 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 "session.export.hh"
#include "base/injector.hh"
#include "bound_tags.hh"
#include "lnav.hh"
#include "sqlitepp.client.hh"
#include "sqlitepp.hh"
#include "textview_curses.hh"
struct log_message_session_state {
int64_t lmss_time_msecs;
std::string lmss_format;
bool lmss_mark;
nonstd::optional<std::string> lmss_comment;
nonstd::optional<std::string> lmss_tags;
std::string lmss_hash;
};
template<>
struct from_sqlite<log_message_session_state> {
inline log_message_session_state operator()(int argc,
sqlite3_value** argv,
int argi)
{
return {
from_sqlite<int64_t>()(argc, argv, argi + 0),
from_sqlite<std::string>()(argc, argv, argi + 1),
from_sqlite<bool>()(argc, argv, argi + 2),
from_sqlite<nonstd::optional<std::string>>()(argc, argv, argi + 3),
from_sqlite<nonstd::optional<std::string>>()(argc, argv, argi + 4),
from_sqlite<std::string>()(argc, argv, argi + 5),
};
}
};
struct log_filter_session_state {
std::string lfss_name;
bool lfss_enabled;
std::string lfss_type;
std::string lfss_language;
std::string lfss_pattern;
};
template<>
struct from_sqlite<log_filter_session_state> {
inline log_filter_session_state operator()(int argc,
sqlite3_value** argv,
int argi)
{
return {
from_sqlite<std::string>()(argc, argv, argi + 0),
from_sqlite<bool>()(argc, argv, argi + 1),
from_sqlite<std::string>()(argc, argv, argi + 2),
from_sqlite<std::string>()(argc, argv, argi + 3),
from_sqlite<std::string>()(argc, argv, argi + 4),
};
}
};
struct log_file_session_state {
std::string lfss_content_id;
std::string lfss_format;
int64_t lfss_time_offset;
};
template<>
struct from_sqlite<log_file_session_state> {
inline log_file_session_state operator()(int argc,
sqlite3_value** argv,
int argi)
{
return {
from_sqlite<std::string>()(argc, argv, argi + 0),
from_sqlite<std::string>()(argc, argv, argi + 1),
from_sqlite<int64_t>()(argc, argv, argi + 2),
};
}
};
namespace lnav {
namespace session {
Result<void, lnav::console::user_message>
export_to(FILE* file)
{
static auto& lnav_db
= injector::get<auto_mem<sqlite3, sqlite_close_wrapper>&,
sqlite_db_tag>();
static const char* BOOKMARK_QUERY = R"(
SELECT log_time_msecs, log_format, log_mark, log_comment, log_tags, log_line_hash
FROM all_logs
WHERE log_mark = 1 OR log_comment IS NOT NULL OR log_tags IS NOT NULL
)";
static const char* FILTER_QUERY = R"(
SELECT view_name, enabled, type, language, pattern FROM lnav_view_filters
)";
static const char* FILE_QUERY = R"(
SELECT content_id, format, time_offset FROM lnav_file
WHERE format IS NOT NULL AND time_offset != 0
)";
static const char* HEADER = R"(
# This file is an export of an lnav session. You can type
# '|/path/to/this/file' in lnav to execute this file and
# restore the state of the session.
#
# The files loaded into the session were:
)";
static constexpr const char MARK_HEADER[] = R"(
# The following SQL statements will restore the bookmarks,
# comments, and tags that were added in the session.
;SELECT total_changes() AS before_mark_changes
)";
static constexpr const char MARK_FOOTER[] = R"(
;SELECT {} - (total_changes() - $before_mark_changes) AS failed_mark_changes
;SELECT echoln(printf('%sERROR%s: failed to restore %d bookmarks',
$ansi_red, $ansi_norm, $failed_mark_changes))
WHERE $failed_mark_changes != 0
)";
static const char* FILTER_HEADER = R"(
# The following SQL statements will restore the filters that
# were added in the session.
)";
static const char* FILE_HEADER = R"(
# The following SQL statements will restore the state of the
# files in the session.
;SELECT total_changes() AS before_file_changes
)";
static constexpr const char FILE_FOOTER[] = R"(
;SELECT {} - (total_changes() - $before_file_changes) AS failed_file_changes
;SELECT echoln(printf('%sERROR%s: failed to restore the state of %d files',
$ansi_red, $ansi_norm, $failed_file_changes))
WHERE $failed_file_changes != 0
)";
static constexpr const char VIEW_HEADER[] = R"(
# The following commands will restore the state of the {} view.
)";
auto prep_mark_res = prepare_stmt(lnav_db.in(), BOOKMARK_QUERY);
if (prep_mark_res.isErr()) {
return Err(
console::user_message::error("unable to export log bookmarks")
.with_reason(prep_mark_res.unwrapErr()));
}
fmt::print(file, FMT_STRING("{}"), HEADER);
for (const auto& lf : lnav_data.ld_active_files.fc_files) {
fmt::print(file, FMT_STRING("# {}"), lf->get_filename());
}
auto mark_count = 0;
auto each_mark_res
= prep_mark_res.unwrap().for_each_row<log_message_session_state>(
[file, &mark_count](const log_message_session_state& lmss) {
if (mark_count == 0) {
fmt::print(file, FMT_STRING(MARK_HEADER));
}
mark_count += 1;
fmt::print(file,
FMT_STRING(";UPDATE all_logs "
"SET log_mark = {}, "
"log_comment = {}, "
"log_tags = {} "
"WHERE log_time_msecs = {} AND "
"log_format = {} AND "
"log_line_hash = {}\n"),
lmss.lmss_mark ? "1" : "0",
sqlitepp::quote(lmss.lmss_comment),
sqlitepp::quote(lmss.lmss_tags),
lmss.lmss_time_msecs,
sqlitepp::quote(lmss.lmss_format),
sqlitepp::quote(lmss.lmss_hash));
return false;
});
if (each_mark_res.isErr()) {
return Err(console::user_message::error(
"failed to fetch bookmark metadata for log message")
.with_reason(each_mark_res.unwrapErr().fe_msg));
}
if (mark_count > 0) {
fmt::print(file, FMT_STRING(MARK_FOOTER), mark_count);
}
auto prep_filter_res = prepare_stmt(lnav_db.in(), FILTER_QUERY);
if (prep_filter_res.isErr()) {
return Err(console::user_message::error("unable to export filter state")
.with_reason(prep_filter_res.unwrapErr()));
}
auto added_filter_header = false;
auto each_filter_res
= prep_filter_res.unwrap().for_each_row<log_filter_session_state>(
[file, &added_filter_header](const log_filter_session_state& lfss) {
if (!added_filter_header) {
fmt::print(file, FMT_STRING("{}"), FILTER_HEADER);
added_filter_header = true;
}
fmt::print(
file,
FMT_STRING(";REPLACE INTO lnav_view_filters "
"(view_name, enabled, type, language, pattern) "
"VALUES ({}, {}, {}, {}, {})\n"),
sqlitepp::quote(lfss.lfss_name),
lfss.lfss_enabled ? 1 : 0,
sqlitepp::quote(lfss.lfss_type),
sqlitepp::quote(lfss.lfss_language),
sqlitepp::quote(lfss.lfss_pattern));
return false;
});
if (each_filter_res.isErr()) {
return Err(console::user_message::error(
"failed to fetch filter state for views")
.with_reason(each_filter_res.unwrapErr().fe_msg));
}
auto prep_file_res = prepare_stmt(lnav_db.in(), FILE_QUERY);
if (prep_file_res.isErr()) {
return Err(console::user_message::error("unable to export file state")
.with_reason(prep_file_res.unwrapErr()));
}
auto file_count = 0;
auto file_stmt = prep_file_res.unwrap();
auto each_file_res = file_stmt.for_each_row<log_file_session_state>(
[file, &file_count](const log_file_session_state& lfss) {
if (file_count == 0) {
fmt::print(file, FMT_STRING("{}"), FILE_HEADER);
}
file_count += 1;
fmt::print(file,
FMT_STRING(";UPDATE lnav_file "
"SET time_offset = {} "
"WHERE content_id = {} AND format = {}\n"),
lfss.lfss_time_offset,
sqlitepp::quote(lfss.lfss_content_id),
sqlitepp::quote(lfss.lfss_format));
return false;
});
if (each_file_res.isErr()) {
return Err(console::user_message::error("failed to fetch file state")
.with_reason(each_file_res.unwrapErr().fe_msg));
}
if (file_count > 0) {
fmt::print(file, FMT_STRING(FILE_FOOTER), file_count);
}
for (auto view_index : {LNV_LOG, LNV_TEXT}) {
auto& tc = lnav_data.ld_views[view_index];
if (tc.get_inner_height() == 0_vl) {
continue;
}
fmt::print(file, FMT_STRING(VIEW_HEADER), lnav_view_titles[view_index]);
fmt::print(file,
FMT_STRING(":switch-to-view {}\n"),
lnav_view_strings[view_index]);
auto* tss = tc.get_sub_source();
auto* lss = dynamic_cast<logfile_sub_source*>(tss);
if (lss != nullptr) {
auto min_level = lss->get_min_log_level();
if (min_level != LEVEL_UNKNOWN) {
fmt::print(file,
FMT_STRING(":set-min-log-level {}\n"),
level_names[min_level]);
}
struct timeval min_time, max_time;
char tsbuf[128];
if (lss->get_min_log_time(min_time)) {
sql_strftime(tsbuf, sizeof(tsbuf), min_time, 'T');
fmt::print(file, FMT_STRING(":hide-lines-before {}\n"), tsbuf);
}
if (lss->get_max_log_time(max_time)) {
sql_strftime(tsbuf, sizeof(tsbuf), max_time, 'T');
fmt::print(file, FMT_STRING(":hide-lines-after {}\n"), tsbuf);
}
}
if (!tc.get_current_search().empty()) {
fmt::print(file, FMT_STRING("/{}\n"), tc.get_current_search());
}
fmt::print(file, FMT_STRING(":goto {}\n"), (int) tc.get_top());
}
return Ok();
}
} // namespace session
} // namespace lnav