feat: add database debug logging

This commit is contained in:
Andrey Kutin 2026-10-25 12:47:29 +03:00
parent f15bf41bfe
commit 02b7b41b63
4 changed files with 52 additions and 7 deletions

View File

@ -43,7 +43,7 @@ cd ..
./build/bin/cards /path/to/config.json ./build/bin/cards /path/to/config.json
``` ```
Для подробного вывода этапов запуска и итоговой конфигурации используйте флаг `--debug`. Пароль базы данных в таком выводе не раскрывается. Для подробного вывода этапов запуска и итоговой конфигурации используйте флаг `--debug`. Он также включает диагностику работы с БД: создание схемы, запуск и остановку воркеров, запросы, выбранную стратегию выборки, результаты и переподключения после ошибок. Пароль базы данных в таком выводе не раскрывается.
```bash ```bash
./build/bin/cards --debug ./build/bin/cards --debug

View File

@ -1,13 +1,17 @@
#include "database.hpp" #include "database.hpp"
#include <chrono> #include <chrono>
#include <iostream>
#include <syncstream>
Database::Database(const DatabaseConfig& cfg, int workers) Database::Database(const DatabaseConfig& cfg, int workers, bool debug)
: connStr( : connStr(
"host=" + cfg.host + "host=" + cfg.host +
" port=" + std::to_string(cfg.port) + " port=" + std::to_string(cfg.port) +
" dbname=" + cfg.dbname + " dbname=" + cfg.dbname +
" user=" + cfg.user + " user=" + cfg.user +
" password=" + cfg.password) { " password=" + cfg.password),
debug(debug) {
logDebug("Creating schema for random_data");
{ {
pqxx::connection probe(connStr); pqxx::connection probe(connStr);
pqxx::work txn(probe); pqxx::work txn(probe);
@ -22,6 +26,7 @@ Database::Database(const DatabaseConfig& cfg, int workers)
); );
txn.commit(); txn.commit();
} }
logDebug("Schema is ready; starting " + std::to_string(workers) + " database worker(s)");
for (int i = 0; i < workers; ++i) { for (int i = 0; i < workers; ++i) {
threadPool.emplace_back([this, i]() { worker(i); }); threadPool.emplace_back([this, i]() { worker(i); });
} }
@ -36,16 +41,20 @@ void Database::stop() {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
stopFlag = true; stopFlag = true;
} }
logDebug("Stopping database workers");
cv.notify_all(); cv.notify_all();
for (auto& t : threadPool) { for (auto& t : threadPool) {
if (t.joinable()) { if (t.joinable()) {
t.join(); t.join();
} }
} }
logDebug("All database workers stopped");
} }
void Database::worker(int id) { void Database::worker(int id) {
logDebug("Worker " + std::to_string(id) + " connecting to database");
auto conn = std::make_unique<pqxx::connection>(connStr); auto conn = std::make_unique<pqxx::connection>(connStr);
logDebug("Worker " + std::to_string(id) + " connected to database");
std::mt19937 rng(std::random_device{}()); std::mt19937 rng(std::random_device{}());
for (;;) { for (;;) {
@ -53,7 +62,10 @@ void Database::worker(int id) {
{ {
std::unique_lock<std::mutex> lock(mtx); std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return stopFlag || !queue.empty(); }); cv.wait(lock, [&] { return stopFlag || !queue.empty(); });
if (stopFlag && queue.empty()) return; if (stopFlag && queue.empty()) {
logDebug("Worker " + std::to_string(id) + " stopped");
return;
}
task = std::move(queue.front()); task = std::move(queue.front());
queue.pop(); queue.pop();
} }
@ -61,23 +73,36 @@ void Database::worker(int id) {
json res; json res;
try { try {
if (task.op == Op::RandomRecord) { if (task.op == Op::RandomRecord) {
logDebug("Worker " + std::to_string(id) + " fetching one random record");
res = getRandomRecord(*conn, rng); res = getRandomRecord(*conn, rng);
} else { } else {
logDebug("Worker " + std::to_string(id) + " fetching " + std::to_string(task.count)
+ " random record(s)");
res = getRandomRecords(*conn, rng, task.count, task.med, task.max); res = getRandomRecords(*conn, rng, task.count, task.med, task.max);
} }
} catch (const std::exception&) { } catch (const std::exception& e) {
logDebug("Worker " + std::to_string(id) + " database error: " + e.what()
+ "; reconnecting");
conn = std::make_unique<pqxx::connection>(connStr); conn = std::make_unique<pqxx::connection>(connStr);
logDebug("Worker " + std::to_string(id) + " reconnected to database");
res = json{{"error", "Database error"}}; res = json{{"error", "Database error"}};
} }
logDebug("Worker " + std::to_string(id) + " completed request"
+ (res.contains("error") ? ": " + res["error"].get<std::string>() : ""));
task.cb(std::move(res)); task.cb(std::move(res));
} }
} }
void Database::enqueue(Task task) { void Database::enqueue(Task task) {
std::size_t queueSize;
const char* operation = task.op == Op::RandomRecord ? "one random record" : "random records";
{ {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
queue.push(std::move(task)); queue.push(std::move(task));
queueSize = queue.size();
} }
logDebug("Queued request for " + std::string(operation)
+ "; pending requests: " + std::to_string(queueSize));
cv.notify_one(); cv.notify_one();
} }
@ -91,6 +116,7 @@ void Database::getRandomRecords(int count, int med, int max, Callback cb) {
json Database::getRandomRecord(pqxx::connection& conn, std::mt19937& rng) { json Database::getRandomRecord(pqxx::connection& conn, std::mt19937& rng) {
int totalCount = getTotalCount(conn); int totalCount = getTotalCount(conn);
logDebug("random_data contains " + std::to_string(totalCount) + " record(s)");
if (totalCount == 0) { if (totalCount == 0) {
return json{ return json{
@ -100,6 +126,7 @@ json Database::getRandomRecord(pqxx::connection& conn, std::mt19937& rng) {
std::uniform_int_distribution<int> idDist(1, totalCount); std::uniform_int_distribution<int> idDist(1, totalCount);
int randomId = idDist(rng); int randomId = idDist(rng);
logDebug("Selected random record ID " + std::to_string(randomId));
pqxx::work txn(conn); pqxx::work txn(conn);
@ -111,6 +138,7 @@ json Database::getRandomRecord(pqxx::connection& conn, std::mt19937& rng) {
); );
if (result.empty()) { if (result.empty()) {
logDebug("Selected record no longer exists; falling back to the first available record");
result = txn.exec("SELECT id, name, text, description, created_at FROM random_data LIMIT 1"); result = txn.exec("SELECT id, name, text, description, created_at FROM random_data LIMIT 1");
} }
@ -137,6 +165,7 @@ json Database::getRandomRecord(pqxx::connection& conn, std::mt19937& rng) {
} }
json Database::getRandomRecordsFast(pqxx::connection& conn, const int count) { json Database::getRandomRecordsFast(pqxx::connection& conn, const int count) {
logDebug("Using recursive CTE strategy for " + std::to_string(count) + " record(s)");
pqxx::work txn(conn); pqxx::work txn(conn);
std::string query = std::string query =
@ -210,6 +239,7 @@ json Database::getRandomRecordsFast(pqxx::connection& conn, const int count) {
} }
txn.commit(); txn.commit();
logDebug("Recursive CTE returned " + std::to_string(response.size()) + " record(s)");
return { return {
{"data", response}, {"data", response},
@ -220,6 +250,8 @@ json Database::getRandomRecordsFast(pqxx::connection& conn, const int count) {
} }
json Database::getRandomRecordsSlow(pqxx::connection& conn, std::mt19937& rng, const int count, const int totalCount) { json Database::getRandomRecordsSlow(pqxx::connection& conn, std::mt19937& rng, const int count, const int totalCount) {
logDebug("Using individual-ID strategy for " + std::to_string(count) + " of "
+ std::to_string(totalCount) + " record(s)");
std::set<int> ids; std::set<int> ids;
{ {
std::uniform_int_distribution<int> idDist(1, totalCount); std::uniform_int_distribution<int> idDist(1, totalCount);
@ -259,6 +291,7 @@ json Database::getRandomRecordsSlow(pqxx::connection& conn, std::mt19937& rng, c
} }
txn.commit(); txn.commit();
logDebug("Individual-ID query returned " + std::to_string(response.size()) + " record(s)");
return { return {
{"data", response}, {"data", response},
@ -277,6 +310,8 @@ json Database::getRandomRecords(pqxx::connection& conn, std::mt19937& rng, int c
} }
int totalCount = getTotalCount(conn); int totalCount = getTotalCount(conn);
logDebug("Requested " + std::to_string(count) + " random record(s); "
+ std::to_string(totalCount) + " available");
if (totalCount == 0) { if (totalCount == 0) {
return json{ return json{
@ -286,10 +321,12 @@ json Database::getRandomRecords(pqxx::connection& conn, std::mt19937& rng, int c
} }
if (count > totalCount) { if (count > totalCount) {
logDebug("Reducing requested count to available record count");
count = totalCount; count = totalCount;
} }
if (count > max) { if (count > max) {
logDebug("Reducing requested count to configured maximum of " + std::to_string(max));
count = max; count = max;
} }
@ -329,3 +366,9 @@ std::string Database::getQueryRandomIds(const int count) {
query += ") ORDER BY id"; query += ") ORDER BY id";
return query; return query;
} }
void Database::logDebug(const std::string& message) const {
if (debug) {
std::osyncstream(std::cout) << "[debug][database] " << message << '\n';
}
}

View File

@ -19,7 +19,7 @@ class Database {
public: public:
using Callback = std::function<void(json)>; using Callback = std::function<void(json)>;
explicit Database(const DatabaseConfig& config, int workers = 2); explicit Database(const DatabaseConfig& config, int workers = 2, bool debug = false);
~Database(); ~Database();
Database(const Database&) = delete; Database(const Database&) = delete;
@ -42,6 +42,7 @@ private:
void worker(int id); void worker(int id);
void enqueue(Task task); void enqueue(Task task);
void stop(); void stop();
void logDebug(const std::string& message) const;
json getRandomRecord(pqxx::connection& conn, std::mt19937& rng); json getRandomRecord(pqxx::connection& conn, std::mt19937& rng);
json getRandomRecords(pqxx::connection& conn, std::mt19937& rng, int count, int med, int max); json getRandomRecords(pqxx::connection& conn, std::mt19937& rng, int count, int med, int max);
@ -56,4 +57,5 @@ private:
std::condition_variable cv; std::condition_variable cv;
std::queue<Task> queue; std::queue<Task> queue;
bool stopFlag = false; bool stopFlag = false;
bool debug;
}; };

View File

@ -59,7 +59,7 @@ int main(int argc, char* argv[]) {
} }
std::cout << "Connecting to database..." << std::endl; std::cout << "Connecting to database..." << std::endl;
auto db = std::make_shared<Database>(config.database); auto db = std::make_shared<Database>(config.database, 2, debug);
std::cout << "Database connected successfully!" << std::endl; std::cout << "Database connected successfully!" << std::endl;
if (debug) { if (debug) {