From 4d2ae760676796092767df2924040349734700f4 Mon Sep 17 00:00:00 2001 From: "i.vetrov" Date: Wed, 31 Jan 2018 22:13:53 +0300 Subject: [PATCH] Add logging to file --- CMakeLists.txt | 4 +- README.md | 20 +++ SConstruct | 6 +- sqlproxy.conf | 5 +- src/logger.hpp | 201 +++++++++++++++++++++++++++++++ server.hpp => src/server.hpp | 67 +++++++---- sqlproxy.cpp => src/sqlproxy.cpp | 46 +++++-- 7 files changed, 310 insertions(+), 39 deletions(-) create mode 100644 src/logger.hpp rename server.hpp => src/server.hpp (76%) rename sqlproxy.cpp => src/sqlproxy.cpp (56%) diff --git a/CMakeLists.txt b/CMakeLists.txt index c2ecc76..38028de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 2.8) set(Boost_INCLUDE_DIR /usr/local/include) set(Boost_LIBRARY_DIR /usr/local/lib) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -pipe -O3 -g -Wall -W -std=c++11") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -pipe -O3 -g -Wall -W -std=c++11 -Isrc") set(CMAKE_LINK_FLAGS "${CMAKE_LINK_FLAGS} -m64 -g -Wl,-O3") find_package(Boost COMPONENTS system program_options thread filesystem regex REQUIRED) include_directories(${Boost_INCLUDE_DIR}) link_directories(${Boost_LIBRARY_DIR}) -add_executable(sqlproxy sqlproxy.cpp) +add_executable(sqlproxy src/sqlproxy.cpp) target_link_libraries(sqlproxy ${Boost_LIBRARIES}) diff --git a/README.md b/README.md index 7ac1ce5..b9e04f7 100644 --- a/README.md +++ b/README.md @@ -34,3 +34,23 @@ with cmake: $ mkdir .build && cd .build $ cmake .. $ make + +### Example of configuration + + logger { + filename "sqlproxy.log" + } + proxy { + proxy_ip "127.0.0.1" + proxy_port 3333 + postgresql_ip "127.0.0.1" + postgresql_port 5432 + } + +### Execute command + + $ sqlproxy --config sqlproxy.conf + +### Testing + + $ pgbench -h localhost -U test -d testdb -p 3333 -c 32 -j 4 -T 360 diff --git a/SConstruct b/SConstruct index 0d37c36..7f7f65c 100644 --- a/SConstruct +++ b/SConstruct @@ -5,13 +5,13 @@ SQLProxy server build configuration env = Environment( CPPFLAGS='-m64 -pipe -O3 -g -Wall -W -std=c++11', LINKFLAGS='-m64 -g -Wl,-O3', - CPPPATH=['/usr/local/include'], + CPPPATH=['src', '/usr/local/include'], LIBPATH=['/usr/local/lib'], LIBS=Split('pthread boost_system boost_program_options boost_thread ' - 'boost_filesystem boost_regex') + 'boost_filesystem') ) env['CPPPATH'].append(filter(None, ARGUMENTS.get("cpppath", "").split(","))) env['LIBPATH'].append(filter(None, ARGUMENTS.get("libpath", "").split(","))) -env.Program('sqlproxy', ['sqlproxy.cpp']) +env.Program('sqlproxy', ['src/sqlproxy.cpp']) diff --git a/sqlproxy.conf b/sqlproxy.conf index 0ae9c94..909f59a 100644 --- a/sqlproxy.conf +++ b/sqlproxy.conf @@ -1,4 +1,7 @@ -SQLProxy { +logger { + filename "sqlproxy.log" +} +proxy { proxy_ip "127.0.0.1" proxy_port 3333 postgresql_ip "127.0.0.1" diff --git a/src/logger.hpp b/src/logger.hpp new file mode 100644 index 0000000..0d90060 --- /dev/null +++ b/src/logger.hpp @@ -0,0 +1,201 @@ +/** + * logger.hpp + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace services { + +/// Class to provide simple logging functionality. Use the services::logger +/// typedef. +template +class basic_logger + : private boost::noncopyable +{ +public: + /// The type of the service that will be used to provide timer operations. + typedef Service service_type; + + /// The native implementation type of the timer. + typedef typename service_type::impl_type impl_type; + + /// Constructor. + /** + * This constructor creates a logger. + * + * @param io_service The io_service object used to locate the logger service. + * + * @param identifier An identifier for this logger. + */ + explicit basic_logger(boost::asio::io_service& io_service, + const std::string& identifier) + : service_(boost::asio::use_service(io_service)) + , impl_(service_.null()) + { + service_.create(impl_, identifier); + } + + /// Destructor. + ~basic_logger() { + service_.destroy(impl_); + } + + /// Get the io_service associated with the object. + boost::asio::io_service& get_io_service() { + return service_.get_io_service(); + } + + /// Set the output file for all logger instances. + void use_file(const std::string& file) { + service_.use_file(impl_, file); + } + + /// Log a message. + void log(const std::string& message) { + service_.log(impl_, message); + } + +private: + /// The backend service implementation. + service_type& service_; + + /// The underlying native implementation. + impl_type impl_; +}; // class basic_logger + + +/// Service implementation for the logger. +class logger_service + : public boost::asio::io_service::service +{ +public: + /// The unique service identifier. + static boost::asio::io_service::id id; + + /// The backend implementation of a logger. + struct logger_impl { + explicit logger_impl(const std::string& ident) : identifier(ident) {} + std::string identifier; + }; + + /// The type for an implementation of the logger. + typedef logger_impl* impl_type; + + /// Constructor creates a thread to run a private io_service. + logger_service(boost::asio::io_service& io_service) + : boost::asio::io_service::service(io_service) + , work_io_service_() + , work_(new boost::asio::io_service::work(work_io_service_)) + , work_thread_(new boost::thread( + boost::bind(&boost::asio::io_service::run, &work_io_service_))) + {} + + /// Destructor shuts down the private io_service. + ~logger_service() { + /// Indicate that we have finished with the private io_service. Its + /// io_service::run() function will exit once all other work has completed. + work_.reset(); + if (work_thread_) + work_thread_->join(); + } + + /// Destroy all user-defined handler objects owned by the service. + void shutdown_service() {} + + /// Return a null logger implementation. + impl_type null() const { + return nullptr; + } + + /// Create a new logger implementation. + void create(impl_type& impl, const std::string& identifier) { + impl = new logger_impl(identifier); + } + + /// Destroy a logger implementation. + void destroy(impl_type& impl) { + delete impl; + impl = null(); + } + + /// Set the output file for the logger. The current implementation sets the + /// output file for all logger instances, and so the impl parameter is not + /// actually needed. It is retained here to illustrate how service functions + /// are typically defined. + void use_file(impl_type& /*impl*/, const std::string& file) { + // Pass the work of opening the file to the background thread. + work_io_service_.post(boost::bind( + &logger_service::use_file_impl, this, file)); + } + + std::string cur_time() const { + std::array buf; + timeval tv; + gettimeofday(&tv, 0); + std::tm *now = localtime(&tv.tv_sec); + snprintf(&buf[0], 24, "%d-%02d-%02d %02d:%02d:%02d.%03ld", + now->tm_year+1900, now->tm_mon+1, now->tm_mday, now->tm_hour, + now->tm_min, now->tm_sec, tv.tv_usec/1000); + + return std::string(&buf[0], 23); + } + + /// Log a message. + void log(impl_type& impl, const std::string& message) { + std::ostringstream os; + os << "[" << cur_time() << "] " << impl->identifier << ": " << message; + + // Pass the work of opening the file to the background thread. + work_io_service_.post(boost::bind( + &logger_service::log_impl, this, os.str())); + } + +private: + /// Helper function used to open the output file from within the private + /// io_service's thread. + /// Must be executed only once + void use_file_impl(const std::string& file) { + if (!ofstream_.is_open()) { + // std::cerr << "Opening log " << file << "\n"; + ofstream_.open(file.c_str(), std::ios_base::app); + } + } + + /// Helper function used to log a message from within the private io_service's + /// thread. + void log_impl(const std::string& text) { + ofstream_ << text << std::endl; + } + + /// Private io_service used for performing logging operations. + boost::asio::io_service work_io_service_; + + /// Work for the private io_service to perform. If we do not give the + /// io_service some work to do then the io_service::run() function will exit + /// immediately. + boost::scoped_ptr work_; + + /// Thread used for running the work io_service's run loop. + boost::scoped_ptr work_thread_; + + /// The file to which log messages will be written. + std::ofstream ofstream_; +}; + +boost::asio::io_service::id logger_service::id; + +/// Typedef for typical logger usage. +typedef basic_logger logger; + +} // namespace services diff --git a/server.hpp b/src/server.hpp similarity index 76% rename from server.hpp rename to src/server.hpp index 95f68c1..1cd697b 100644 --- a/server.hpp +++ b/src/server.hpp @@ -4,6 +4,7 @@ #pragma once +#include "logger.hpp" #include #include #include @@ -33,10 +34,14 @@ class session typedef boost::shared_ptr ptr_type; /// Session constructor - session(boost::asio::io_service& ios) - : client_socket_(ios), - server_socket_(ios) - {} + session(boost::asio::io_service& ios, const std::string& log_name) + : client_socket_(ios) + , server_socket_(ios) + , logger_(ios, "SQL") + { + // Set the name of the file that all logger instances will use. + logger_.use_file(log_name); + } /// Client socket property tcp::socket& client_socket() { @@ -106,19 +111,31 @@ class session const size_t& bytes_transferred) { if (!ec) { if (client_data_[0] == 'Q') { - // Log sql request to console - std::cout << client_data_[0] << " "; - uint64_t p = (client_data_[1] << 24) + - (client_data_[2] << 16) + - (client_data_[3] << 8) + - client_data_[4]; - std::cout << p << " "; + // Log sql request + // uint64_t p = (client_data_[1] << 24) + + // (client_data_[2] << 16) + + // (client_data_[3] << 8) + + // client_data_[4]; + // std::cout << p << "] "; std::string command( - // const_cast(&client_data_[5]), &client_data_[5], - bytes_transferred - 5); - std::cout << command << "\n"; + bytes_transferred - 6); + logger_.log(command); } + // Log connection user and database + // else { + // std::string value(&client_data_[0], bytes_transferred); + // std::size_t pos = value.find("user"); + // if (pos != std::string::npos) { + // std::size_t pos1 = value.find("database"); + // if (pos1 != std::string::npos) { + // std::stringstream ss; + // ss << "Connection user: " << value.substr(pos + 5, pos1 - pos - 6) + // << ", database: " << value.substr(pos1 + 9); + // logger_.log(ss.str()); + // } + // } + // } async_write( server_socket_, @@ -179,6 +196,7 @@ class session tcp::socket client_socket_; tcp::socket server_socket_; + services::logger logger_; enum { max_length = 4096 }; std::array client_data_; @@ -193,21 +211,23 @@ class server public: server(boost::asio::io_service& io_service, const std::string& local_host, uint16_t local_port, - const std::string& server_host, uint16_t server_port) - : io_service_(io_service), - localhost_address_( - boost::asio::ip::address_v4::from_string(local_host)), - acceptor_(io_service_, - tcp::endpoint(localhost_address_, local_port)), - server_port_(server_port), - server_host_(server_host) + const std::string& server_host, uint16_t server_port, + const std::string& log_name) + : io_service_(io_service) + , localhost_address_( + boost::asio::ip::address_v4::from_string(local_host)) + , acceptor_(io_service_, + tcp::endpoint(localhost_address_, local_port)) + , server_port_(server_port) + , server_host_(server_host) + , log_name_(log_name) {} /// Accept client connections bool accept_connections() { try { // Create session with client/server sockets - session_ = boost::make_shared(io_service_); + session_ = boost::make_shared(io_service_, log_name_); acceptor_.async_accept( session_->client_socket(), @@ -245,6 +265,7 @@ class server session::ptr_type session_; uint16_t server_port_; std::string server_host_; + std::string log_name_; }; // class server } // namespace sqlproxy diff --git a/sqlproxy.cpp b/src/sqlproxy.cpp similarity index 56% rename from sqlproxy.cpp rename to src/sqlproxy.cpp index cfe2089..dd0e634 100644 --- a/sqlproxy.cpp +++ b/src/sqlproxy.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace pt = boost::property_tree; @@ -46,29 +47,54 @@ void init_all(int argc, char **argv) { pt::info_parser::read_info(in, proxy_config); in.close(); - std::string local_ip = proxy_config.get("SQLProxy.proxy_ip"); - uint16_t local_port = proxy_config.get("SQLProxy.proxy_port"); - std::string server_ip = proxy_config.get("SQLProxy.postgresql_ip"); - uint16_t server_port = proxy_config.get("SQLProxy.postgresql_port"); + std::string local_ip = proxy_config.get("proxy.proxy_ip"); + uint16_t local_port = proxy_config.get("proxy.proxy_port"); + std::string server_ip = proxy_config.get("proxy.postgresql_ip"); + uint16_t server_port = proxy_config.get("proxy.postgresql_port"); + std::string log_name = proxy_config.get("logger.filename"); - std::cout << "local: " << local_ip << ":" << local_port << ", " - << "remote: " << server_ip << ":" << server_port << "\n"; + std::cerr << "proxy: " << local_ip << ":" << local_port << ", " + << "server: " << server_ip << ":" << server_port << "\n"; boost::asio::io_service io_service; - sqlproxy::server server( - io_service, - local_ip, local_port, - server_ip, server_port); + sqlproxy::server server(io_service, local_ip, local_port, + server_ip, server_port, log_name); server.accept_connections(); io_service.run(); } // void init_all(int argc, char **argv) } // namespace sqlproxy +/// Handle signals +void signal_handler(int signum) { + std::string signame; + switch (signum) { + case SIGHUP: + signame = "SIGHUP"; + break; + case SIGINT: + signame = "SIGINT"; + break; + case SIGTERM: + signame = "SIGTERM"; + break; + default: + signame = "UNKNOWN"; + } + std::stringstream ss; + ss << "Exit by signal [" << signum << "] " << signame; + throw std::runtime_error(ss.str()); +} + /// Entry point int main(int argc, char **argv) { try { + // Handling signals INT (Ctrl+C), TERM и HUP + signal(SIGINT , signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGHUP , signal_handler); + sqlproxy::init_all(argc, argv); } catch (po::required_option &err) { std::cerr << "Exception: " << err.what() << "\n";