hostile-takeover/server/logger.cpp

209 lines
5.8 KiB
C++

#include "server/logger.h"
#include "server/endpoint.h"
#include "server/server.h"
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>
namespace wi {
#define MSG_LOGENTRY 1
Logger::Logger(const char *pszDir, dword server_id) :
base::MessageHandler(worker_), dir_(pszDir), pf_(NULL),
rotating_(false) {
char szPrefix[64];
sprintf(szPrefix, "server-%u-%u", base::GetSecondsUnixEpocUTC(), server_id);
prefix_ = szPrefix;
worker_.Start(this, &Logger::ThreadStart);
}
Logger::~Logger() {
// Thread::Stop gets called in the Thread destructor, which does a join
// with the actual thread, which synchronizes exiting
}
void Logger::ThreadStart(void *pv) {
// Start logging
OpenLog();
// Run and process messages. RunLoop will return when this thread exits
// When worker_ destructs, queued base::MessageData objects get destroyed
worker_.RunLoop();
// Close files
CloseLog();
}
std::string Logger::GetLogFilename() {
time_t t = time(NULL);
struct tm *tm = localtime(&t);
char szFilename[PATH_MAX];
sprintf(szFilename, "%s/%s_%04d-%02d-%02d.log", dir_.c_str(),
prefix_.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
return std::string(szFilename);
}
bool Logger::OpenLog() {
// Create log dir if it doesn't exist
struct stat st;
if (stat(dir_.c_str(), &st) != 0) {
if (errno == ENOENT) {
// Dir doesn't exist; Assume one level directory to keep this easy
if (mkdir(dir_.c_str(), 0755) != 0) {
return false;
}
} else {
return false;
}
} else {
// Ensure it is a directory
if (!(st.st_mode & S_IFDIR)) {
return false;
}
}
// Close it if it is open... shouldn't be the case but be safe
if (pf_ != NULL) {
CloseLog();
}
// Attempt open for writing. Allow append to the end of an existing file
std::string filename = GetLogFilename();
pf_ = fopen(filename.c_str(), "ab");
if (pf_ == NULL) {
return false;
}
filenameLast_ = filename;
// Called on worker thread so handle directly
LogEntry entry(knLogEntryTypeLogger);
entry.message_ = "OPEN LOG";
WriteLogEntry(entry);
return true;
}
void Logger::CloseLog() {
// Called on worker thread so handle directly
LogEntry entry(knLogEntryTypeLogger);
entry.message_ = "CLOSE LOG";
WriteLogEntry(entry);
// Close log
if (pf_ != NULL) {
fclose(pf_);
pf_ = NULL;
}
}
void Logger::RotateLog() {
if (!rotating_) {
std::string filename = GetLogFilename();
if (filenameLast_.compare(filename) != 0) {
rotating_ = true;
CloseLog();
OpenLog();
rotating_ = false;
}
}
}
void Logger::Submit(const LogEntry& entry) {
// Called on any thread, post to worker thread
worker_.Post(MSG_LOGENTRY, this, new LogEntryData(entry));
}
void Logger::InitLogEntryFromEndpoint(Endpoint *endpoint, LogEntry &entry) {
entry.gameid_ = endpoint->gameid();
strncpyz(entry.did_, endpoint->did(), sizeof(entry.did_));
entry.ip_ = endpoint->xpump().socket()->GetRemoteAddress().ip();
entry.anon_ = endpoint->anonymous();
entry.game_name_ = endpoint->GetGameName();
entry.player_name_ = endpoint->name();
entry.mod_ = endpoint->IsModerator();
entry.admin_ = endpoint->IsAdmin();
Room *room = endpoint->server().lobby().FindRoom(endpoint->roomid());
if (room != NULL) {
entry.roomid_ = endpoint->roomid();
entry.room_name_ = room->name();
entry.private_room_ = (room->password()[0] != 0);
}
}
void Logger::Log(LogEntryType type, Endpoint *endpoint, const char *pszMsg) {
LogEntry entry(type);
InitLogEntryFromEndpoint(endpoint, entry);
entry.message_ = pszMsg;
Submit(entry);
}
void Logger::LogRoomChat(Endpoint *endpoint, dword roomid, const char *pszRoomName, bool private_room, const char *pszMsg) {
if (strlen(pszMsg) != 0) {
LogEntry entry(knLogEntryTypeChat);
if (endpoint != NULL) {
InitLogEntryFromEndpoint(endpoint, entry);
}
entry.roomid_ = roomid;
entry.room_name_ = pszRoomName;
entry.private_room_ = private_room;
entry.message_ = pszMsg;
Submit(entry);
}
}
void Logger::LogGameChat(Endpoint *endpoint, dword roomid, const char *pszRoomName, bool private_room, dword gameid, const char *pszGameName, const char *pszMsg) {
if (strlen(pszMsg) != 0) {
// TODO: Put roomid / room name in here
LogEntry entry(knLogEntryTypeChat);
if (endpoint != NULL) {
InitLogEntryFromEndpoint(endpoint, entry);
}
entry.roomid_ = roomid;
entry.room_name_ = pszRoomName;
entry.private_room_ = private_room;
entry.gameid_ = gameid;
entry.game_name_ = pszGameName;
entry.message_ = pszMsg;
Submit(entry);
}
}
void Logger::OnMessage(base::Message *pmsg) {
// Worker thread message handler
if (pmsg->id == MSG_LOGENTRY) {
LogEntryData *data = (LogEntryData *)pmsg->data;
WriteLogEntry(data->entry_);
delete data;
}
}
bool Logger::WriteLogEntry(const LogEntry& entry) {
// Executes on worker thread
// Check for log rotation
RotateLog();
// If there is no open log file, fail
if (pf_ == NULL) {
return false;
}
// Serialize the LogEntry
base::ByteBuffer bb;
entry.Serialize(bb);
// Write to the log file and flush immediately
base::ByteBuffer bbT;
bbT.WriteDword(bb.Length());
bbT.WriteBytes(bb.Data(), bb.Length());
if (fwrite(bbT.Data(), bbT.Length(), 1, pf_) != 1) {
return false;
}
fflush(pf_);
return true;
}
} // namespace wi