Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sql auditing #1370

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dev/error_code.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ namespace sqlite_orm {
index_is_out_of_bounds,
value_is_null,
no_tables_specified,
failure_to_init_logfile,
};
}

Expand Down
146 changes: 146 additions & 0 deletions dev/sql_auditing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#pragma once

#include <fstream>
fnc12 marked this conversation as resolved.
Show resolved Hide resolved
#include <chrono>
#include <ctime> // std::localtime and std::tm and std::time_t
#include <iomanip> // std::put_time
#include <memory>
#include <string>
#include <memory>
#include "error_code.h"

enum class auditing_behavior : signed char { OFF = 0, ON = 1 };

namespace sqlite_orm {
namespace internal {
class storage_base;
}
}
using sqlite_orm::internal::storage_base;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this using is here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using is used here because log_settings and log_admin are declared in the glocal namespace

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use global namespace in the lib code


struct log_settings {
auditing_behavior behavior;
std::shared_ptr<std::ofstream> log_file;
std::string log_file_name;
std::string time_format;
enum status_code { FAIL, GOOD } status = status_code::FAIL;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid using non-class enums cause old enums add their constants into global scope

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

bool global_settings = false;

public:
log_settings(auditing_behavior behave,
std::shared_ptr<std::ofstream> file,
std::string file_name,
std::string format,
status_code stats,
bool glob_settings) {
behavior = behave;
log_file = file;
bool ok = log_file->good();
bool open = log_file->is_open();
log_file_name = file_name;
time_format = format;
status = stats;
global_settings = glob_settings;
}
log_settings() {}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need default constructor here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need a default constructor for log_settings because we provide a default constructor for log_admin!!

void open() {
using namespace std;
if(!log_file->is_open()) {
log_file->open(log_file_name, ios::trunc | ios::out);
if(!log_file->good()) {
status = FAIL;
log_file->close();
throw std::system_error{sqlite_orm::orm_error_code::failure_to_init_logfile};
}
}
status = GOOD;
}
void log(std::string message) {
if(status == GOOD && behavior == auditing_behavior::ON) {
// would use format if C++ 20
auto now = std::chrono::system_clock::now();

std::time_t now_time = std::chrono::system_clock::to_time_t(now);

// Convert to local time (std::tm structure)
// WARNING: localtime is not thread safe!
std::tm local_time = *std::localtime(&now_time);

// Print the local time in a human-readable format
*log_file << "@: " << std::put_time(&local_time, time_format.c_str()) << " = ";
*log_file << message << std::endl;
}
}
};

inline std::shared_ptr<std::ofstream> get_file(std::string log_file_name) {
static std::map<std::string, std::shared_ptr<std::ofstream>> files;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to keep file streams in a static map? better avoid static vars at all cause it is not thread safe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__the other alternative would be a global object;....

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use global/static objects cause it it not thread safe and break the architecture. Global object is anti-pattern

auto it = files.find(log_file_name);
if(it == files.end()) {
std::shared_ptr<std::ofstream> pointer_to_shared{new std::ofstream()};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use std::make_shared instead of new. Avoid using new and delete if you can

files.insert({log_file_name, pointer_to_shared});
}
return files[log_file_name];
}

struct log_admin {
private:
log_admin(){};
log_settings global_settings; // not belonging to any storage_base!
std::map<storage_base*, log_settings> settings;
std::map<sqlite3*, storage_base*> map_sqlite3;
bool global_settings_initialized = false;

public:
static log_admin& admin() {
static log_admin admin{};
return admin;
}

void register_db(storage_base* sbase, sqlite3* db) {
map_sqlite3[db] = sbase;
}

// pass nullptr if not belonging to one particular storage_base!
void open_log_file(std::string log_file_name,
auditing_behavior on_or_off,
storage_base* sbase,
std::string time_format) {
std::shared_ptr<std::ofstream> log_file = get_file(log_file_name);
if(sbase == nullptr) {
global_settings = log_settings(on_or_off, log_file, log_file_name, time_format, log_settings::FAIL, false);
global_settings.open();
global_settings_initialized = true;
} else {
settings[sbase] = log_settings(on_or_off, log_file, log_file_name, time_format, log_settings::FAIL, false);
settings[sbase].open();
}
}
void set_auditing_behavior(auditing_behavior on_or_off, storage_base* sbase) {
auto pointer = settings.find(sbase);
if(pointer != settings.end()) {
pointer->second.behavior = on_or_off;
}
}
void setFormatStr(std::string format, storage_base* sbase) {
auto pointer = settings.find(sbase);
if(pointer != settings.end()) {
pointer->second.time_format = format;
}
}
void log(const std::string& message, sqlite3* db) {
storage_base* base = map_sqlite3[db];
if(base) {
auto pointer = settings.find(base);
if(pointer != settings.end()) {
pointer->second.log(message);
return;
}
}
// exclusive or behavior!!
// not belonging to any particular storage_base
if(global_settings_initialized) {
global_settings.log(message);
}
}
};
3 changes: 3 additions & 0 deletions dev/storage_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,9 @@ namespace sqlite_orm {
if(this->on_open) {
this->on_open(db);
}
// JD added
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is useless cause git blame has info about authors of every line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

log_admin::admin().register_db(this, db);
// JD end
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here: this comment is useless cause git blame has info about authors of every line

Copy link
Contributor Author

@juandent juandent Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

}

template<class F>
Expand Down
5 changes: 5 additions & 0 deletions dev/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include "error_code.h"

#include "sql_auditing.h"

namespace sqlite_orm {

/**
Expand Down Expand Up @@ -61,6 +63,7 @@ namespace sqlite_orm {
if(sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
throw_translated_sqlite_error(db);
}
log_admin::admin().log(query, db);
return stmt;
}

Expand All @@ -69,6 +72,7 @@ namespace sqlite_orm {
if(rc != SQLITE_OK) {
throw_translated_sqlite_error(db);
}
log_admin::admin().log(query, db);
}

inline void perform_exec(sqlite3* db,
Expand All @@ -79,6 +83,7 @@ namespace sqlite_orm {
if(rc != SQLITE_OK) {
throw_translated_sqlite_error(db);
}
log_admin::admin().log(query, db);
}

inline void perform_exec(sqlite3* db,
Expand Down
155 changes: 155 additions & 0 deletions include/sqlite_orm/sqlite_orm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2863,6 +2863,7 @@ namespace sqlite_orm {
index_is_out_of_bounds,
value_is_null,
no_tables_specified,
failure_to_init_logfile,
};
}

Expand Down Expand Up @@ -13447,6 +13448,154 @@ namespace sqlite_orm {

// #include "error_code.h"

// #include "sql_auditing.h"

#include <fstream>
#include <chrono>
#include <ctime> // std::localtime and std::tm and std::time_t
#include <iomanip> // std::put_time
#include <memory>
#include <string>
#include <memory>
// #include "error_code.h"

enum class auditing_behavior : signed char { OFF = 0, ON = 1 };

namespace sqlite_orm {
namespace internal {
class storage_base;
}
}
using sqlite_orm::internal::storage_base;

struct log_settings {
auditing_behavior behavior;
std::shared_ptr<std::ofstream> log_file;
std::string log_file_name;
std::string time_format;
enum status_code { FAIL, GOOD } status = status_code::FAIL;
bool global_settings = false;

public:
log_settings(auditing_behavior behave,
std::shared_ptr<std::ofstream> file,
std::string file_name,
std::string format,
status_code stats,
bool glob_settings) {
behavior = behave;
log_file = file;
bool ok = log_file->good();
bool open = log_file->is_open();
log_file_name = file_name;
time_format = format;
status = stats;
global_settings = glob_settings;
}
log_settings() {}
void open() {
using namespace std;
if(!log_file->is_open()) {
log_file->open(log_file_name, ios::trunc | ios::out);
if(!log_file->good()) {
status = FAIL;
log_file->close();
throw std::system_error{sqlite_orm::orm_error_code::failure_to_init_logfile};
}
}
status = GOOD;
}
void log(std::string message) {
if(status == GOOD && behavior == auditing_behavior::ON) {
// would use format if C++ 20
auto now = std::chrono::system_clock::now();

std::time_t now_time = std::chrono::system_clock::to_time_t(now);

// Convert to local time (std::tm structure)
// WARNING: localtime is not thread safe!
std::tm local_time = *std::localtime(&now_time);

// Print the local time in a human-readable format
*log_file << "@: " << std::put_time(&local_time, time_format.c_str()) << " = ";
*log_file << message << std::endl;
}
}
};

inline std::shared_ptr<std::ofstream> get_file(std::string log_file_name) {
static std::map<std::string, std::shared_ptr<std::ofstream>> files;
auto it = files.find(log_file_name);
if(it == files.end()) {
std::shared_ptr<std::ofstream> pointer_to_shared{new std::ofstream()};
files.insert({log_file_name, pointer_to_shared});
}
return files[log_file_name];
}

struct log_admin {
private:
log_admin(){};
log_settings global_settings; // not belonging to any storage_base!
std::map<storage_base*, log_settings> settings;
std::map<sqlite3*, storage_base*> map_sqlite3;
bool global_settings_initialized = false;

public:
static log_admin& admin() {
static log_admin admin{};
return admin;
}

void register_db(storage_base* sbase, sqlite3* db) {
map_sqlite3[db] = sbase;
}

// pass nullptr if not belonging to one particular storage_base!
void open_log_file(std::string log_file_name,
auditing_behavior on_or_off,
storage_base* sbase,
std::string time_format) {
std::shared_ptr<std::ofstream> log_file = get_file(log_file_name);
//std::shared_ptr<std::ofstream> log_file{new std::ofstream()};
if(sbase == nullptr) {
global_settings = log_settings(on_or_off, log_file, log_file_name, time_format, log_settings::FAIL, false);
global_settings.open();
global_settings_initialized = true;
} else {
settings[sbase] = log_settings(on_or_off, log_file, log_file_name, time_format, log_settings::FAIL, false);
settings[sbase].open();
}
}
void set_auditing_behavior(auditing_behavior on_or_off, storage_base* sbase) {
auto pointer = settings.find(sbase);
if(pointer != settings.end()) {
pointer->second.behavior = on_or_off;
}
}
void setFormatStr(std::string format, storage_base* sbase) {
auto pointer = settings.find(sbase);
if(pointer != settings.end()) {
pointer->second.time_format = format;
}
}
void log(const std::string& message, sqlite3* db) {
storage_base* base = map_sqlite3[db];
if(base) {
auto pointer = settings.find(base);
if(pointer != settings.end()) {
pointer->second.log(message);
return;
}
}
// exclusive or behavior!!
// not belonging to any particular storage_base
if(global_settings_initialized) {
global_settings.log(message);
}
}
};

namespace sqlite_orm {

/**
Expand Down Expand Up @@ -13502,6 +13651,7 @@ namespace sqlite_orm {
if(sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
throw_translated_sqlite_error(db);
}
log_admin::admin().log(query, db);
return stmt;
}

Expand All @@ -13510,6 +13660,7 @@ namespace sqlite_orm {
if(rc != SQLITE_OK) {
throw_translated_sqlite_error(db);
}
log_admin::admin().log(query, db);
}

inline void perform_exec(sqlite3* db,
Expand All @@ -13520,6 +13671,7 @@ namespace sqlite_orm {
if(rc != SQLITE_OK) {
throw_translated_sqlite_error(db);
}
log_admin::admin().log(query, db);
}

inline void perform_exec(sqlite3* db,
Expand Down Expand Up @@ -18217,6 +18369,9 @@ namespace sqlite_orm {
if(this->on_open) {
this->on_open(db);
}
// JD added
log_admin::admin().register_db(this, db);
// JD end
}

template<class F>
Expand Down
Loading
Loading