dvander@592: /** dvander@2726: * vim: set ts=4 sw=4 : ds@1330: * ============================================================================= dvander@1231: * SourceMod dvander@2726: * Copyright (C) 2004-2009 AlliedModders LLC. All rights reserved. ds@1330: * ============================================================================= dvander@592: * ds@1330: * This program is free software; you can redistribute it and/or modify it under ds@1330: * the terms of the GNU General Public License, version 3.0, as published by the ds@1330: * Free Software Foundation. dvander@1231: * ds@1330: * This program is distributed in the hope that it will be useful, but WITHOUT ds@1330: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ds@1330: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ds@1330: * details. dvander@1231: * ds@1330: * You should have received a copy of the GNU General Public License along with ds@1330: * this program. If not, see . dvander@1231: * ds@1330: * As a special exception, AlliedModders LLC gives you permission to link the ds@1330: * code of this program (as well as its derivative works) to "Half-Life 2," the ds@1330: * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software ds@1330: * by the Valve Corporation. You must obey the GNU General Public License in ds@1330: * all respects for all other code used. Additionally, AlliedModders LLC grants ds@1330: * this exception to all derivative works. AlliedModders LLC defines further ds@1330: * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), ds@1330: * or . dvander@592: * dvander@592: * Version: $Id$ dvander@592: */ dvander@592: dvander@592: #include dvander@592: #include "sourcemod.h" dvander@592: #include "sourcemm_api.h" dvander@663: #include "sm_stringutil.h" dvander@592: #include "Logger.h" dvander@2411: #include "LibrarySys.h" dvander@1371: #include "TimerSys.h" dvander@2784: #include dvander@592: dvander@616: Logger g_Logger; dvander@592: dvander@592: /** dvander@592: * :TODO: This should be creating the log folder if it doesn't exist dvander@592: */ dvander@592: dvander@663: ConfigResult Logger::OnSourceModConfigChanged(const char *key, dvander@663: const char *value, dvander@663: ConfigSource source, dvander@663: char *error, dvander@663: size_t maxlength) ds@661: { dvander@663: if (strcasecmp(key, "Logging") == 0) ds@661: { ds@669: bool state; ds@661: ds@661: if (strcasecmp(value, "on") == 0) ds@661: { ds@669: state = true; ds@661: } else if (strcasecmp(value, "off") == 0) { ds@661: state = false; ds@661: } else { dvander@663: UTIL_Format(error, maxlength, "Invalid value: must be \"on\" or \"off\""); dvander@663: return ConfigResult_Reject; ds@661: } ds@661: dvander@663: if (source == ConfigSource_Console) ds@661: { ds@669: state ? EnableLogging() : DisableLogging(); dvander@663: } else { ds@661: m_InitialState = state; ds@661: } ds@661: dvander@663: return ConfigResult_Accept; dvander@663: } else if (strcasecmp(key, "LogMode") == 0) { ds@661: if (strcasecmp(value, "daily") == 0) ds@661: { ds@661: m_Mode = LoggingMode_Daily; ds@661: } else if (strcasecmp(value, "map") == 0) { ds@661: m_Mode = LoggingMode_PerMap; ds@661: } else if (strcasecmp(value, "game") == 0) { ds@661: m_Mode = LoggingMode_Game; ds@661: } else { dvander@663: UTIL_Format(error, maxlength, "Invalid value: must be [daily|map|game]"); dvander@663: return ConfigResult_Reject; ds@661: } ds@661: dvander@663: return ConfigResult_Accept; ds@661: } ds@661: dvander@663: return ConfigResult_Ignore; ds@661: } ds@661: dvander@616: void Logger::OnSourceModStartup(bool late) dvander@592: { dvander@663: InitLogger(m_Mode); dvander@592: } dvander@592: dvander@616: void Logger::OnSourceModAllShutdown() dvander@592: { dvander@592: CloseLogger(); dvander@592: } dvander@592: dvander@673: void Logger::OnSourceModLevelChange(const char *mapName) dvander@673: { dvander@673: MapChange(mapName); dvander@673: } dvander@673: dvander@616: void Logger::_NewMapFile() dvander@592: { dvander@592: if (!m_Active) dvander@592: { dvander@592: return; dvander@592: } ds@661: ds@661: /* Append "Log file closed" to previous log file */ ds@661: _CloseFile(); dvander@592: dvander@592: char _filename[256]; dvander@592: int i = 0; dvander@592: dvander@592: time_t t; dvander@1371: GetAdjustedTime(&t); dvander@592: tm *curtime = localtime(&t); dvander@592: dvander@592: while (true) dvander@592: { ds@738: g_SourceMod.BuildPath(Path_SM, _filename, sizeof(_filename), "logs/L%02d%02d%03d.log", curtime->tm_mon + 1, curtime->tm_mday, i); dvander@592: FILE *fp = fopen(_filename, "r"); dvander@592: if (!fp) dvander@592: { dvander@592: break; dvander@592: } dvander@592: fclose(fp); dvander@592: i++; dvander@592: } dvander@592: m_NrmFileName.assign(_filename); dvander@592: dvander@592: FILE *fp = fopen(m_NrmFileName.c_str(), "w"); dvander@592: if (!fp) dvander@592: { dvander@1444: char error[255]; dvander@1444: g_LibSys.GetPlatformError(error, sizeof(error)); dvander@1444: LogFatal("[SM] Unexpected fatal logging error (file \"%s\")", m_NrmFileName.c_str()); dvander@1444: LogFatal("[SM] Platform returned error: \"%s\"", error); dvander@1444: LogFatal("[SM] Logging has been disabled."); dvander@592: m_Active = false; dvander@592: return; dvander@592: } else { dvander@592: char date[32]; dvander@592: strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime); dvander@2784: fprintf(fp, "L %s: SourceMod log file started (file \"L%02d%02d%03d.log\") (Version \"%s\")\n", date, curtime->tm_mon + 1, curtime->tm_mday, i, SM_FULL_VERSION); dvander@592: fclose(fp); dvander@592: } dvander@592: } dvander@592: dvander@616: void Logger::_CloseFile() dvander@592: { dvander@592: if (!m_Active) dvander@592: { dvander@592: return; dvander@592: } dvander@592: dvander@592: FILE *fp = NULL; dvander@592: if (!m_NrmFileName.empty()) dvander@592: { dvander@592: fp = fopen(m_NrmFileName.c_str(), "r+"); dvander@592: if (fp) dvander@592: { dvander@592: fseek(fp, 0, SEEK_END); dvander@592: LogMessage("Log file closed."); dvander@592: fclose(fp); dvander@592: } dvander@592: m_NrmFileName.clear(); dvander@592: } dvander@592: dvander@592: if (!m_ErrMapStart) dvander@592: { dvander@592: return; dvander@592: } dvander@592: fp = fopen(m_ErrFileName.c_str(), "r+"); dvander@592: if (fp) dvander@592: { dvander@592: fseek(fp, 0, SEEK_END); dvander@592: LogError("Error log file session closed."); dvander@592: fclose(fp); dvander@592: } dvander@592: m_ErrFileName.clear(); dvander@592: } dvander@592: dvander@663: void Logger::InitLogger(LoggingMode mode) dvander@592: { ds@661: m_Mode = mode; dvander@663: m_Active = m_InitialState; dvander@592: dvander@592: time_t t; dvander@1371: GetAdjustedTime(&t); dvander@592: tm *curtime = localtime(&t); dvander@592: m_CurDay = curtime->tm_mday; dvander@592: dvander@592: char _filename[256]; ds@738: g_SourceMod.BuildPath(Path_SM, _filename, sizeof(_filename), "logs/errors_%04d%02d%02d.log", curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday); dvander@592: m_ErrFileName.assign(_filename); dvander@592: ds@661: switch (m_Mode) dvander@592: { dvander@592: case LoggingMode_PerMap: dvander@592: { dvander@663: if (!m_Active) dvander@592: { dvander@592: m_DelayedStart = true; dvander@592: } dvander@592: break; dvander@592: } dvander@592: case LoggingMode_Daily: dvander@592: { ds@738: g_SourceMod.BuildPath(Path_SM, _filename, sizeof(_filename), "logs/L%04d%02d%02d.log", curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday); dvander@592: m_NrmFileName.assign(_filename); dvander@592: m_DailyPrintHdr = true; dvander@592: break; dvander@592: } dvander@592: default: dvander@592: { dvander@592: /* do nothing... */ dvander@592: break; dvander@592: } dvander@592: } dvander@592: } dvander@592: dvander@616: void Logger::CloseLogger() dvander@592: { dvander@592: _CloseFile(); dvander@592: } dvander@592: dvander@1378: void Logger::LogToOpenFile(FILE *fp, const char *msg, ...) dvander@1378: { dvander@1378: if (!m_Active) dvander@1378: { dvander@1378: return; dvander@1378: } dvander@1378: dvander@1378: va_list ap; dvander@1378: va_start(ap, msg); dvander@1378: LogToOpenFileEx(fp, msg, ap); dvander@1378: va_end(ap); dvander@1378: } dvander@1378: dvander@2475: void Logger::LogToFileOnly(FILE *fp, const char *msg, ...) dvander@2475: { dvander@2475: if (!m_Active) dvander@2475: { dvander@2475: return; dvander@2475: } dvander@2475: dvander@2475: va_list ap; dvander@2475: va_start(ap, msg); dvander@2475: LogToFileOnlyEx(fp, msg, ap); dvander@2475: va_end(ap); dvander@2475: } dvander@2475: dvander@1378: void Logger::LogToOpenFileEx(FILE *fp, const char *msg, va_list ap) dvander@1378: { dvander@1378: if (!m_Active) dvander@1378: { dvander@1378: return; dvander@1378: } dvander@1378: skamonkey@3501: static ConVar *sv_logecho = icvar->FindVar("sv_logecho"); skamonkey@3501: dvander@1378: char buffer[3072]; dvander@1378: UTIL_FormatArgs(buffer, sizeof(buffer), msg, ap); dvander@1378: dvander@1378: char date[32]; dvander@1378: time_t t; dvander@1378: GetAdjustedTime(&t); dvander@1378: tm *curtime = localtime(&t); dvander@1378: strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime); dvander@1378: dvander@1378: fprintf(fp, "L %s: %s\n", date, buffer); skamonkey@3501: skamonkey@3501: if (!sv_logecho || sv_logecho->GetBool()) skamonkey@3501: { skamonkey@3501: g_SMAPI->ConPrintf("L %s: %s\n", date, buffer); skamonkey@3501: } dvander@1378: } dvander@1378: dvander@2475: void Logger::LogToFileOnlyEx(FILE *fp, const char *msg, va_list ap) dvander@2475: { dvander@2475: if (!m_Active) dvander@2475: { dvander@2475: return; dvander@2475: } dvander@2475: dvander@2475: char buffer[3072]; dvander@2475: UTIL_FormatArgs(buffer, sizeof(buffer), msg, ap); dvander@2475: dvander@2475: char date[32]; dvander@2475: time_t t; dvander@2475: GetAdjustedTime(&t); dvander@2475: tm *curtime = localtime(&t); dvander@2475: strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime); dvander@2475: dvander@2475: fprintf(fp, "L %s: %s\n", date, buffer); dvander@2475: fflush(fp); dvander@2475: } dvander@2475: dvander@616: void Logger::LogMessage(const char *vafmt, ...) dvander@592: { dvander@592: if (!m_Active) dvander@592: { dvander@592: return; dvander@592: } dvander@592: ds@661: if (m_Mode == LoggingMode_Game) dvander@592: { dvander@592: va_list ap; dvander@592: va_start(ap, vafmt); ds@661: _PrintToGameLog(vafmt, ap); dvander@592: va_end(ap); dvander@592: return; dvander@592: } dvander@592: dvander@592: if (m_DelayedStart) dvander@592: { dvander@592: m_DelayedStart = false; dvander@592: _NewMapFile(); dvander@592: } dvander@592: dvander@592: time_t t; dvander@1371: GetAdjustedTime(&t); dvander@592: tm *curtime = localtime(&t); dvander@592: dvander@592: FILE *fp = NULL; ds@661: if (m_Mode == LoggingMode_PerMap) dvander@592: { dvander@592: fp = fopen(m_NrmFileName.c_str(), "a+"); dvander@592: if (!fp) dvander@592: { dvander@592: _NewMapFile(); dvander@592: fp = fopen(m_NrmFileName.c_str(), "a+"); dvander@592: if (!fp) dvander@592: { dvander@592: goto print_error; dvander@592: } dvander@592: } dvander@592: } else { dvander@592: if (m_CurDay != curtime->tm_mday) dvander@592: { dvander@592: char _filename[256]; ds@738: g_SourceMod.BuildPath(Path_SM, _filename, sizeof(_filename), "logs/L%04d%02d%02d.log", curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday); dvander@592: m_NrmFileName.assign(_filename); dvander@592: m_CurDay = curtime->tm_mday; dvander@592: m_DailyPrintHdr = true; dvander@592: } dvander@592: fp = fopen(m_NrmFileName.c_str(), "a+"); dvander@592: } dvander@592: dvander@592: if (fp) dvander@592: { dvander@592: if (m_DailyPrintHdr) dvander@592: { dvander@1378: char date[32]; dvander@592: m_DailyPrintHdr = false; dvander@1378: strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime); dvander@2784: fprintf(fp, "L %s: SourceMod log file session started (file \"L%04d%02d%02d.log\") (Version \"%s\")\n", date, curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday, SM_FULL_VERSION); dvander@592: } dvander@1378: va_list ap; dvander@1378: va_start(ap, vafmt); dvander@1378: LogToOpenFileEx(fp, vafmt, ap); dvander@1378: va_end(ap); dvander@592: fclose(fp); dvander@592: } else { dvander@592: goto print_error; dvander@592: } dvander@592: dvander@592: return; dvander@592: print_error: dvander@1444: char error[255]; dvander@1444: g_LibSys.GetPlatformError(error, sizeof(error)); dvander@1444: LogFatal("[SM] Unexpected fatal logging error (file \"%s\")", m_NrmFileName.c_str()); dvander@1444: LogFatal("[SM] Platform returned error: \"%s\"", error); dvander@1444: LogFatal("[SM] Logging has been disabled."); dvander@592: m_Active = false; dvander@592: } dvander@592: dvander@616: void Logger::LogError(const char *vafmt, ...) dvander@592: { dvander@2726: va_list ap; dvander@2726: va_start(ap, vafmt); dvander@2726: LogErrorEx(vafmt, ap); dvander@2726: va_end(ap); dvander@2726: } dvander@2726: dvander@2726: void Logger::LogErrorEx(const char *vafmt, va_list ap) dvander@2726: { dvander@592: if (!m_Active) dvander@592: { dvander@592: return; dvander@592: } dvander@592: dvander@592: time_t t; dvander@1371: GetAdjustedTime(&t); dvander@592: tm *curtime = localtime(&t); dvander@592: dvander@592: if (curtime->tm_mday != m_CurDay) dvander@592: { dvander@592: char _filename[256]; ds@738: g_SourceMod.BuildPath(Path_SM, _filename, sizeof(_filename), "logs/errors_%04d%02d%02d.log", curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday); dvander@592: m_ErrFileName.assign(_filename); dvander@592: m_CurDay = curtime->tm_mday; dvander@592: m_ErrMapStart = false; dvander@592: } dvander@592: dvander@592: FILE *fp = fopen(m_ErrFileName.c_str(), "a+"); dvander@592: if (fp) dvander@592: { dvander@592: if (!m_ErrMapStart) dvander@592: { dvander@1378: char date[32]; dvander@1378: strftime(date, sizeof(date), "%m/%d/%Y - %H:%M:%S", curtime); dvander@592: fprintf(fp, "L %s: SourceMod error session started\n", date); ds@738: fprintf(fp, "L %s: Info (map \"%s\") (file \"errors_%04d%02d%02d.log\")\n", date, m_CurMapName.c_str(), curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday); dvander@592: m_ErrMapStart = true; dvander@592: } dvander@1378: LogToOpenFileEx(fp, vafmt, ap); dvander@592: fclose(fp); faluco@1686: } faluco@1686: else faluco@1686: { dvander@1444: char error[255]; dvander@1444: g_LibSys.GetPlatformError(error, sizeof(error)); dvander@1444: LogFatal("[SM] Unexpected fatal logging error (file \"%s\")", m_NrmFileName.c_str()); dvander@1444: LogFatal("[SM] Platform returned error: \"%s\"", error); dvander@1444: LogFatal("[SM] Logging has been disabled."); dvander@592: m_Active = false; dvander@592: return; dvander@592: } dvander@592: } dvander@592: dvander@616: void Logger::MapChange(const char *mapname) dvander@592: { dvander@592: m_CurMapName.assign(mapname); dvander@592: ds@661: switch (m_Mode) dvander@592: { dvander@592: case LoggingMode_Daily: dvander@592: { dvander@592: LogMessage("-------- Mapchange to %s --------", mapname); dvander@592: break; dvander@592: } dvander@592: case LoggingMode_PerMap: dvander@592: { dvander@592: _NewMapFile(); dvander@592: break; dvander@592: } dvander@592: default: dvander@592: { dvander@592: /* Do nothing... */ dvander@592: break; dvander@592: } dvander@592: } dvander@592: dvander@592: if (m_ErrMapStart) dvander@592: { dvander@592: LogError("Error log file session closed."); dvander@592: } dvander@592: m_ErrMapStart = false; dvander@592: } dvander@592: ds@661: void Logger::_PrintToGameLog(const char *fmt, va_list ap) dvander@592: { dvander@592: char msg[3072]; dvander@592: size_t len; dvander@592: dvander@592: len = vsnprintf(msg, sizeof(msg)-2, fmt, ap); dvander@592: len = (len >= sizeof(msg)) ? (sizeof(msg) - 2) : len; dvander@592: dvander@592: msg[len++] = '\n'; dvander@592: msg[len] = '\0'; dvander@592: dvander@1914: Engine_LogPrintWrapper(msg); dvander@592: } dvander@592: dvander@616: const char *Logger::GetLogFileName(LogType type) const dvander@592: { dvander@592: switch (type) dvander@592: { dvander@592: case LogType_Normal: dvander@592: { dvander@592: return m_NrmFileName.c_str(); dvander@592: } dvander@592: case LogType_Error: dvander@592: { dvander@592: return m_ErrFileName.c_str(); dvander@592: } dvander@592: default: dvander@592: { dvander@592: return ""; dvander@592: } dvander@592: } dvander@592: } dvander@592: dvander@616: LoggingMode Logger::GetLoggingMode() const dvander@592: { ds@661: return m_Mode; dvander@592: } dvander@592: dvander@616: void Logger::EnableLogging() dvander@592: { dvander@592: if (m_Active) dvander@592: { dvander@592: return; dvander@592: } dvander@592: m_Active = true; ds@669: LogMessage("[SM] Logging enabled manually by user."); dvander@592: } dvander@592: dvander@616: void Logger::DisableLogging() dvander@592: { dvander@592: if (!m_Active) dvander@592: { dvander@592: return; dvander@592: } ds@669: LogMessage("[SM] Logging disabled manually by user."); dvander@592: m_Active = false; dvander@592: } dvander@663: dvander@663: void Logger::LogFatal(const char *msg, ...) dvander@663: { dvander@665: /* :TODO: make this print all pretty-like dvander@665: * In fact, the pretty log printing function should be abstracted. dvander@665: * It's already implemented twice which is bad. dvander@665: */ dvander@665: dvander@1444: va_list ap; dvander@1638: char path[PLATFORM_MAX_PATH]; dvander@1444: dvander@1638: g_SourceMod.BuildPath(Path_Game, path, sizeof(path), "sourcemod_fatal.log"); dvander@1444: dvander@663: FILE *fp = fopen(path, "at"); dvander@1444: if (fp) dvander@663: { dvander@1638: m_Active = true; dvander@1638: va_start(ap, msg); dvander@1638: LogToOpenFileEx(fp, msg, ap); dvander@1638: va_end(ap); dvander@1638: m_Active = false; dvander@1444: fclose(fp); dvander@663: } dvander@1444: } dvander@663: dvander@1914: bool g_in_game_log_hook = false; dvander@1914: dvander@1914: void Engine_LogPrintWrapper(const char *msg) dvander@1914: { dvander@1914: if (g_in_game_log_hook) dvander@1914: { dvander@1914: ENGINE_CALL(LogPrint)(msg); dvander@1914: } dvander@1914: else dvander@1914: { dvander@1914: engine->LogPrint(msg); dvander@1914: } dvander@1914: }