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