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@4188: #include "logic_bridge.h"
dvander@2784: #include
dvander@592:
dvander@616: Logger g_Logger;
dvander@592:
dvander@3791: SH_DECL_HOOK1_void(IVEngineServer, LogPrint, SH_NOATTRIB, false, const char *);
dvander@3791:
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@3791: static void HookLogPrint(const char *message)
dvander@3791: {
dvander@3791: g_in_game_log_hook = true;
dvander@3791: bool stopped = logicore.OnLogPrint(message);
dvander@3791: g_in_game_log_hook = false;
dvander@3791:
dvander@3791: if (stopped)
dvander@3791: RETURN_META(MRES_SUPERCEDE);
dvander@3791: }
dvander@3791:
dvander@616: void Logger::OnSourceModStartup(bool late)
dvander@592: {
dvander@663: InitLogger(m_Mode);
dvander@3791:
kyle@3819: SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(HookLogPrint), false);
dvander@592: }
dvander@592:
dvander@616: void Logger::OnSourceModAllShutdown()
dvander@592: {
dvander@592: CloseLogger();
kyle@3819: SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(HookLogPrint), false);
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@4188: 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, SOURCEMOD_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);
matheusavs3@3926: m_NrmCurDay = curtime->tm_mday;
matheusavs3@3926: m_ErrCurDay = 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@3791: va_list ap;
dvander@3791: va_start(ap, vafmt);
dvander@3791: LogMessageEx(vafmt, ap);
dvander@3791: va_end(ap);
dvander@3791: }
dvander@3791:
dvander@3791: void Logger::LogMessageEx(const char *vafmt, va_list ap)
dvander@3791: {
dvander@592: if (!m_Active)
dvander@592: {
dvander@592: return;
dvander@592: }
dvander@592:
ds@661: if (m_Mode == LoggingMode_Game)
dvander@592: {
ds@661: _PrintToGameLog(vafmt, 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 {
matheusavs3@3926: if (m_NrmCurDay != 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);
matheusavs3@3926: m_NrmCurDay = 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@4188: 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, SOURCEMOD_VERSION);
dvander@592: }
dvander@1378: LogToOpenFileEx(fp, vafmt, 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:
matheusavs3@3926: if (curtime->tm_mday != m_ErrCurDay)
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);
matheusavs3@3926: m_ErrCurDay = 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@3872: va_list ap;
dvander@3872: va_start(ap, msg);
dvander@3872: LogFatalEx(msg, ap);
dvander@3872: va_end(ap);
dvander@3872: }
dvander@3872:
dvander@3872: void Logger::LogFatalEx(const char *msg, va_list ap)
dvander@3872: {
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@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: LogToOpenFileEx(fp, msg, 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: }