From 136469d722a9986be6bbad68788619284919d876 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 16 Apr 2013 22:29:39 +0200 Subject: [PATCH] Log all finished accesses to an access log. --- Makefile | 2 +- accesslog.cpp | 93 +++++++++++++++++++++++++++++++++++++++++++ accesslog.h | 40 +++++++++++++++++++ client.cpp | 6 ++- config.cpp | 5 ++- config.h | 2 + cubemap.config.sample | 4 ++ main.cpp | 12 ++++++ server.cpp | 6 +++ 9 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 accesslog.cpp create mode 100644 accesslog.h diff --git a/Makefile b/Makefile index 52ef830..1232ae1 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ PROTOC=protoc CXXFLAGS=-Wall -O2 -g LDLIBS=-lpthread -lprotobuf -OBJS=main.o client.o server.o stream.o serverpool.o mutexlock.o input.o httpinput.o udpinput.o parse.o config.o markpool.o acceptor.o stats.o thread.o util.o log.o state.pb.o +OBJS=main.o client.o server.o stream.o serverpool.o mutexlock.o input.o httpinput.o udpinput.o parse.o config.o markpool.o acceptor.o stats.o accesslog.o thread.o util.o log.o state.pb.o all: cubemap diff --git a/accesslog.cpp b/accesslog.cpp new file mode 100644 index 0000000..b666f27 --- /dev/null +++ b/accesslog.cpp @@ -0,0 +1,93 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "accesslog.h" +#include "client.h" +#include "log.h" +#include "mutexlock.h" + +using namespace std; + +AccessLogThread::AccessLogThread() {} + +AccessLogThread::AccessLogThread(const string &filename) + : filename(filename) {} + +void AccessLogThread::write(const ClientStats& client) +{ + MutexLock lock(&mutex); + pending_writes.push_back(client); +} + +void AccessLogThread::do_work() +{ + // Open the file. + if (filename.empty()) { + logfp = NULL; + } else { + logfp = fopen(filename.c_str(), "a+"); + if (logfp == NULL) { + log_perror(filename.c_str()); + // Continue as before. + } + } + + while (!should_stop) { + // Empty the queue. + vector writes; + { + MutexLock lock(&mutex); + swap(pending_writes, writes); + } + + if (logfp == NULL) { + continue; + } + + // Do the actual writes. + time_t now = time(NULL); + for (size_t i = 0; i < writes.size(); ++i) { + fprintf(logfp, "%llu %s %s %d %llu %llu %llu\n", + (long long unsigned)(writes[i].connect_time), + writes[i].remote_addr.c_str(), + writes[i].stream_id.c_str(), + int(now - writes[i].connect_time), + (long long unsigned)(writes[i].bytes_sent), + (long long unsigned)(writes[i].bytes_lost), + (long long unsigned)(writes[i].num_loss_events)); + } + fflush(logfp); + + // Wait until the stop_fd pipe is closed, one second has passed. + // or a spurious signal arrives. + pollfd pfd; + pfd.fd = stop_fd_read; + pfd.events = POLLIN | POLLRDHUP; + + int nfds = poll(&pfd, 1, 1000); + if (nfds == 0 || (nfds == -1 && errno == EINTR)) { + continue; + } + if (nfds == 1) { + // Should stop. + break; + } + if (nfds == -1) { + log_perror("poll"); + usleep(100000); + continue; + } + } + + if (fclose(logfp) == EOF) { + log_perror("fclose"); + } + + logfp = NULL; +} diff --git a/accesslog.h b/accesslog.h new file mode 100644 index 0000000..66be5a8 --- /dev/null +++ b/accesslog.h @@ -0,0 +1,40 @@ +#ifndef _ACCESSLOG_H +#define _ACCESSLOG_H + +// A class to log clients that just disconnected. Since this is shared by all +// Server instances, we try not to let write() block too much, and rather do +// all the I/O in a separate I/O thread. + +#include + +#include +#include + +#include "client.h" +#include "thread.h" + +class AccessLogThread : public Thread { +public: + // Used if we do not have a file to log to. The thread will still exist, + // but won't actually write anywhere. + AccessLogThread(); + + // Log to a given file. If the file can't be opened, log an error + // to the error log, and work as if we didn't have a log file. + AccessLogThread(const std::string &filename); + + // Add a log entry. Entries are written out at least once every second. + void write(const ClientStats& client); + +private: + virtual void do_work(); + + // The file we are logging to. If NULL, do not log. + FILE *logfp; + std::string filename; + + pthread_mutex_t mutex; + std::vector pending_writes; +}; + +#endif // _ACCESSLOG_H diff --git a/client.cpp b/client.cpp index 5871da1..28e1f06 100644 --- a/client.cpp +++ b/client.cpp @@ -92,7 +92,11 @@ ClientProto Client::serialize() const ClientStats Client::get_stats() const { ClientStats stats; - stats.stream_id = stream_id; + if (stream_id.empty()) { + stats.stream_id = "-"; + } else { + stats.stream_id = stream_id; + } stats.sock = sock; stats.fwmark = fwmark; stats.remote_addr = remote_addr; diff --git a/config.cpp b/config.cpp index 77acf6d..fad29a5 100644 --- a/config.cpp +++ b/config.cpp @@ -287,12 +287,15 @@ bool parse_config(const string &filename, Config *config) if (has_stats_interval && !has_stats_file) { log(WARNING, "'stats_interval' given, but no 'stats_file'. No statistics will be written."); } + + fetch_config_string(lines, "access_log", &config->access_log_file); for (size_t i = 0; i < lines.size(); ++i) { const ConfigLine &line = lines[i]; if (line.keyword == "num_servers" || line.keyword == "stats_file" || - line.keyword == "stats_interval") { + line.keyword == "stats_interval" || + line.keyword == "access_log") { // Already taken care of, above. } else if (line.keyword == "port") { if (!parse_port(line, config)) { diff --git a/config.h b/config.h index eee2e1b..47dabe9 100644 --- a/config.h +++ b/config.h @@ -37,6 +37,8 @@ struct Config { std::string stats_file; // Empty means no stats file. int stats_interval; + + std::string access_log_file; // Empty means no accses_log file. }; // Parse and validate configuration. Returns false on error. diff --git a/cubemap.config.sample b/cubemap.config.sample index b959e94..8504d2a 100644 --- a/cubemap.config.sample +++ b/cubemap.config.sample @@ -13,6 +13,10 @@ port 9094 stats_file cubemap.stats stats_interval 60 +# Logging of clients as they disconnect (and as such as no longer visible in the stats file). +# You can only have zero or one of these. +access_log access.log + # Logging of various informational and error messages. You can have as many of these as you want. error_log type=file filename=cubemap.log error_log type=syslog diff --git a/main.cpp b/main.cpp index 8430c20..be3e8eb 100644 --- a/main.cpp +++ b/main.cpp @@ -16,6 +16,7 @@ #include #include +#include "accesslog.h" #include "acceptor.h" #include "config.h" #include "input.h" @@ -29,6 +30,7 @@ using namespace std; +AccessLogThread *access_log = NULL; ServerPool *servers = NULL; volatile bool hupped = false; volatile bool stopped = false; @@ -307,6 +309,14 @@ start: open_logs(config.log_destinations); log(INFO, "Cubemap " SERVER_VERSION " starting."); + if (config.access_log_file.empty()) { + // Create a dummy logger. + access_log = new AccessLogThread(); + } else { + access_log = new AccessLogThread(config.access_log_file); + } + access_log->run(); + servers = new ServerPool(config.num_servers); CubemapStateProto loaded_state; @@ -420,6 +430,8 @@ start: } } delete servers; + access_log->stop(); + delete access_log; shut_down_logging(); if (stopped) { diff --git a/server.cpp b/server.cpp index 3d915c2..24aa84e 100644 --- a/server.cpp +++ b/server.cpp @@ -14,6 +14,7 @@ #include #include +#include "accesslog.h" #include "log.h" #include "markpool.h" #include "mutexlock.h" @@ -24,6 +25,8 @@ using namespace std; +extern AccessLogThread *access_log; + Server::Server() { pthread_mutex_init(&mutex, NULL); @@ -504,6 +507,9 @@ void Server::close_client(Client *client) } } + // Log to access_log. + access_log->write(client->get_stats()); + // Bye-bye! int ret; do { -- 2.39.2