eqemu-server/common/debug.cpp
Arthur Ice d41331d948 Apparently the original implementation from facebook was incorrect.
Fixed it, sadly this does mean we need to call vsnprintf twice.
Once to find out the size involved, a second time to apply.
2013-05-20 17:51:08 -07:00

466 lines
11 KiB
C++

#include "debug.h"
#include <iostream>
#include <string>
#include <cstdarg>
#include <time.h>
#ifdef _WINDOWS
#include <process.h>
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#else
#include <sys/types.h>
#include <unistd.h>
#endif
#include "../common/StringUtil.h"
#include "../common/MiscFunctions.h"
#include "../common/platform.h"
#ifndef va_copy
#define va_copy(d,s) ((d) = (s))
#endif
static volatile bool logFileValid = false;
static EQEMuLog realLogFile;
EQEMuLog *LogFile = &realLogFile;
static const char* FileNames[EQEMuLog::MaxLogID] = { "logs/eqemu", "logs/eqemu", "logs/eqemu_error", "logs/eqemu_debug", "logs/eqemu_quest", "logs/eqemu_commands", "logs/crash" };
static const char* LogNames[EQEMuLog::MaxLogID] = { "Status", "Normal", "Error", "Debug", "Quest", "Command", "Crash" };
EQEMuLog::EQEMuLog() {
// MOpen = new Mutex;
// MLog = new Mutex*[MaxLogID];
// fp = new FILE*[MaxLogID];
// pLogStatus = new uint8[MaxLogID];
for (int i=0; i<MaxLogID; i++) {
fp[i] = 0;
// MLog[i] = new Mutex;
#if EQDEBUG >= 2
pLogStatus[i] = 1 | 2;
#else
pLogStatus[i] = 0;
#endif
logCallbackFmt[i] = nullptr;
logCallbackBuf[i] = nullptr;
logCallbackPva[i] = nullptr;
}
// TODO: Make this read from an ini or something, everyone has different opinions on what it should be
#if EQDEBUG < 2
pLogStatus[Status] = 2;
pLogStatus[Error] = 2;
pLogStatus[Quest] = 2;
pLogStatus[Commands] = 1;
#endif
logFileValid = true;
}
EQEMuLog::~EQEMuLog() {
logFileValid = false;
for (int i=0; i<MaxLogID; i++) {
LockMutex lock(&MLog[i]); //to prevent termination race
if (fp[i])
fclose(fp[i]);
}
// safe_delete_array(fp);
// safe_delete_array(MLog);
// safe_delete_array(pLogStatus);
// safe_delete(MOpen);
}
bool EQEMuLog::open(LogIDs id) {
if (!logFileValid) {
return false;
}
if (id >= MaxLogID) {
return false;
}
LockMutex lock(&MOpen);
if (pLogStatus[id] & 4) {
return false;
}
if (fp[id]) {
//cerr<<"Warning: LogFile already open"<<endl;
return true;
}
std::string filename = FileNames[id];
const EQEmuExePlatform &platform = GetExecutablePlatform();
if(platform == ExePlatformWorld) {
filename.append("_world");
} else if(platform == ExePlatformZone) {
filename.append("_zone");
} else if(platform == ExePlatformLaunch) {
filename.append("_launch");
} else if(platform == ExePlatformUCS) {
filename.append("_ucs");
} else if(platform == ExePlatformQueryServ) {
filename.append("_queryserv");
} else if(platform == ExePlatformSharedMemory) {
filename.append("_shared_memory");
}
#ifndef NO_PIDLOG
// According to http://msdn.microsoft.com/en-us/library/vstudio/ee404875(v=vs.100).aspx
// Visual Studio 2010 doesn't have std::to_string(int) but it does have one for
// long long. Oh well, it works fine and formats perfectly acceptably.
filename.append(std::to_string((long long)getpid()));
#endif
filename.append(".log");
fp[id] = fopen(filename.c_str(), "a");
if (!fp[id]) {
std::cerr << "Failed to open log file: " << filename << std::endl;
pLogStatus[id] |= 4; // set file state to error
return false;
}
fputs("---------------------------------------------\n",fp[id]);
write(id, "Starting Log: %s", filename.c_str());
return true;
}
bool EQEMuLog::write(LogIDs id, const char *fmt, ...) {
if (!logFileValid) {
return false;
}
if (id >= MaxLogID) {
return false;
}
bool dofile = false;
if (pLogStatus[id] & 1) {
dofile = open(id);
}
if (!(dofile || pLogStatus[id] & 2))
return false;
LockMutex lock(&MLog[id]);
if (!logFileValid)
return false; //check again for threading race reasons (to avoid two mutexes)
time_t aclock;
struct tm *newtime;
time( &aclock ); /* Get time in seconds */
newtime = localtime( &aclock ); /* Convert time to struct */
if (dofile) {
#ifndef NO_PIDLOG
fprintf(fp[id], "[%02d.%02d. - %02d:%02d:%02d] ", newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec);
#else
fprintf(fp[id], "%04i [%02d.%02d. - %02d:%02d:%02d] ", getpid(), newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec);
#endif
}
va_list argptr,tmpargptr;
if (dofile) {
va_start(argptr, fmt);
va_copy(tmpargptr,argptr);
vfprintf( fp[id], fmt, tmpargptr );
va_end(tmpargptr);
}
if(logCallbackFmt[id]) {
msgCallbackFmt p = logCallbackFmt[id];
va_start(argptr, fmt);
va_copy(tmpargptr,argptr);
p(id, fmt, tmpargptr );
va_end(tmpargptr);
}
std::string outputMessage;
va_start(argptr, fmt);
va_copy(tmpargptr,argptr);
vStringFormat(outputMessage, fmt, tmpargptr);
va_end(tmpargptr);
if (pLogStatus[id] & 2) {
if (pLogStatus[id] & 8) {
std::cerr << "[" << LogNames[id] << "] ";
std::cerr << outputMessage;
}
else {
std::cout << "[" << LogNames[id] << "] ";
std::cout << outputMessage;
}
}
if (dofile)
fprintf(fp[id], "\n");
if (pLogStatus[id] & 2) {
if (pLogStatus[id] & 8) {
std::cerr << std::endl;
} else {
std::cout << std::endl;
}
}
if(dofile)
fflush(fp[id]);
return true;
}
//write with Prefix and a VA_list
bool EQEMuLog::writePVA(LogIDs id, const char *prefix, const char *fmt, va_list argptr) {
if (!logFileValid) {
return false;
}
if (id >= MaxLogID) {
return false;
}
bool dofile = false;
if (pLogStatus[id] & 1) {
dofile = open(id);
}
if (!(dofile || pLogStatus[id] & 2)) {
return false;
}
LockMutex lock(&MLog[id]);
if (!logFileValid)
return false; //check again for threading race reasons (to avoid two mutexes)
time_t aclock;
struct tm *newtime;
time( &aclock ); /* Get time in seconds */
newtime = localtime( &aclock ); /* Convert time to struct */
va_list tmpargptr;
if (dofile) {
#ifndef NO_PIDLOG
fprintf(fp[id], "[%02d.%02d. - %02d:%02d:%02d] %s", newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec, prefix);
#else
fprintf(fp[id], "%04i [%02d.%02d. - %02d:%02d:%02d] %s", getpid(), newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec, prefix);
#endif
va_copy(tmpargptr, argptr);
vfprintf( fp[id], fmt, tmpargptr );
}
if(logCallbackPva[id]) {
msgCallbackPva p = logCallbackPva[id];
va_copy(tmpargptr, argptr);
p(id, prefix, fmt, tmpargptr );
}
if (pLogStatus[id] & 2) {
if (pLogStatus[id] & 8) {
fprintf(stderr, "[%s] %s", LogNames[id], prefix);
vfprintf( stderr, fmt, argptr );
}
else {
fprintf(stdout, "[%s] %s", LogNames[id], prefix);
vfprintf( stdout, fmt, argptr );
}
}
va_end(argptr);
if (dofile)
fprintf(fp[id], "\n");
if (pLogStatus[id] & 2) {
if (pLogStatus[id] & 8)
fprintf(stderr, "\n");
else
fprintf(stdout, "\n");
}
if(dofile)
fflush(fp[id]);
return true;
}
bool EQEMuLog::writebuf(LogIDs id, const char *buf, uint8 size, uint32 count) {
if (!logFileValid) {
return false;
}
if (id >= MaxLogID) {
return false;
}
bool dofile = false;
if (pLogStatus[id] & 1) {
dofile = open(id);
}
if (!(dofile || pLogStatus[id] & 2))
return false;
LockMutex lock(&MLog[id]);
if (!logFileValid)
return false; //check again for threading race reasons (to avoid two mutexes)
time_t aclock;
struct tm *newtime;
time( &aclock ); /* Get time in seconds */
newtime = localtime( &aclock ); /* Convert time to struct */
if (dofile)
#ifndef NO_PIDLOG
fprintf(fp[id], "[%02d.%02d. - %02d:%02d:%02d] ", newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec);
#else
fprintf(fp[id], "%04i [%02d.%02d. - %02d:%02d:%02d] ", getpid(), newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min, newtime->tm_sec);
#endif
if (dofile) {
fwrite(buf, size, count, fp[id]);
fprintf(fp[id], "\n");
}
if(logCallbackBuf[id]) {
msgCallbackBuf p = logCallbackBuf[id];
p(id, buf, size, count);
}
if (pLogStatus[id] & 2) {
if (pLogStatus[id] & 8) {
fprintf(stderr, "[%s] ", LogNames[id]);
fwrite(buf, size, count, stderr);
fprintf(stderr, "\n");
} else {
fprintf(stdout, "[%s] ", LogNames[id]);
fwrite(buf, size, count, stdout);
fprintf(stdout, "\n");
}
}
if(dofile)
fflush(fp[id]);
return true;
}
bool EQEMuLog::writeNTS(LogIDs id, bool dofile, const char *fmt, ...) {
va_list argptr, tmpargptr;
va_start(argptr, fmt);
if (dofile) {
va_copy(tmpargptr, argptr);
vfprintf( fp[id], fmt, tmpargptr );
}
if (pLogStatus[id] & 2) {
if (pLogStatus[id] & 8)
vfprintf( stderr, fmt, argptr );
else
vfprintf( stdout, fmt, argptr );
}
va_end(argptr);
return true;
};
bool EQEMuLog::Dump(LogIDs id, uint8* data, uint32 size, uint32 cols, uint32 skip) {
if (!logFileValid) {
#if EQDEBUG >= 10
cerr << "Error: Dump() from null pointer"<<endl;
#endif
return false;
}
if (size == 0)
return true;
if (!LogFile)
return false;
if (id >= MaxLogID)
return false;
bool dofile = false;
if (pLogStatus[id] & 1) {
dofile = open(id);
}
if (!(dofile || pLogStatus[id] & 2))
return false;
LockMutex lock(&MLog[id]);
if (!logFileValid)
return false; //check again for threading race reasons (to avoid two mutexes)
write(id, "Dumping Packet: %i", size);
// Output as HEX
int beginningOfLineOffset = 0;
uint32 indexInData;
std::string asciiOutput;
for(indexInData=skip; indexInData<size; indexInData++) {
if ((indexInData-skip)%cols==0) {
if (indexInData != skip)
writeNTS(id, dofile, " | %s\n", asciiOutput.c_str());
writeNTS(id, dofile, "%4i: ", indexInData-skip);
asciiOutput.clear();
beginningOfLineOffset = 0;
}
else if ((indexInData-skip)%(cols/2) == 0) {
writeNTS(id, dofile, "- ");
}
writeNTS(id, dofile, "%02X ", (unsigned char)data[indexInData]);
if (data[indexInData] >= 32 && data[indexInData] < 127)
{
// According to http://msdn.microsoft.com/en-us/library/vstudio/ee404875(v=vs.100).aspx
// Visual Studio 2010 doesn't have std::to_string(int) but it does have the long long
// version.
asciiOutput.append(std::to_string((long long)data[indexInData]));
}
else
{
asciiOutput.append(".");
}
}
uint32 k = ((indexInData-skip)-1)%cols;
if (k < 8)
writeNTS(id, dofile, " ");
for (uint32 h = k+1; h < cols; h++) {
writeNTS(id, dofile, " ");
}
writeNTS(id, dofile, " | %s\n", asciiOutput.c_str());
if (dofile)
fflush(fp[id]);
return true;
}
void EQEMuLog::SetCallback(LogIDs id, msgCallbackFmt proc) {
if (!logFileValid)
return;
if (id >= MaxLogID) {
return;
}
logCallbackFmt[id] = proc;
}
void EQEMuLog::SetCallback(LogIDs id, msgCallbackBuf proc) {
if (!logFileValid)
return;
if (id >= MaxLogID) {
return;
}
logCallbackBuf[id] = proc;
}
void EQEMuLog::SetCallback(LogIDs id, msgCallbackPva proc) {
if (!logFileValid)
return;
if (id >= MaxLogID) {
return;
}
logCallbackPva[id] = proc;
}
void EQEMuLog::SetAllCallbacks(msgCallbackFmt proc) {
if (!logFileValid)
return;
int r;
for(r = Status; r < MaxLogID; r++) {
SetCallback((LogIDs)r, proc);
}
}
void EQEMuLog::SetAllCallbacks(msgCallbackBuf proc) {
if (!logFileValid)
return;
int r;
for(r = Status; r < MaxLogID; r++) {
SetCallback((LogIDs)r, proc);
}
}
void EQEMuLog::SetAllCallbacks(msgCallbackPva proc) {
if (!logFileValid)
return;
int r;
for(r = Status; r < MaxLogID; r++) {
SetCallback((LogIDs)r, proc);
}
}