186 lines
4.4 KiB
C++
186 lines
4.4 KiB
C++
#include <psemek/journal/journal.hpp>
|
|
#include <psemek/log/log.hpp>
|
|
#include <psemek/util/exception.hpp>
|
|
#include <psemek/util/hash_table.hpp>
|
|
|
|
#include <format>
|
|
#include <sstream>
|
|
|
|
#ifdef PSEMEK_USE_SQLITE
|
|
#include <sqlite3.h>
|
|
#endif
|
|
|
|
namespace psemek::journal
|
|
{
|
|
|
|
#ifdef PSEMEK_USE_SQLITE
|
|
|
|
struct journal::impl
|
|
{
|
|
impl(std::filesystem::path const & path)
|
|
{
|
|
if (sqlite3_open(path.c_str(), &database) != SQLITE_OK)
|
|
{
|
|
auto error_message = std::format("Failed to open sqlite database at {}: {}", path.c_str(), sqlite3_errmsg(database));
|
|
sqlite3_close(database);
|
|
throw util::exception(std::move(error_message));
|
|
}
|
|
}
|
|
|
|
~impl()
|
|
{
|
|
sqlite3_close(database);
|
|
}
|
|
|
|
void log_event(event const & event)
|
|
{
|
|
auto table_it = tables.find(event.metadata.name);
|
|
|
|
if (table_it == tables.end())
|
|
{
|
|
std::ostringstream command;
|
|
|
|
command << "PRAGMA synchronous = OFF;";
|
|
|
|
command << "CREATE TABLE IF NOT EXISTS " << event.metadata.name << "(timestamp TEXT";
|
|
|
|
table_it = tables.insert({event.metadata.name, event.metadata}).first;
|
|
table_it->second.source_file = event.metadata.source_file;
|
|
table_it->second.source_line = event.metadata.source_line;
|
|
|
|
for (auto const & attribute : event.metadata.columns)
|
|
command << ", " << attribute << " TEXT";
|
|
|
|
command << ");";
|
|
|
|
auto command_str = command.str();
|
|
|
|
char * error = nullptr;
|
|
if (sqlite3_exec(database, command_str.data(), nullptr, nullptr, &error) != SQLITE_OK)
|
|
{
|
|
auto error_message = std::string("Failed to create sqlite table: ") + error + std::string("\nSQL command: ") + command_str;
|
|
sqlite3_free(error);
|
|
throw util::exception(std::move(error_message));
|
|
}
|
|
}
|
|
|
|
{
|
|
std::ostringstream command;
|
|
|
|
command << "PRAGMA synchronous = OFF; INSERT INTO " << event.metadata.name << "(timestamp";
|
|
|
|
for (auto const & attribute : event.metadata.columns)
|
|
command << ", " << attribute;
|
|
|
|
command << ") VALUES (\"" << event.data.time << "\"";
|
|
|
|
for (auto const & attribute : event.data.values)
|
|
command << ", " << attribute;
|
|
|
|
command << ");";
|
|
|
|
auto command_str = command.str();
|
|
|
|
char * error = nullptr;
|
|
if (sqlite3_exec(database, command_str.data(), nullptr, nullptr, &error) != SQLITE_OK)
|
|
{
|
|
auto error_message = std::string("Failed to insert into sqlite table: ") + error + std::string("\nSQL command: ") + command_str;
|
|
sqlite3_free(error);
|
|
throw util::exception(std::move(error_message));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::pair<event_metadata const *, event_data>> select(std::string const & query)
|
|
{
|
|
std::vector<std::pair<event_metadata const *, event_data>> result;
|
|
|
|
for (auto const & table : tables)
|
|
{
|
|
std::string table_query = "SELECT * FROM " + table.first + (query.empty() ? "" : " WHERE ") + query + ";";
|
|
|
|
struct context
|
|
{
|
|
std::vector<std::pair<event_metadata const *, event_data>> * result;
|
|
event_metadata const * metadata;
|
|
};
|
|
|
|
context ctx{&result, &table.second};
|
|
|
|
auto callback = [](void * pcontext, int columns, char ** values, char **) -> int
|
|
{
|
|
auto & ctx = *(context *)pcontext;
|
|
|
|
auto & event = ctx.result->emplace_back(std::pair{ctx.metadata, event_data{}});
|
|
|
|
event.second.time = values[0];
|
|
|
|
for (int i = 1; i < columns; ++i)
|
|
event.second.values.push_back(values[i]);
|
|
|
|
return 0;
|
|
};
|
|
|
|
char * error = nullptr;
|
|
if (sqlite3_exec(database, table_query.c_str(), callback, &ctx, &error) != SQLITE_OK)
|
|
sqlite3_free(error);
|
|
}
|
|
|
|
std::sort(result.begin(), result.end(), [](auto const & e1, auto const & e2){ return e1.second.time < e2.second.time; });
|
|
|
|
return result;
|
|
}
|
|
|
|
sqlite3 * database = nullptr;
|
|
bool enabled = true;
|
|
|
|
util::hash_map<std::string, event_metadata> tables;
|
|
};
|
|
|
|
#else
|
|
|
|
struct journal::impl
|
|
{
|
|
impl(std::filesystem::path const &)
|
|
{}
|
|
|
|
void log_event(event const &)
|
|
{}
|
|
|
|
std::vector<std::pair<event_metadata const *, event_data>> select(std::string const &)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
bool enabled = true;
|
|
};
|
|
|
|
#endif
|
|
|
|
journal::journal(std::filesystem::path const & path)
|
|
: pimpl_(make_impl(path))
|
|
{}
|
|
|
|
journal::~journal() = default;
|
|
|
|
bool journal::enabled() const
|
|
{
|
|
return impl().enabled;
|
|
}
|
|
|
|
void journal::set_enabled(bool enabled)
|
|
{
|
|
impl().enabled = enabled;
|
|
}
|
|
|
|
void journal::log_event(event const & event)
|
|
{
|
|
impl().log_event(event);
|
|
}
|
|
|
|
std::vector<std::pair<event_metadata const *, event_data>> journal::select(std::string const & query)
|
|
{
|
|
return impl().select(query);
|
|
}
|
|
|
|
}
|