[events] initial events work

Related to #811
pull/1006/head
Timothy Stack 2 years ago
parent 1ea385ea3b
commit 6c52760cc9

@ -19,6 +19,11 @@ lnav v0.10.2:
a regular expression entry on regex101.com and existing patterns
can be edited.
* Add initial support for pcap(3) files using tshark(1).
* To make it possible to automate some operations, there is now an
"lnav_events" table that is updated when internal events occur
within lnav (e.g. opening a file, format is detected). You
can then add SQLite TRIGGERs to this table that can perform a
task by updating other tables.
* Added a "top_meta" column to the lnav_views table that contains
metadata related to the top line in the view.
* Added a "log_opid" hidden column to all log tables that contains

@ -1,5 +1,6 @@
{
"$id": "https://lnav.org/schemas/config-v1.schema.json",
"title": "https://lnav.org/schemas/config-v1.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"$schema": {

@ -0,0 +1,26 @@
{
"$id": "https://lnav.org/event-file-format-detected-v1.schema.json",
"title": "https://lnav.org/event-file-format-detected-v1.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Event fired when a log format is detected for a file.",
"properties": {
"$schema": {
"title": "/$schema",
"type": "string",
"examples": [
"https://lnav.org/event-file-format-detected-v1.schema.json"
]
},
"filename": {
"title": "/filename",
"description": "The path of the file for which a matching format was found",
"type": "string"
},
"format": {
"title": "/format",
"description": "The name of the format",
"type": "string"
}
},
"additionalProperties": false
}

@ -0,0 +1,21 @@
{
"$id": "https://lnav.org/event-file-open-v1.schema.json",
"title": "https://lnav.org/event-file-open-v1.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Event fired when a file is opened.",
"properties": {
"$schema": {
"title": "/$schema",
"type": "string",
"examples": [
"https://lnav.org/event-file-open-v1.schema.json"
]
},
"filename": {
"title": "/filename",
"description": "The path of the file that was opened",
"type": "string"
}
},
"additionalProperties": false
}

@ -1,5 +1,6 @@
{
"$id": "https://lnav.org/schemas/format-v1.schema.json",
"title": "https://lnav.org/schemas/format-v1.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"$schema": {

@ -247,7 +247,7 @@ master_doc = 'index'
# General information about the project.
project = u'lnav'
copyright = u'2021, Tim Stack'
copyright = u'2022, Tim Stack'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

@ -0,0 +1,50 @@
.. _Events:
Events (v0.10.2+)
=================
The events mechanism allows **lnav** to be automated based on events that
occur during processing. For example, filters could be added only when a
particular log file format is detected instead of always installing them.
Events are published through the :ref:`lnav_events<table_lnav_events>` SQLite
table. Reacting to events can be done by creating a SQLite trigger on the
table and inspecting the content of the event.
Trigger Example
---------------
The following is an example of a trigger that adds an out filter when a
syslog file is loaded. You can copy the code into an :file:`.sql` file and
install it by running :code:`lnav -i my_trigger.sql`.
.. code-block:: sql
:caption: my_trigger.sql
:linenos:
CREATE TRIGGER IF NOT EXISTS add_format_specific_filters
AFTER INSERT ON lnav_events WHEN
-- Check the event type
jget(NEW.content, '/$schema') =
'https://lnav.org/event-file-format-detected-v1.schema.json' AND
-- Only create the filter when a given format is seen
jget(NEW.content, '/format') = 'syslog_log' AND
-- Don't create the filter if it's already there
NOT EXISTS (
SELECT 1 FROM lnav_view_filters WHERE pattern = 'noisy message')
BEGIN
INSERT INTO lnav_view_filters (view_name, enabled, type, pattern) VALUES
('log', 1, 'OUT', 'noisy message');
END;
.. _event_reference:
Reference
---------
The following tables describe the schema of the event JSON objects.
.. jsonschema:: ../schemas/event-file-open-v1.schema.json#
:lift_description:
.. jsonschema:: ../schemas/event-file-format-detected-v1.schema.json#
:lift_description:

@ -27,6 +27,7 @@ Contents:
commands
sqlext
sqltab
events
data
faq

@ -1,4 +1,3 @@
.. _sql-tab:
SQLite Tables Reference
@ -8,6 +7,7 @@ In addition to the tables generated for each log format, **lnav** includes
the following tables/views:
* `environ`_
* `lnav_events`_
* `lnav_file`_
* `lnav_views`_
* `lnav_view_stack`_
@ -51,6 +51,22 @@ named "FILENAME" and then open it in **lnav** by referencing it with
:open $FILENAME
.. _table_lnav_events:
lnav_events
-----------
The **lnav_events** table allows you to react to events that occur while
**lnav** is running using SQLite triggers. For example, when a file is
opened, a row is inserted into the :code:`lnav_events` table that contains
a timestamp and a JSON object with the event ID and the path of the file.
The following columns are available in this table:
:ts: The timestamp of the event.
:content: A JSON object that contains the event information. See the
:ref:`event_reference` for more information about the types
of events that are available.
lnav_file
---------

@ -282,6 +282,7 @@ add_library(
input_dispatcher.cc
json-extension-functions.cc
listview_curses.cc
lnav.events.cc
lnav.indexing.cc
lnav.management_cli.cc
lnav_commands.cc
@ -385,6 +386,7 @@ add_library(
input_dispatcher.hh
itertools.similar.hh
k_merge_tree.h
lnav.events.hh
lnav.indexing.hh
lnav.management_cli.hh
lnav_config.hh

@ -222,6 +222,7 @@ noinst_HEADERS = \
line_buffer.hh \
listview_curses.hh \
lnav.hh \
lnav.events.hh \
lnav.indexing.hh \
lnav.management_cli.hh \
lnav_commands.hh \
@ -464,12 +465,14 @@ lnav.$(OBJEXT): help-txt.h init-sql.h
lnav_SOURCES = \
lnav.cc \
lnav.events.cc \
lnav.indexing.cc \
lnav.management_cli.cc \
$(PLUGIN_SRCS)
lnav_test_SOURCES = \
lnav.cc \
lnav.events.cc \
lnav.indexing.cc \
lnav.management_cli.cc \
test_override.c \

@ -52,32 +52,32 @@ template<class T, free_func_t default_free = free>
class auto_mem {
public:
explicit auto_mem(T* ptr = nullptr)
: am_ptr(ptr), am_free_func(default_free){};
: am_ptr(ptr), am_free_func(default_free)
{
}
auto_mem(const auto_mem& am) = delete;
template<typename F>
explicit auto_mem(F free_func) noexcept
: am_ptr(nullptr), am_free_func((free_func_t) free_func){};
: am_ptr(nullptr), am_free_func((free_func_t) free_func)
{
}
auto_mem(auto_mem&& other) noexcept
: am_ptr(other.release()), am_free_func(other.am_free_func){};
~auto_mem()
: am_ptr(other.release()), am_free_func(other.am_free_func)
{
this->reset();
};
}
operator T*() const
{
return this->am_ptr;
};
~auto_mem() { this->reset(); }
operator T*() const { return this->am_ptr; }
auto_mem& operator=(T* ptr)
{
this->reset(ptr);
return *this;
};
}
auto_mem& operator=(auto_mem&) = delete;
@ -86,7 +86,7 @@ public:
this->reset(am.release());
this->am_free_func = am.am_free_func;
return *this;
};
}
T* release()
{
@ -94,18 +94,15 @@ public:
this->am_ptr = nullptr;
return retval;
};
}
T* in() const
{
return this->am_ptr;
};
T* in() const { return this->am_ptr; }
T** out()
{
this->reset();
return &this->am_ptr;
};
}
void reset(T* ptr = nullptr)
{
@ -115,7 +112,7 @@ public:
}
this->am_ptr = ptr;
}
};
}
private:
T* am_ptr;
@ -125,43 +122,25 @@ private:
template<typename T, void (*free_func)(T*)>
class static_root_mem {
public:
static_root_mem()
{
memset(&this->srm_value, 0, sizeof(T));
};
static_root_mem() { memset(&this->srm_value, 0, sizeof(T)); }
~static_root_mem()
{
free_func(&this->srm_value);
};
~static_root_mem() { free_func(&this->srm_value); }
const T* operator->() const
{
return &this->srm_value;
};
const T* operator->() const { return &this->srm_value; }
const T& in() const
{
return this->srm_value;
};
const T& in() const { return this->srm_value; }
T* inout()
{
free_func(&this->srm_value);
memset(&this->srm_value, 0, sizeof(T));
return &this->srm_value;
};
}
private:
static_root_mem& operator=(T&)
{
return *this;
};
static_root_mem& operator=(T&) { return *this; }
static_root_mem& operator=(static_root_mem&)
{
return *this;
};
static_root_mem& operator=(static_root_mem&) { return *this; }
T srm_value;
};
@ -187,10 +166,7 @@ public:
this->ab_size = 0;
}
char* in()
{
return this->ab_buffer;
}
char* in() { return this->ab_buffer; }
std::pair<char*, size_t> release()
{
@ -201,10 +177,7 @@ public:
return retval;
}
size_t size() const
{
return this->ab_size;
}
size_t size() const { return this->ab_size; }
void expand_by(size_t amount)
{

@ -29,6 +29,7 @@
#include "dump_internals.hh"
#include "lnav.events.hh"
#include "lnav.hh"
#include "lnav_config.hh"
#include "log_format_loader.hh"
@ -41,9 +42,16 @@ namespace lnav {
void
dump_internals(const char* internals_dir)
{
dump_schema_to(
lnav_config_handlers, internals_dir, "config-v1.schema.json");
dump_schema_to(root_format_handler, internals_dir, "format-v1.schema.json");
for (const auto* handlers :
std::initializer_list<const json_path_container*>{
&lnav_config_handlers,
&root_format_handler,
&lnav::events::file::open::handlers,
&lnav::events::file::format_detected::handlers,
})
{
dump_schema_to(*handlers, internals_dir);
}
execute_examples();

@ -203,7 +203,7 @@ struct same_file {
{
return !lf->is_closed() && this->sf_stat.st_dev == lf->get_stat().st_dev
&& this->sf_stat.st_ino == lf->get_stat().st_ino;
};
}
const struct stat& sf_stat;
};

@ -1,7 +1,8 @@
{
"$schema": "https://lnav.org/schemas/format-v1.schema.json",
"esx_syslog_log": {
"description": "Format file generated from regex101 entry -- https://regex101.com/r/wHXtl4/1",
"title": "ESXi Syslog",
"description": "Format specific to the ESXi syslog",
"regex": {
"std": {
"pattern": "^(?<timestamp>(?:\\S{3,8}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2}|\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3})?Z))\\s+(?<level>\\w+)\\((?<syslog_pri>\\d+)\\)(?:\\[\\+\\]|\\+)?(?:(?: (?<log_syslog_tag>(?<log_procname>(?:[^\\[:]+|[^:]+))(?:\\[(?<log_pid>\\d+)\\])?):\\s*(?<body>.*))$|:?(?:(?: ---)? last message repeated \\d+ times?(?: ---)?))"

@ -99,6 +99,7 @@
#include "hist_source.hh"
#include "init-sql.h"
#include "listview_curses.hh"
#include "lnav.events.hh"
#include "lnav.hh"
#include "lnav.indexing.hh"
#include "lnav.management_cli.hh"
@ -2118,6 +2119,40 @@ main(int argc, char* argv[])
continue;
}
if (endswith(file_path, ".sql")) {
auto sql_path = ghc::filesystem::path(file_path);
auto read_res = lnav::filesystem::read_file(sql_path);
if (read_res.isErr()) {
lnav::console::print(
stderr,
lnav::console::user_message::error(
attr_line_t("unable to read SQL file: ")
.append(lnav::roles::file(file_path)))
.with_reason(read_res.unwrapErr()));
return EXIT_FAILURE;
}
auto dst_path = formats_installed_path / sql_path.filename();
auto write_res
= lnav::filesystem::write_file(dst_path, read_res.unwrap());
if (write_res.isErr()) {
lnav::console::print(
stderr,
lnav::console::user_message::error(
attr_line_t("unable to write SQL file: ")
.append(lnav::roles::file(file_path)))
.with_reason(write_res.unwrapErr()));
return EXIT_FAILURE;
}
lnav::console::print(
stderr,
lnav::console::user_message::ok(
attr_line_t("installed -- ")
.append(lnav::roles::file(dst_path))));
continue;
}
if (file_path == "extra") {
install_extra_formats();
continue;
@ -2249,6 +2284,7 @@ main(int argc, char* argv[])
register_regexp_vtab(lnav_data.ld_db.in());
register_xpath_vtab(lnav_data.ld_db.in());
register_fstat_vtab(lnav_data.ld_db.in());
lnav::events::register_events_tab(lnav_data.ld_db.in());
lnav_data.ld_vtab_manager = std::make_unique<log_vtab_manager>(
lnav_data.ld_db, lnav_data.ld_views[LNV_LOG], lnav_data.ld_log_source);

@ -0,0 +1,128 @@
/**
* 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 "lnav.events.hh"
namespace lnav {
namespace events {
namespace file {
const std::string open::SCHEMA_ID
= "https://lnav.org/event-file-open-v1.schema.json";
const auto open::handlers = typed_json_path_container<open>{
yajlpp::property_handler("$schema").for_field(&open::o_schema)
.with_example(open::SCHEMA_ID),
yajlpp::property_handler("filename")
.with_description("The path of the file that was opened")
.for_field(&open::o_filename),
}
.with_schema_id(open::SCHEMA_ID)
.with_description("Event fired when a file is opened.");
const std::string format_detected::SCHEMA_ID
= "https://lnav.org/event-file-format-detected-v1.schema.json";
const auto format_detected::handlers = typed_json_path_container<format_detected>{
yajlpp::property_handler("$schema").for_field(&format_detected::fd_schema)
.with_example(format_detected::SCHEMA_ID),
yajlpp::property_handler("filename")
.with_description("The path of the file for which a matching format was found")
.for_field(&format_detected::fd_filename),
yajlpp::property_handler("format")
.with_description("The name of the format")
.for_field(&format_detected::fd_format),
}
.with_schema_id(format_detected::SCHEMA_ID)
.with_description("Event fired when a log format is detected for a file.");
} // namespace file
int
register_events_tab(sqlite3* db)
{
static const char* CREATE_EVENTS_TAB_SQL = R"(
CREATE TABLE lnav_events (
ts TEXT NOT NULL DEFAULT(strftime('%Y-%m-%dT%H:%M:%f', 'now')),
content TEXT
)
)";
static const char* DELETE_EVENTS_TRIGGER_SQL = R"(
CREATE TRIGGER lnav_events_cleaner AFTER INSERT ON lnav_events
BEGIN
DELETE FROM lnav_events WHERE rowid <= NEW.rowid - 1000;
END
)";
auto_mem<char> errmsg(sqlite3_free);
if (sqlite3_exec(db, CREATE_EVENTS_TAB_SQL, nullptr, nullptr, errmsg.out())
!= SQLITE_OK)
{
log_error("Unable to create events table: %s", errmsg.in());
}
if (sqlite3_exec(
db, DELETE_EVENTS_TRIGGER_SQL, nullptr, nullptr, errmsg.out())
!= SQLITE_OK)
{
log_error("Unable to create event cleaner trigger: %s", errmsg.in());
}
return 0;
}
void
details::publish(sqlite3* db, const std::string& content)
{
static const char* INSERT_SQL = R"(
INSERT INTO lnav_events (content) VALUES (?)
)";
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
if (sqlite3_prepare_v2(db, INSERT_SQL, -1, stmt.out(), nullptr)
!= SQLITE_OK) {
log_error("unable to prepare statement: %s", sqlite3_errmsg(db));
return;
}
if (sqlite3_bind_text(
stmt.in(), 1, content.c_str(), content.size(), SQLITE_TRANSIENT)
!= SQLITE_OK)
{
log_error("unable to bind parameter: %s", sqlite3_errmsg(db));
return;
}
if (sqlite3_step(stmt) != SQLITE_DONE) {
log_error("failed to execute insert: %s", sqlite3_errmsg(db));
}
}
} // namespace events
} // namespace lnav

@ -0,0 +1,100 @@
/**
* 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.
*/
#ifndef lnav_events_hh
#define lnav_events_hh
#include <sqlite3.h>
#include "yajlpp/yajlpp_def.hh"
namespace lnav {
namespace events {
namespace file {
struct open {
std::string o_filename;
std::string o_schema{SCHEMA_ID};
static const std::string SCHEMA_ID;
static const typed_json_path_container<open> handlers;
};
struct format_detected {
std::string fd_filename;
std::string fd_format;
std::string fd_schema{SCHEMA_ID};
static const std::string SCHEMA_ID;
static const typed_json_path_container<format_detected> handlers;
};
} // namespace file
int register_events_tab(sqlite3* db);
namespace details {
void publish(sqlite3* db, const std::string& content);
} // namespace details
template<typename T>
void
publish(sqlite3* db, T event)
{
auto serialized = T::handlers.to_string(event);
details::publish(db, serialized);
}
template<typename T, typename F>
void
publish(sqlite3* db, const T& container, F func)
{
auto_mem<char> errmsg(sqlite3_free);
if (sqlite3_exec(db, "BEGIN TRANSACTION", nullptr, nullptr, errmsg.out())
!= SQLITE_OK)
{
log_error("unable to start event transaction: %s", errmsg.in());
}
for (const auto& elem : container) {
publish(db, func(elem));
}
if (sqlite3_exec(db, "COMMIT TRANSACTION", nullptr, nullptr, errmsg.out())
!= SQLITE_OK)
{
log_error("unable to commit event transaction: %s", errmsg.in());
}
}
} // namespace events
} // namespace lnav
#endif

@ -29,6 +29,7 @@
#include "lnav.indexing.hh"
#include "lnav.events.hh"
#include "lnav.hh"
#include "service_tags.hh"
#include "session_data.hh"
@ -146,6 +147,12 @@ public:
ld->set_visibility(iter->second.fs_is_visible);
};
}
lnav::events::publish(lnav_data.ld_db.in(),
lnav::events::file::format_detected{
lf->get_filename(),
lf->get_format_name().to_string(),
});
} else {
this->closed_files({lf});
}
@ -186,7 +193,7 @@ rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
}
{
textfile_sub_source* tss = &lnav_data.ld_text_source;
auto* tss = &lnav_data.ld_text_source;
textfile_callback cb;
if (tss->rescan_files(cb, deadline)) {
@ -348,6 +355,13 @@ update_active_files(file_collection& new_files)
std::make_move_iterator(
lnav_data.ld_active_files.fc_child_pollers.end()));
lnav::events::publish(
lnav_data.ld_db.in(), new_files.fc_files, [](const auto& lf) {
return lnav::events::file::open{
lf->get_filename(),
};
});
return true;
}

@ -226,10 +226,10 @@ struct subcmd_format_t {
auto um = console::user_message::error(
attr_line_t("expecting an operation to perform on the ")
.append(lnav::roles::symbol(sf.sf_regex_name))
.append(" regular expression using regex101.com"));
.append(" regular expression"));
um.with_help(attr_line_t{"the available subcommands are:"}.append(
sf.sf_regex101_app->get_subcommands({})
sf.sf_regex_app->get_subcommands({})
| lnav::itertools::fold(subcmd_reducer, attr_line_t{})));
return {um};
@ -446,7 +446,7 @@ struct subcmd_format_t {
auto um = console::user_message::error(
attr_line_t("expecting an operation to perform on the ")
.append(lnav::roles::symbol(sf.sf_regex_name))
.append(" regex"));
.append(" regex using regex101.com"));
auto get_res
= lnav::session::regex101::get_entry(sf.sf_name, sf.sf_regex_name);
@ -460,7 +460,7 @@ struct subcmd_format_t {
}
um.with_help(attr_line_t{"the available subcommands are:"}.append(
sf.sf_regex_app->get_subcommands({})
sf.sf_regex101_app->get_subcommands({})
| lnav::itertools::fold(subcmd_reducer, attr_line_t{})));
return {um};

@ -34,8 +34,10 @@
#include "yajlpp.hh"
#include "base/fs_util.hh"
#include "config.h"
#include "fmt/format.h"
#include "ghc/filesystem.hpp"
#include "yajl/api/yajl_parse.h"
#include "yajlpp_def.hh"
@ -1036,9 +1038,8 @@ yajlpp_parse_context::get_line_number() const
&this->ypc_json_text[0], &this->ypc_json_text[consumed], '\n');
return this->ypc_line_number + current_count;
} else {
return this->ypc_line_number;
}
return this->ypc_line_number;
}
void
@ -1064,9 +1065,15 @@ yajlpp_gen_context::gen_schema(const json_path_container* handlers)
if (!handlers->jpc_schema_id.empty()) {
schema.gen("$id");
schema.gen(handlers->jpc_schema_id);
schema.gen("title");
schema.gen(handlers->jpc_schema_id);
}
schema.gen("$schema");
schema.gen("http://json-schema.org/draft-07/schema#");
if (!handlers->jpc_description.empty()) {
schema.gen("description");
schema.gen(handlers->jpc_description);
}
handlers->gen_schema(*this);
if (!this->ygc_schema_definitions.empty()) {
@ -1355,13 +1362,13 @@ schema_printer(FILE* file, const char* str, size_t len)
}
void
dump_schema_to(const json_path_container& jpc,
const char* internals_dir,
const char* name)
dump_schema_to(const json_path_container& jpc, const char* internals_dir)
{
yajlpp_gen genner;
yajlpp_gen_context ygc(genner, jpc);
auto schema_path = fmt::format(FMT_STRING("{}/{}"), internals_dir, name);
auto internals_dir_path = ghc::filesystem::path(internals_dir);
auto schema_file_name = ghc::filesystem::path(jpc.jpc_schema_id).filename();
auto schema_path = internals_dir_path / schema_file_name;
auto file = std::unique_ptr<FILE, decltype(&fclose)>(
fopen(schema_path.c_str(), "w+"), fclose);

@ -622,8 +622,6 @@ struct json_string {
size_t js_len{0};
};
void dump_schema_to(const json_path_container& jpc,
const char* internals_dir,
const char* name);
void dump_schema_to(const json_path_container& jpc, const char* internals_dir);
#endif

@ -1299,6 +1299,18 @@ struct typed_json_path_container : public json_path_container {
{
}
typed_json_path_container<T>& with_schema_id(const std::string& id)
{
this->jpc_schema_id = id;
return *this;
}
typed_json_path_container<T>& with_description(std::string desc)
{
this->jpc_description = std::move(desc);
return *this;
}
yajlpp_parser<T> parser_for(intern_string_t src) const
{
return yajlpp_parser<T>{src, this};

@ -178,6 +178,7 @@ dist_noinst_SCRIPTS = \
test_config.sh \
test_curl.sh \
test_data_parser.sh \
test_events.sh \
test_format_installer.sh \
test_format_loader.sh \
test_grep_proc.sh \
@ -371,6 +372,7 @@ TESTS = \
test_cli.sh \
test_cmds.sh \
test_config.sh \
test_events.sh \
test_listview.sh \
test_meta.sh \
test_mvwattrline.sh \

@ -212,6 +212,8 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_config.sh_d708b6fd32d83ce0ee00ca5383388308ba5a06e1.out \
$(srcdir)/%reldir%/test_config.sh_eec3768ebc201ca63bca1411270965f78db1abfc.err \
$(srcdir)/%reldir%/test_config.sh_eec3768ebc201ca63bca1411270965f78db1abfc.out \
$(srcdir)/%reldir%/test_events.sh_3dae146ef3bf201c43656344803694a34a3dbfec.err \
$(srcdir)/%reldir%/test_events.sh_3dae146ef3bf201c43656344803694a34a3dbfec.out \
$(srcdir)/%reldir%/test_format_loader.sh_15e861d2327512a721fd42ae51dc5427689e0bb6.err \
$(srcdir)/%reldir%/test_format_loader.sh_15e861d2327512a721fd42ae51dc5427689e0bb6.out \
$(srcdir)/%reldir%/test_format_loader.sh_3f1d6f35e8a9ae4fd3e91ffaa82a037b5a847ab7.err \
@ -308,6 +310,58 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_pretty_print.sh_a6d9042e5e95f2a49194bd80c1eed154813ddf41.out \
$(srcdir)/%reldir%/test_pretty_print.sh_cd361eeca7e91bfab942b75d6c3422c7a456a111.err \
$(srcdir)/%reldir%/test_pretty_print.sh_cd361eeca7e91bfab942b75d6c3422c7a456a111.out \
$(srcdir)/%reldir%/test_regex101.sh_0fa3663a45aca6a328cb728872af7ed7ee896f1c.err \
$(srcdir)/%reldir%/test_regex101.sh_0fa3663a45aca6a328cb728872af7ed7ee896f1c.out \
$(srcdir)/%reldir%/test_regex101.sh_182ae9244db314a953af2bee969726e381bc5a32.err \
$(srcdir)/%reldir%/test_regex101.sh_182ae9244db314a953af2bee969726e381bc5a32.out \
$(srcdir)/%reldir%/test_regex101.sh_2158f1f011ba8e1b152396c072790c076fdb8ce8.err \
$(srcdir)/%reldir%/test_regex101.sh_2158f1f011ba8e1b152396c072790c076fdb8ce8.out \
$(srcdir)/%reldir%/test_regex101.sh_281af24141680330791db7f7c5fa70833ce08a6b.err \
$(srcdir)/%reldir%/test_regex101.sh_281af24141680330791db7f7c5fa70833ce08a6b.out \
$(srcdir)/%reldir%/test_regex101.sh_35703b13990785632cca82123fb3883797959c0b.err \
$(srcdir)/%reldir%/test_regex101.sh_35703b13990785632cca82123fb3883797959c0b.out \
$(srcdir)/%reldir%/test_regex101.sh_366730cac50b4a09b7de4b84641791470b1cb9a3.err \
$(srcdir)/%reldir%/test_regex101.sh_366730cac50b4a09b7de4b84641791470b1cb9a3.out \
$(srcdir)/%reldir%/test_regex101.sh_3d18474a3e472fff6e23e0c41337ec9188fee591.err \
$(srcdir)/%reldir%/test_regex101.sh_3d18474a3e472fff6e23e0c41337ec9188fee591.out \
$(srcdir)/%reldir%/test_regex101.sh_442cc58676590a3604d5c2183f5fe0a75c98351a.err \
$(srcdir)/%reldir%/test_regex101.sh_442cc58676590a3604d5c2183f5fe0a75c98351a.out \
$(srcdir)/%reldir%/test_regex101.sh_566fd88d216a44bc1c6e23f2d6f2d0caf99d42f9.err \
$(srcdir)/%reldir%/test_regex101.sh_566fd88d216a44bc1c6e23f2d6f2d0caf99d42f9.out \
$(srcdir)/%reldir%/test_regex101.sh_5f2f7ecb6ab9cbec4b41385b91bd038906b8a7b2.err \
$(srcdir)/%reldir%/test_regex101.sh_5f2f7ecb6ab9cbec4b41385b91bd038906b8a7b2.out \
$(srcdir)/%reldir%/test_regex101.sh_629bde30483e0a6461076e9058f3a5eb81ae0425.err \
$(srcdir)/%reldir%/test_regex101.sh_629bde30483e0a6461076e9058f3a5eb81ae0425.out \
$(srcdir)/%reldir%/test_regex101.sh_630db454054cf92ec9bd0f4e3e83300047f583ff.err \
$(srcdir)/%reldir%/test_regex101.sh_630db454054cf92ec9bd0f4e3e83300047f583ff.out \
$(srcdir)/%reldir%/test_regex101.sh_771af6f3d29b8350542d5c6e98bdbf4c223cd531.err \
$(srcdir)/%reldir%/test_regex101.sh_771af6f3d29b8350542d5c6e98bdbf4c223cd531.out \
$(srcdir)/%reldir%/test_regex101.sh_7991a5b617867cf37c9f7baa85ffa425f7d455a2.err \
$(srcdir)/%reldir%/test_regex101.sh_7991a5b617867cf37c9f7baa85ffa425f7d455a2.out \
$(srcdir)/%reldir%/test_regex101.sh_79ee3f5fe71ccec97b2619d8c1f74ca97ffd2243.err \
$(srcdir)/%reldir%/test_regex101.sh_79ee3f5fe71ccec97b2619d8c1f74ca97ffd2243.out \
$(srcdir)/%reldir%/test_regex101.sh_7de76c174c58d67bf93e8f01d6d55ebb6a023f10.err \
$(srcdir)/%reldir%/test_regex101.sh_7de76c174c58d67bf93e8f01d6d55ebb6a023f10.out \
$(srcdir)/%reldir%/test_regex101.sh_8a43e6657d4f60e68d31eb8302542ca28e80d077.err \
$(srcdir)/%reldir%/test_regex101.sh_8a43e6657d4f60e68d31eb8302542ca28e80d077.out \
$(srcdir)/%reldir%/test_regex101.sh_8e93a3b6b941847c71409a297779fbb0a6666a51.err \
$(srcdir)/%reldir%/test_regex101.sh_8e93a3b6b941847c71409a297779fbb0a6666a51.out \
$(srcdir)/%reldir%/test_regex101.sh_95c56a9d146ec9a7c2196559d316f928b2ae6ae9.err \
$(srcdir)/%reldir%/test_regex101.sh_95c56a9d146ec9a7c2196559d316f928b2ae6ae9.out \
$(srcdir)/%reldir%/test_regex101.sh_9d101ee29c45cdb8c0f117ad736c9a5dd5da5839.err \
$(srcdir)/%reldir%/test_regex101.sh_9d101ee29c45cdb8c0f117ad736c9a5dd5da5839.out \
$(srcdir)/%reldir%/test_regex101.sh_c43e07df9b3068696fdc8759c7561135db981b38.err \
$(srcdir)/%reldir%/test_regex101.sh_c43e07df9b3068696fdc8759c7561135db981b38.out \
$(srcdir)/%reldir%/test_regex101.sh_cbd859487e4ea011cd6e0f0f114d70158bfd8b43.err \
$(srcdir)/%reldir%/test_regex101.sh_cbd859487e4ea011cd6e0f0f114d70158bfd8b43.out \
$(srcdir)/%reldir%/test_regex101.sh_cf6c0a9f0f04e24ce1fae7a0a434830b14447f83.err \
$(srcdir)/%reldir%/test_regex101.sh_cf6c0a9f0f04e24ce1fae7a0a434830b14447f83.out \
$(srcdir)/%reldir%/test_regex101.sh_d84597760285c3964b258726341e018f6cd49954.err \
$(srcdir)/%reldir%/test_regex101.sh_d84597760285c3964b258726341e018f6cd49954.out \
$(srcdir)/%reldir%/test_regex101.sh_f23e393dbf23d0d8e276e9b7610c7b74d79980f8.err \
$(srcdir)/%reldir%/test_regex101.sh_f23e393dbf23d0d8e276e9b7610c7b74d79980f8.out \
$(srcdir)/%reldir%/test_regex101.sh_fc41b6ee90cbf038620151f16d164b361acf82dd.err \
$(srcdir)/%reldir%/test_regex101.sh_fc41b6ee90cbf038620151f16d164b361acf82dd.out \
$(srcdir)/%reldir%/test_sessions.sh_0300a1391c33b1c45ddfa90198a6bd0a5404a77f.err \
$(srcdir)/%reldir%/test_sessions.sh_0300a1391c33b1c45ddfa90198a6bd0a5404a77f.out \
$(srcdir)/%reldir%/test_sessions.sh_17b85654b929b2a8fc1705a170ced544783292fa.err \

@ -0,0 +1,2 @@
{"content":{"$schema":"https://lnav.org/event-file-open-v1.schema.json","filename":"{test_dir}/logfile_access_log.0"}}
{"content":{"$schema":"https://lnav.org/event-file-format-detected-v1.schema.json","filename":"{test_dir}/logfile_access_log.0","format":"access_log"}}

@ -0,0 +1,2 @@
✘ error: regex “std” of format “syslog_log” has not been pushed to regex101.com
 = help: use the “push” subcommand to create the regex on regex101.com for easy editing

@ -0,0 +1,3 @@
✘ error: unable to import: https://regex101.com/r/zpEnjV/1
reason: format file already exists: regex101-home/.lnav/formats/installed/unit_test_log.json
 = help: delete the existing file to continue

@ -0,0 +1,3 @@
⚠ warning: not deleting regex101 entry “zpEnjV”
reason: delete code is not known for this entry
 = note: formats created by importing a regex101.com entry will not have a delete code

@ -0,0 +1 @@
✔ deleted regex101 entry: zpEnjV

@ -0,0 +1 @@
✘ error: expecting a regex101.com URL to import

@ -0,0 +1,4 @@
✔ converted regex101 entry to format file: regex101-home/.lnav/formats/installed/unit_test_log.json
 = note: the converted format may still have errors
 = help: use the following command to patch the regex as more changes are made on regex101.com:
lnav -m format unit_test_log regex std regex101 pull

@ -0,0 +1,10 @@
{
"$schema": "https://lnav.org/schemas/format-v1.schema.json",
"unit_test_log": {
"regex": {
"std": {
"pattern": "\\[(?<timestamp>\\d+\\/\\d+\\/\\d+ \\d+:\\d+:\\d+) (?<jobserver>[\\w.]+) (?<workqueue>[\\w.]+) (?<processid>\\d+)\\] (?<body>.*)$"
}
}
}
}

@ -0,0 +1,14 @@
✘ error: invalid sample log message: "[03/22/2021 02:00:02 job1074.example.com db.db81.example_events 54026] {\"ELAPSED\":\"0.011\",\"LEVEL\":\"info\",\"MESSAGE\":\"finished in 0.011\\n\",\"PREFIX\":\"YFgyWQriCmsAAofJAAAAHg\",\"ROUTINGKEY\":\"EXAMPLE1366.Example.Events._Publish\"}"
reason: sample does not match any patterns
 --> regex101-home/.lnav/formats/installed/unit_test_log.json:26
 = note: the following shows how each pattern matched this sample:
[03/22/2021 02:00:02 job1074.example.com db.db81.example_events 54026] {"ELAPSED":"0.011","LEVEL":"info","MESSAGE":"finished in 0.011\n","PREFIX":"YFgyWQriCmsAAofJAAAAHg","ROUTINGKEY":"EXAMPLE1366.Example.Events._Publish"}
 = note: std = “”
✘ error: invalid sample log message: "[03/22/2021 02:00:02 job1074.example.com db.db81.example_events 54026] {\"ELAPSED\":\"0.011\",\"LEVEL\":\"info\",\"MESSAGE\":\"finished in 0.011\\n\",\"PREFIX\":\"YFgyWQriCmsAAofJAAAAHg\",\"ROUTINGKEY\":\"EXAMPLE1366.Example.Events._Publish\"}"
reason: sample does not match any patterns
 --> regex101-home/.lnav/formats/installed/unit_test_log.json:30
 = note: the following shows how each pattern matched this sample:
[03/22/2021 02:00:02 job1074.example.com db.db81.example_events 54026] {"ELAPSED":"0.011","LEVEL":"info","MESSAGE":"finished in 0.011\n","PREFIX":"YFgyWQriCmsAAofJAAAAHg","ROUTINGKEY":"EXAMPLE1366.Example.Events._Publish"}
 = note: std = “”

@ -0,0 +1,3 @@
✔ format patch file written to: regex101-home/.lnav/formats/installed/unit_test_log.regex101-zpEnjV.json
 = help: once the regex has been found to be working correctly, move the contents of the patch file to the original file at:
regex101-home/.lnav/formats/installed/unit_test_log.json

@ -0,0 +1,2 @@
regex101-home/.lnav/formats/installed/unit_test_log.json
regex101-home/.lnav/formats/installed/unit_test_log.regex101-zpEnjV.json

@ -0,0 +1,3 @@
✘ error: cannot delete regex101 entry while patch file exists
 = note: regex101-home/.lnav/formats/installed/unit_test_log.regex101-zpEnjV.json
 = help: move the contents of the patch file to the main log format and then delete the file to continue

@ -0,0 +1,2 @@
✘ error: invalid regex “abc(def)” from “https://regex101.com/r/cvCJNP/1”
reason: only the “pcre” flavor of regexes are supported

@ -0,0 +1,4 @@
✔ converted regex101 entry to format file: regex101-home/.lnav/formats/installed/unit_test_log.regex101-hGiqBL.json
 = note: the converted format may still have errors
 = help: use the following command to patch the regex as more changes are made on regex101.com:
lnav -m format unit_test_log regex alt regex101 pull

@ -0,0 +1 @@
✘ error: unknown regex: non-existent

@ -0,0 +1,5 @@
✘ error: expecting an operation to perform on the std regex using regex101.com
 = help: the available subcommands are:
push: create/update an entry for this regex on regex101.com
pull: create a patch format file for this regular expression based on the entry in regex101.com
delete: delete the entry regex101.com that was created by a push operation

@ -0,0 +1,2 @@
✔ local regex is in sync with entry “zpEnjV” on regex101.com
 = help: make edits on “https://regex101.com/r/zpEnjV” and then run this command again to update the local values

@ -0,0 +1,3 @@
✘ error: unknown regex: s
 = note: did you mean one of the following?
std

@ -0,0 +1,3 @@
✔ the following regex101 entries were found:
format unit_test_log regex std regex101

@ -0,0 +1,3 @@
✘ error: expecting an operation to perform on the std regular expression
 = help: the available subcommands are:
regex101: use regex101.com to edit this regular expression

@ -0,0 +1,4 @@
✘ error: unable to import: abc
reason: expecting a format name that matches the regular expression “^\w+$”
 = note: “def-jkl”
^ matched up to here

@ -0,0 +1 @@
✘ error: no regex101 entry for syslog_log/std

@ -0,0 +1,2 @@
✘ error: unable to get entry “badregex123” on regex101.com
reason: received response code 400 content “{"error":"Invalid permalink id",}”

@ -0,0 +1,34 @@
{
"$schema": "https://lnav.org/schemas/format-v1.schema.json",
"unit_test_log": {
"description": "Format file generated from regex101 entry -- https://regex101.com/r/zpEnjV/2",
"regex": {
"std": {
"pattern": "\\[(?<timestamp>\\d+\\/\\d+\\/\\d+ \\d+:\\d+:\\d+) (?<jobserver>[\\w.]+) (?<workqueue>[\\w.]+) (?<processid>\\d+)\\] (?<body>.*)$"
}
},
"value": {
"jobserver": {
"kind": "string"
},
"processid": {
"kind": "string"
},
"timestamp": {
"kind": "string"
},
"workqueue": {
"kind": "string"
}
},
"sample": [
{
"line": "[03/22/2021 02:00:02 job1074.example.com db.db81.example_events 54026] {\"ELAPSED\":\"0.011\",\"LEVEL\":\"info\",\"MESSAGE\":\"finished in 0.011\\n\",\"PREFIX\":\"YFgyWQriCmsAAofJAAAAHg\",\"ROUTINGKEY\":\"EXAMPLE1366.Example.Events._Publish\"}"
},
{
"description": "sample 1",
"line": "[03/22/2021 02:00:02 job1074.example.com db.db81.example_events 54026] {\"ELAPSED\":\"0.011\",\"LEVEL\":\"info\",\"MESSAGE\":\"finished in 0.011\\n\",\"PREFIX\":\"YFgyWQriCmsAAofJAAAAHg\",\"ROUTINGKEY\":\"EXAMPLE1366.Example.Events._Publish\"}"
}
]
}
}

@ -0,0 +1 @@
✘ error: unknown format: non-existent

@ -0,0 +1,7 @@
✘ error: expecting an operation to perform on the std regex using regex101.com
 = note: this regex is currently associated with the following regex101.com entry:
https://regex101.com/r/zpEnjV
 = help: the available subcommands are:
push: create/update an entry for this regex on regex101.com
pull: create a patch format file for this regular expression based on the entry in regex101.com
delete: delete the entry regex101.com that was created by a push operation

@ -0,0 +1,15 @@
{
"$schema": "https://lnav.org/schemas/format-v1.schema.json",
"unit_test_log": {
"regex": {
"alt": {
"pattern": "^\\[(?<timestamp>\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3,6})?)Z?\\](?<body>.*)$"
}
},
"sample": [
{
"line": "[2021-05-21T21:58:57.022497Z]"
}
]
}
}

@ -0,0 +1 @@
✘ error: “bro” is an internal format that is not defined in a configuration file

@ -0,0 +1,6 @@
#! /bin/bash
run_cap_test ${lnav_test} -n \
-c ';SELECT json(content) as content FROM lnav_events' \
-c ':write-jsonlines-to -' \
${test_dir}/logfile_access_log.0

@ -1179,12 +1179,12 @@ CREATE VIEW lnav_view_filters_and_stats AS
CREATE VIRTUAL TABLE regexp_capture USING regexp_capture_impl();
CREATE VIRTUAL TABLE xpath USING xpath_impl();
CREATE VIRTUAL TABLE fstat USING fstat_impl();
CREATE TABLE lnav_events (
ts TEXT NOT NULL DEFAULT(strftime('%Y-%m-%dT%H:%M:%f', 'now')),
content TEXT
);
CREATE TABLE http_status_codes (
status integer PRIMARY KEY,
message text,
FOREIGN KEY(status) REFERENCES access_log(sc_status)
);
EOF

Loading…
Cancel
Save