summaryrefslogtreecommitdiffstats
path: root/src/log.watch.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/log.watch.cc388
1 files changed, 388 insertions, 0 deletions
diff --git a/src/log.watch.cc b/src/log.watch.cc
new file mode 100644
index 0000000..e90b2eb
--- /dev/null
+++ b/src/log.watch.cc
@@ -0,0 +1,388 @@
+/**
+ * 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 "log.watch.hh"
+
+#include <sqlite3.h>
+
+#include "base/injector.hh"
+#include "bound_tags.hh"
+#include "lnav.events.hh"
+#include "lnav_config_fwd.hh"
+#include "log_format.hh"
+#include "logfile_sub_source.cfg.hh"
+#include "readline_highlighters.hh"
+#include "sql_util.hh"
+
+namespace lnav {
+namespace log {
+namespace watch {
+
+struct compiled_watch_expr {
+ auto_mem<sqlite3_stmt> cwe_stmt{sqlite3_finalize};
+ bool cwe_enabled{true};
+};
+
+struct expressions : public lnav_config_listener {
+ void reload_config(error_reporter& reporter) override
+ {
+ auto& lnav_db = injector::get<auto_sqlite3&>();
+
+ if (lnav_db.in() == nullptr) {
+ log_warning("db not initialized yet!");
+ return;
+ }
+
+ const auto& cfg = injector::get<const logfile_sub_source_ns::config&>();
+
+ this->e_watch_exprs.clear();
+ for (const auto& pair : cfg.c_watch_exprs) {
+ auto stmt_str = fmt::format(FMT_STRING("SELECT 1 WHERE {}"),
+ pair.second.we_expr);
+ compiled_watch_expr cwe;
+
+ log_info("preparing watch expression: %s", stmt_str.c_str());
+ auto retcode = sqlite3_prepare_v2(lnav_db,
+ stmt_str.c_str(),
+ stmt_str.size(),
+ cwe.cwe_stmt.out(),
+ nullptr);
+ if (retcode != SQLITE_OK) {
+ auto sql_al = attr_line_t(pair.second.we_expr)
+ .with_attr_for_all(SA_PREFORMATTED.value())
+ .with_attr_for_all(
+ VC_ROLE.value(role_t::VCR_QUOTED_CODE));
+ readline_sqlite_highlighter(sql_al, -1);
+ intern_string_t watch_expr_path = intern_string::lookup(
+ fmt::format(FMT_STRING("/log/watch-expressions/{}/expr"),
+ pair.first));
+ auto snippet = lnav::console::snippet::from(
+ source_location(watch_expr_path), sql_al);
+
+ auto um = lnav::console::user_message::error(
+ "SQL expression is invalid")
+ .with_reason(sqlite3_errmsg(lnav_db))
+ .with_snippet(snippet);
+
+ reporter(&pair.second.we_expr, um);
+ continue;
+ }
+
+ this->e_watch_exprs.emplace(pair.first, std::move(cwe));
+ }
+ }
+
+ void unload_config() override {
+ this->e_watch_exprs.clear();
+ }
+
+ std::map<std::string, compiled_watch_expr> e_watch_exprs;
+};
+
+static expressions exprs;
+
+void
+eval_with(logfile& lf, logfile::iterator ll)
+{
+ if (std::none_of(exprs.e_watch_exprs.begin(),
+ exprs.e_watch_exprs.end(),
+ [](const auto& elem) { return elem.second.cwe_enabled; }))
+ {
+ return;
+ }
+
+ static auto& lnav_db = injector::get<auto_sqlite3&>();
+
+ char timestamp_buffer[64] = "";
+ shared_buffer_ref raw_sbr;
+ logline_value_vector values;
+ lf.read_full_message(ll, values.lvv_sbr);
+ values.lvv_sbr.erase_ansi();
+ auto format = lf.get_format();
+ string_attrs_t sa;
+ auto line_number = std::distance(lf.begin(), ll);
+ format->annotate(line_number, sa, values);
+
+ for (auto& watch_pair : exprs.e_watch_exprs) {
+ if (!watch_pair.second.cwe_enabled) {
+ continue;
+ }
+
+ auto* stmt = watch_pair.second.cwe_stmt.in();
+ sqlite3_reset(stmt);
+
+ auto count = sqlite3_bind_parameter_count(stmt);
+ auto missing_column = false;
+ for (int lpc = 0; lpc < count; lpc++) {
+ const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
+
+ if (name[0] == '$') {
+ const char* env_value;
+
+ if ((env_value = getenv(&name[1])) != nullptr) {
+ sqlite3_bind_text(
+ stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
+ }
+ continue;
+ }
+ if (strcmp(name, ":log_level") == 0) {
+ sqlite3_bind_text(
+ stmt, lpc + 1, ll->get_level_name(), -1, SQLITE_STATIC);
+ continue;
+ }
+ if (strcmp(name, ":log_time") == 0) {
+ auto len = sql_strftime(timestamp_buffer,
+ sizeof(timestamp_buffer),
+ ll->get_timeval(),
+ 'T');
+ sqlite3_bind_text(
+ stmt, lpc + 1, timestamp_buffer, len, SQLITE_STATIC);
+ continue;
+ }
+ if (strcmp(name, ":log_time_msecs") == 0) {
+ sqlite3_bind_int64(stmt, lpc + 1, ll->get_time_in_millis());
+ continue;
+ }
+ if (strcmp(name, ":log_format") == 0) {
+ const auto format_name = format->get_name();
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ format_name.get(),
+ format_name.size(),
+ SQLITE_STATIC);
+ continue;
+ }
+ if (strcmp(name, ":log_format_regex") == 0) {
+ const auto pat_name = format->get_pattern_name(line_number);
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ pat_name.get(),
+ pat_name.size(),
+ SQLITE_STATIC);
+ continue;
+ }
+ if (strcmp(name, ":log_path") == 0) {
+ const auto& filename = lf.get_filename();
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ filename.c_str(),
+ filename.length(),
+ SQLITE_STATIC);
+ continue;
+ }
+ if (strcmp(name, ":log_unique_path") == 0) {
+ const auto& filename = lf.get_unique_path();
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ filename.c_str(),
+ filename.length(),
+ SQLITE_STATIC);
+ continue;
+ }
+ if (strcmp(name, ":log_text") == 0) {
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ values.lvv_sbr.get_data(),
+ values.lvv_sbr.length(),
+ SQLITE_STATIC);
+ continue;
+ }
+ if (strcmp(name, ":log_body") == 0) {
+ auto body_attr_opt = get_string_attr(sa, SA_BODY);
+ if (body_attr_opt) {
+ const auto& sar
+ = body_attr_opt.value().saw_string_attr->sa_range;
+
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ values.lvv_sbr.get_data_at(sar.lr_start),
+ sar.length(),
+ SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(stmt, lpc + 1);
+ }
+ continue;
+ }
+ if (strcmp(name, ":log_opid") == 0) {
+ auto opid_attr_opt = get_string_attr(sa, logline::L_OPID);
+ if (opid_attr_opt) {
+ const auto& sar
+ = opid_attr_opt.value().saw_string_attr->sa_range;
+
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ values.lvv_sbr.get_data_at(sar.lr_start),
+ sar.length(),
+ SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(stmt, lpc + 1);
+ }
+ continue;
+ }
+ if (strcmp(name, ":log_raw_text") == 0) {
+ auto res = lf.read_raw_message(ll);
+
+ if (res.isOk()) {
+ raw_sbr = res.unwrap();
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ raw_sbr.get_data(),
+ raw_sbr.length(),
+ SQLITE_STATIC);
+ }
+ continue;
+ }
+ if (strcmp(name, ":log_tags") == 0) {
+ const auto& bm = lf.get_bookmark_metadata();
+ auto bm_iter = bm.find(line_number);
+ if (bm_iter != bm.end() && !bm_iter->second.bm_tags.empty()) {
+ const auto& meta = bm_iter->second;
+ yajlpp_gen gen;
+
+ yajl_gen_config(gen, yajl_gen_beautify, false);
+
+ {
+ yajlpp_array arr(gen);
+
+ for (const auto& str : meta.bm_tags) {
+ arr.gen(str);
+ }
+ }
+
+ string_fragment sf = gen.to_string_fragment();
+
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ sf.data(),
+ sf.length(),
+ SQLITE_TRANSIENT);
+ }
+ continue;
+ }
+ auto found = false;
+ for (const auto& lv : values.lvv_values) {
+ if (lv.lv_meta.lvm_name != &name[1]) {
+ continue;
+ }
+
+ found = true;
+ switch (lv.lv_meta.lvm_kind) {
+ case value_kind_t::VALUE_BOOLEAN:
+ sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
+ break;
+ case value_kind_t::VALUE_FLOAT:
+ sqlite3_bind_double(stmt, lpc + 1, lv.lv_value.d);
+ break;
+ case value_kind_t::VALUE_INTEGER:
+ sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
+ break;
+ case value_kind_t::VALUE_NULL:
+ sqlite3_bind_null(stmt, lpc + 1);
+ break;
+ default:
+ sqlite3_bind_text(stmt,
+ lpc + 1,
+ lv.text_value(),
+ lv.text_length(),
+ SQLITE_TRANSIENT);
+ break;
+ }
+ break;
+ }
+ if (!found) {
+ missing_column = true;
+ break;
+ }
+ }
+
+ if (missing_column) {
+ continue;
+ }
+
+ auto step_res = sqlite3_step(stmt);
+
+ switch (step_res) {
+ case SQLITE_OK:
+ case SQLITE_DONE:
+ continue;
+ case SQLITE_ROW:
+ break;
+ default: {
+ log_error("failed to execute watch expression: %s -- %s",
+ watch_pair.first.c_str(),
+ sqlite3_errmsg(lnav_db));
+ watch_pair.second.cwe_enabled = false;
+ continue;
+ }
+ }
+
+ if (!timestamp_buffer[0]) {
+ sql_strftime(timestamp_buffer,
+ sizeof(timestamp_buffer),
+ ll->get_timeval(),
+ 'T');
+ }
+ auto lmd = lnav::events::log::msg_detected{
+ watch_pair.first,
+ lf.get_filename(),
+ lf.get_format_name().to_string(),
+ (uint32_t) line_number,
+ timestamp_buffer,
+ };
+ for (const auto& lv : values.lvv_values) {
+ switch (lv.lv_meta.lvm_kind) {
+ case value_kind_t::VALUE_NULL:
+ lmd.md_values[lv.lv_meta.lvm_name.to_string()]
+ = json_null_t{};
+ break;
+ case value_kind_t::VALUE_BOOLEAN:
+ lmd.md_values[lv.lv_meta.lvm_name.to_string()]
+ = lv.lv_value.i ? true : false;
+ break;
+ case value_kind_t::VALUE_INTEGER:
+ lmd.md_values[lv.lv_meta.lvm_name.to_string()]
+ = lv.lv_value.i;
+ break;
+ case value_kind_t::VALUE_FLOAT:
+ lmd.md_values[lv.lv_meta.lvm_name.to_string()]
+ = lv.lv_value.d;
+ break;
+ default:
+ lmd.md_values[lv.lv_meta.lvm_name.to_string()]
+ = lv.to_string();
+ break;
+ }
+ }
+ lnav::events::publish(lnav_db, lmd);
+ }
+}
+
+} // namespace watch
+} // namespace log
+} // namespace lnav