dvander@592: /**
ds@653: * vim: set ts=4 :
ds@1330: * =============================================================================
dvander@1231: * SourceMod
dvander@1231: * Copyright (C) 2004-2007 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@592: #include "systems/LibrarySys.h"
dvander@1371: #include "TimerSys.h"
dvander@592: #include "sm_version.h"
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@592: g_SMAPI->ConPrint("[SM] Unexpected fatal logging error. SourceMod logging disabled.\n");
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);
ds@738: 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);
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@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:
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);
dvander@1378: g_SMAPI->ConPrintf("L %s: %s\n", date, buffer);
dvander@1378: }
dvander@1378:
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);
ds@738: 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);
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@592: g_SMAPI->ConPrint("[SM] Unexpected fatal logging error. SourceMod logging disabled.\n");
dvander@592: m_Active = false;
dvander@592: }
dvander@592:
dvander@616: void Logger::LogError(const char *vafmt, ...)
dvander@592: {
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: 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: g_SMAPI->ConPrint("[SM] Unexpected fatal logging error. SourceMod logging disabled.\n");
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@592: engine->LogPrint(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@663: char path[PLATFORM_MAX_PATH];
dvander@663: g_SourceMod.BuildPath(Path_Game, path, sizeof(path), "sourcemod_fatal.log");
dvander@663: FILE *fp = fopen(path, "at");
dvander@663: if (!fp)
dvander@663: {
dvander@663: /* We're just doomed, aren't we... */
dvander@663: return;
dvander@663: }
dvander@663:
dvander@663: va_list ap;
dvander@663: va_start(ap, msg);
dvander@663: vfprintf(fp, msg, ap);
dvander@663: va_end(ap);
dvander@663:
dvander@663: fputs("\n", fp);
dvander@663:
dvander@663: fclose(fp);
dvander@663: }