mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-25 15:15:40 +00:00
- Reworked logging system to support output to multiple outputs: files, stdout, stderr, and syslog (OutputDebugString on Windows, syslog on other systems).
- Added ordinal logging level: all, debug, info, error. Compiling with DEBUG defined is no longer necessary for debug logging. Be careful when logging debug level because it produces copious output. - Fixed -enter-debugger options when compiled with SUPERMODEL_DEBUGGER. - Command line parse errors no longer ignored; Supermodel will now exit.
This commit is contained in:
parent
6491ed46a7
commit
60c923f45f
|
@ -323,7 +323,7 @@ namespace Debugger
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CSupermodelDebugger::CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, ::CLogger *logger) :
|
CSupermodelDebugger::CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, std::shared_ptr<CLogger> logger) :
|
||||||
CConsoleDebugger(), m_model3(model3), m_inputs(inputs), m_logger(logger),
|
CConsoleDebugger(), m_model3(model3), m_inputs(inputs), m_logger(logger),
|
||||||
m_loadEmuState(false), m_saveEmuState(false), m_resetEmu(false)
|
m_loadEmuState(false), m_saveEmuState(false), m_resetEmu(false)
|
||||||
{
|
{
|
||||||
|
|
|
@ -49,7 +49,7 @@ namespace Debugger
|
||||||
private:
|
private:
|
||||||
::CModel3 *m_model3;
|
::CModel3 *m_model3;
|
||||||
::CInputs *m_inputs;
|
::CInputs *m_inputs;
|
||||||
::CLogger *m_logger;
|
std::shared_ptr<CLogger> m_logger;
|
||||||
|
|
||||||
bool m_loadEmuState;
|
bool m_loadEmuState;
|
||||||
bool m_saveEmuState;
|
bool m_saveEmuState;
|
||||||
|
@ -83,7 +83,7 @@ namespace Debugger
|
||||||
static CCPUDebug *CreateNetBoardCPUDebug(::CModel3 *model3);
|
static CCPUDebug *CreateNetBoardCPUDebug(::CModel3 *model3);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, ::CLogger *logger);
|
CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, std::shared_ptr<CLogger> logger);
|
||||||
|
|
||||||
void Poll();
|
void Poll();
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,51 @@
|
||||||
|
/**
|
||||||
|
** Supermodel
|
||||||
|
** A Sega Model 3 Arcade Emulator.
|
||||||
|
** Copyright 2011-2020 Bart Trzynadlowski, Nik Henson, Ian Curtis,
|
||||||
|
** Harry Tuttle, and Spindizzi
|
||||||
|
**
|
||||||
|
** This file is part of Supermodel.
|
||||||
|
**
|
||||||
|
** Supermodel is free software: you can redistribute it and/or modify it under
|
||||||
|
** the terms of the GNU General Public License as published by the Free
|
||||||
|
** Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
** any later version.
|
||||||
|
**
|
||||||
|
** Supermodel is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
** more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License along
|
||||||
|
** with Supermodel. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
**/
|
||||||
|
|
||||||
#include "OSD/Logger.h"
|
#include "OSD/Logger.h"
|
||||||
|
#include <set>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <syslog.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// Logger object is used to redirect log messages appropriately
|
// Logger object is used to redirect log messages appropriately
|
||||||
static CLogger *s_Logger = NULL;
|
static std::shared_ptr<CLogger> s_Logger;
|
||||||
|
|
||||||
CLogger *GetLogger()
|
|
||||||
|
std::shared_ptr<CLogger> GetLogger()
|
||||||
{
|
{
|
||||||
return s_Logger;
|
return s_Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetLogger(CLogger *Logger)
|
void SetLogger(std::shared_ptr<CLogger> logger)
|
||||||
{
|
{
|
||||||
s_Logger = Logger;
|
s_Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugLog(const char *fmt, ...)
|
void DebugLog(const char *fmt, ...)
|
||||||
{
|
{
|
||||||
if (s_Logger == NULL)
|
if (!s_Logger)
|
||||||
return;
|
return;
|
||||||
va_list vl;
|
va_list vl;
|
||||||
va_start(vl, fmt);
|
va_start(vl, fmt);
|
||||||
|
@ -25,7 +55,7 @@ void DebugLog(const char *fmt, ...)
|
||||||
|
|
||||||
void InfoLog(const char *fmt, ...)
|
void InfoLog(const char *fmt, ...)
|
||||||
{
|
{
|
||||||
if (s_Logger == NULL)
|
if (!s_Logger)
|
||||||
return;
|
return;
|
||||||
va_list vl;
|
va_list vl;
|
||||||
va_start(vl, fmt);
|
va_start(vl, fmt);
|
||||||
|
@ -35,7 +65,7 @@ void InfoLog(const char *fmt, ...)
|
||||||
|
|
||||||
bool ErrorLog(const char *fmt, ...)
|
bool ErrorLog(const char *fmt, ...)
|
||||||
{
|
{
|
||||||
if (s_Logger == NULL)
|
if (!s_Logger)
|
||||||
return FAIL;
|
return FAIL;
|
||||||
va_list vl;
|
va_list vl;
|
||||||
va_start(vl, fmt);
|
va_start(vl, fmt);
|
||||||
|
@ -43,3 +73,322 @@ bool ErrorLog(const char *fmt, ...)
|
||||||
va_end(vl);
|
va_end(vl);
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::pair<bool, CLogger::LogLevel> GetLogLevel(const Util::Config::Node &config)
|
||||||
|
{
|
||||||
|
const std::map<std::string, CLogger::LogLevel> logLevelByString
|
||||||
|
{
|
||||||
|
{ "debug", CLogger::LogLevel::Debug },
|
||||||
|
{ "info", CLogger::LogLevel::Info },
|
||||||
|
{ "error", CLogger::LogLevel::Error },
|
||||||
|
{ "all", CLogger::LogLevel::All },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string logLevel = Util::ToLower(config["LogLevel"].ValueAsDefault<std::string>("info"));
|
||||||
|
|
||||||
|
auto it = logLevelByString.find(logLevel);
|
||||||
|
if (it != logLevelByString.end())
|
||||||
|
{
|
||||||
|
return std::pair<bool, CLogger::LogLevel>(OKAY, it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLog("Invalid log level: %s", logLevel.c_str());
|
||||||
|
return std::pair<bool, CLogger::LogLevel>(FAIL, CLogger::LogLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CLogger> CreateLogger(const Util::Config::Node &config)
|
||||||
|
{
|
||||||
|
std::vector<std::shared_ptr<CLogger>> loggers;
|
||||||
|
|
||||||
|
// Get log level
|
||||||
|
auto logLevelResult = GetLogLevel(config);
|
||||||
|
if (logLevelResult.first != OKAY)
|
||||||
|
{
|
||||||
|
return std::shared_ptr<CLogger>();
|
||||||
|
}
|
||||||
|
CLogger::LogLevel logLevel = logLevelResult.second;
|
||||||
|
|
||||||
|
// Console message logger always required
|
||||||
|
loggers.push_back(std::make_shared<CConsoleErrorLogger>());
|
||||||
|
|
||||||
|
// Parse other log outputs
|
||||||
|
std::string logOutputs = config["LogOutput"].ValueAsDefault<std::string>("");
|
||||||
|
std::vector<std::string> outputs = Util::Format(logOutputs).Split(',');
|
||||||
|
|
||||||
|
std::set<std::string> supportedDestinations { "stdout", "stderr", "syslog" };
|
||||||
|
std::set<std::string> destinations; // log output destinations
|
||||||
|
std::set<std::string> filenames; // anything that is not a known destination is assumed to be a file
|
||||||
|
for (auto output: outputs)
|
||||||
|
{
|
||||||
|
// Is this a known destination or a file?
|
||||||
|
std::string canonicalizedOutput = Util::TrimWhiteSpace(Util::ToLower(output));
|
||||||
|
if (supportedDestinations.count(canonicalizedOutput) > 0)
|
||||||
|
{
|
||||||
|
destinations.insert(canonicalizedOutput);
|
||||||
|
}
|
||||||
|
else if (!canonicalizedOutput.empty())
|
||||||
|
{
|
||||||
|
filenames.insert(Util::TrimWhiteSpace(output)); // trim whitespace but preserve capitalization of filenames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// File logger (if any files were specified)
|
||||||
|
std::vector<std::string> logFilenames(filenames.begin(), filenames.end());
|
||||||
|
std::vector<FILE *> systemFiles;
|
||||||
|
|
||||||
|
if (destinations.count("stdout") > 0)
|
||||||
|
{
|
||||||
|
systemFiles.push_back(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destinations.count("stderr") > 0)
|
||||||
|
{
|
||||||
|
systemFiles.push_back(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!logFilenames.empty() || !systemFiles.empty())
|
||||||
|
{
|
||||||
|
loggers.push_back(std::make_shared<CFileLogger>(logLevel, logFilenames, systemFiles));
|
||||||
|
}
|
||||||
|
|
||||||
|
// System logger
|
||||||
|
if (destinations.count("syslog") > 0)
|
||||||
|
{
|
||||||
|
loggers.push_back(std::make_shared<CSystemLogger>(logLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_shared<CMultiLogger>(loggers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CMultiLogger
|
||||||
|
*/
|
||||||
|
|
||||||
|
void CMultiLogger::DebugLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
for (auto &logger: m_loggers)
|
||||||
|
{
|
||||||
|
logger->DebugLog(fmt, vl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMultiLogger::InfoLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
for (auto &logger: m_loggers)
|
||||||
|
{
|
||||||
|
logger->InfoLog(fmt, vl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMultiLogger::ErrorLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
for (auto &logger: m_loggers)
|
||||||
|
{
|
||||||
|
logger->ErrorLog(fmt, vl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CMultiLogger::CMultiLogger(std::vector<std::shared_ptr<CLogger>> loggers)
|
||||||
|
: m_loggers(loggers)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CConsoleErrorLogger
|
||||||
|
*/
|
||||||
|
|
||||||
|
void CConsoleErrorLogger::DebugLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
// To view debug-level logging on the console, use a file logger writing
|
||||||
|
// to stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConsoleErrorLogger::InfoLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
// To view info-level logging on the console, use a file logger writing
|
||||||
|
// to stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
void CConsoleErrorLogger::ErrorLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
char string[4096];
|
||||||
|
vsprintf(string, fmt, vl);
|
||||||
|
fprintf(stderr, "Error: %s\n", string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CFileLogger
|
||||||
|
*/
|
||||||
|
|
||||||
|
void CFileLogger::DebugLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
if (m_logLevel > LogLevel::Debug)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char string1[4096];
|
||||||
|
char string2[4096];
|
||||||
|
|
||||||
|
vsprintf(string1, fmt, vl);
|
||||||
|
sprintf(string2, "[Debug] %s", string1);
|
||||||
|
|
||||||
|
// Debug logging is so copious that we don't bother to guarantee it is saved
|
||||||
|
std::unique_lock<std::mutex> lock(m_mtx);
|
||||||
|
WriteToFiles(string2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFileLogger::InfoLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
if (m_logLevel > LogLevel::Info)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char string1[4096];
|
||||||
|
char string2[4096];
|
||||||
|
|
||||||
|
vsprintf(string1, fmt, vl);
|
||||||
|
sprintf(string2, "[Info] %s\n", string1);
|
||||||
|
|
||||||
|
// Write to file, close, and reopen to ensure it was saved
|
||||||
|
std::unique_lock<std::mutex> lock(m_mtx);
|
||||||
|
WriteToFiles(string2);
|
||||||
|
ReopenFiles(std::ios::app);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFileLogger::ErrorLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
if (m_logLevel > LogLevel::Error)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char string1[4096];
|
||||||
|
char string2[4096];
|
||||||
|
|
||||||
|
vsprintf(string1, fmt, vl);
|
||||||
|
sprintf(string2, "[Error] %s\n", string1);
|
||||||
|
|
||||||
|
// Write to file, close, and reopen to ensure it was saved
|
||||||
|
std::unique_lock<std::mutex> lock(m_mtx);
|
||||||
|
WriteToFiles(string2);
|
||||||
|
ReopenFiles(std::ios::app);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFileLogger::ReopenFiles(std::ios_base::openmode mode)
|
||||||
|
{
|
||||||
|
// Close existing
|
||||||
|
for (std::ofstream &ofs: m_logFiles)
|
||||||
|
{
|
||||||
|
ofs.close();
|
||||||
|
}
|
||||||
|
m_logFiles.clear();
|
||||||
|
|
||||||
|
// (Re-)Open
|
||||||
|
for (auto filename: m_logFilenames)
|
||||||
|
{
|
||||||
|
std::ofstream ofs(filename.c_str(), mode);
|
||||||
|
if (ofs.is_open() && ofs.good())
|
||||||
|
{
|
||||||
|
m_logFiles.emplace_back(std::move(ofs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFileLogger::WriteToFiles(const char *str)
|
||||||
|
{
|
||||||
|
for (std::ofstream &ofs: m_logFiles)
|
||||||
|
{
|
||||||
|
ofs << str;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (FILE *fp: m_systemFiles)
|
||||||
|
{
|
||||||
|
fputs(str, fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CFileLogger::CFileLogger(CLogger::LogLevel level, std::vector<std::string> filenames)
|
||||||
|
: m_logLevel(level),
|
||||||
|
m_logFilenames(filenames)
|
||||||
|
{
|
||||||
|
ReopenFiles(std::ios::out);
|
||||||
|
}
|
||||||
|
|
||||||
|
CFileLogger::CFileLogger(CLogger::LogLevel level, std::vector<std::string> filenames, std::vector<FILE *> systemFiles)
|
||||||
|
: m_logLevel(level),
|
||||||
|
m_logFilenames(filenames),
|
||||||
|
m_systemFiles(systemFiles)
|
||||||
|
{
|
||||||
|
ReopenFiles(std::ios::out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CSystemLogger
|
||||||
|
*/
|
||||||
|
|
||||||
|
void CSystemLogger::DebugLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
if (m_logLevel > LogLevel::Debug)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char string1[4096];
|
||||||
|
char string2[4096];
|
||||||
|
|
||||||
|
vsprintf(string1, fmt, vl);
|
||||||
|
sprintf(string2, "[Debug] %s", string1);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
OutputDebugString(string2);
|
||||||
|
#else
|
||||||
|
syslog(LOG_DEBUG, string2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSystemLogger::InfoLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
if (m_logLevel > LogLevel::Info)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char string1[4096];
|
||||||
|
char string2[4096];
|
||||||
|
|
||||||
|
vsprintf(string1, fmt, vl);
|
||||||
|
sprintf(string2, "[Info] %s\n", string1);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
OutputDebugString(string2);
|
||||||
|
#else
|
||||||
|
syslog(LOG_INFO, string2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSystemLogger::ErrorLog(const char *fmt, va_list vl)
|
||||||
|
{
|
||||||
|
if (m_logLevel > LogLevel::Error)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char string1[4096];
|
||||||
|
char string2[4096];
|
||||||
|
|
||||||
|
vsprintf(string1, fmt, vl);
|
||||||
|
sprintf(string2, "[Error] %s\n", string1);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
OutputDebugString(string2);
|
||||||
|
#else
|
||||||
|
syslog(LOG_ERR, string2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
CSystemLogger::CSystemLogger(CLogger::LogLevel level)
|
||||||
|
: m_logLevel(level)
|
||||||
|
{
|
||||||
|
}
|
203
Src/OSD/Logger.h
203
Src/OSD/Logger.h
|
@ -1,7 +1,8 @@
|
||||||
/**
|
/**
|
||||||
** Supermodel
|
** Supermodel
|
||||||
** A Sega Model 3 Arcade Emulator.
|
** A Sega Model 3 Arcade Emulator.
|
||||||
** Copyright 2011 Bart Trzynadlowski, Nik Henson
|
** Copyright 2011-2020 Bart Trzynadlowski, Nik Henson, Ian Curtis,
|
||||||
|
** Harry Tuttle, and Spindizzi
|
||||||
**
|
**
|
||||||
** This file is part of Supermodel.
|
** This file is part of Supermodel.
|
||||||
**
|
**
|
||||||
|
@ -31,9 +32,14 @@
|
||||||
|
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
#include "Version.h"
|
#include "Version.h"
|
||||||
|
#include "Util/NewConfig.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
|
@ -50,6 +56,15 @@
|
||||||
class CLogger
|
class CLogger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// Log level in ascending order
|
||||||
|
enum LogLevel: int
|
||||||
|
{
|
||||||
|
All = 0,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Error
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DebugLog(fmt, ...):
|
* DebugLog(fmt, ...):
|
||||||
* DebugLog(fmt, vl):
|
* DebugLog(fmt, vl):
|
||||||
|
@ -118,6 +133,39 @@ public:
|
||||||
virtual void ErrorLog(const char *fmt, va_list vl) = 0;
|
virtual void ErrorLog(const char *fmt, va_list vl) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CMultiLogger:
|
||||||
|
*
|
||||||
|
* Redirects to multiple loggers.
|
||||||
|
*/
|
||||||
|
class CMultiLogger: public CLogger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void DebugLog(const char *fmt, va_list vl);
|
||||||
|
void InfoLog(const char *fmt, va_list vl);
|
||||||
|
void ErrorLog(const char *fmt, va_list vl);
|
||||||
|
CMultiLogger(std::vector<std::shared_ptr<CLogger>> loggers);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::shared_ptr<CLogger>> m_loggers;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CConsoleErrorLogger:
|
||||||
|
*
|
||||||
|
* Logger that prints only essential error messages, formatted appropriately,
|
||||||
|
* to the console (stderr).
|
||||||
|
*
|
||||||
|
* In the future, logging and console output need to be separated. This is
|
||||||
|
* intended to be an interrim solution.
|
||||||
|
*/
|
||||||
|
class CConsoleErrorLogger: public CLogger
|
||||||
|
{
|
||||||
|
void DebugLog(const char *fmt, va_list vl);
|
||||||
|
void InfoLog(const char *fmt, va_list vl);
|
||||||
|
void ErrorLog(const char *fmt, va_list vl);
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* CFileLogger:
|
* CFileLogger:
|
||||||
*
|
*
|
||||||
|
@ -125,114 +173,41 @@ public:
|
||||||
* closed for each message in order to preserve contents in case of program
|
* closed for each message in order to preserve contents in case of program
|
||||||
* crash.
|
* crash.
|
||||||
*/
|
*/
|
||||||
class CFileLogger : public CLogger
|
class CFileLogger: public CLogger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
void DebugLog(const char *fmt, va_list vl);
|
||||||
void DebugLog(const char *fmt, va_list vl)
|
void InfoLog(const char *fmt, va_list vl);
|
||||||
{
|
void ErrorLog(const char *fmt, va_list vl);
|
||||||
#ifdef DEBUG
|
CFileLogger(LogLevel level, std::vector<std::string> filenames);
|
||||||
char string[1024];
|
CFileLogger(LogLevel level, std::vector<std::string> filenames, std::vector<FILE *> systemFiles);
|
||||||
FILE *fp;
|
|
||||||
|
|
||||||
fp = fopen(m_debugLogFile, "a");
|
|
||||||
if (NULL != fp)
|
|
||||||
{
|
|
||||||
vsprintf(string, fmt, vl);
|
|
||||||
fprintf(fp, string);
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
#endif // DEBUG
|
|
||||||
}
|
|
||||||
|
|
||||||
void InfoLog(const char *fmt, va_list vl)
|
|
||||||
{
|
|
||||||
char string[4096];
|
|
||||||
FILE *fp;
|
|
||||||
|
|
||||||
vsprintf(string, fmt, vl);
|
|
||||||
|
|
||||||
fp = fopen(m_errorLogFile, "a");
|
|
||||||
if (NULL != fp)
|
|
||||||
{
|
|
||||||
fprintf(fp, "%s\n", string);
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
CLogger::DebugLog("Info: ");
|
|
||||||
CLogger::DebugLog(string);
|
|
||||||
CLogger::DebugLog("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrorLog(const char *fmt, va_list vl)
|
|
||||||
{
|
|
||||||
char string[4096];
|
|
||||||
FILE *fp;
|
|
||||||
|
|
||||||
vsprintf(string, fmt, vl);
|
|
||||||
fprintf(stderr, "Error: %s\n", string);
|
|
||||||
|
|
||||||
fp = fopen(m_errorLogFile, "a");
|
|
||||||
if (NULL != fp)
|
|
||||||
{
|
|
||||||
fprintf(fp, "%s\n", string);
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
CLogger::DebugLog("Error: ");
|
|
||||||
CLogger::DebugLog(string);
|
|
||||||
CLogger::DebugLog("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ClearLogs():
|
|
||||||
*
|
|
||||||
* Clears all log files.
|
|
||||||
*/
|
|
||||||
void ClearLogs(void)
|
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
|
||||||
ClearLog(m_debugLogFile, "Supermodel v" SUPERMODEL_VERSION " Debug Log");
|
|
||||||
#endif // DEBUG
|
|
||||||
ClearLog(m_errorLogFile, "Supermodel v" SUPERMODEL_VERSION " Error Log");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ClearLog(file, title):
|
|
||||||
*
|
|
||||||
* Clears a log file.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* file File name.
|
|
||||||
* title A string that is written to the file after it is cleared.
|
|
||||||
*/
|
|
||||||
void ClearLog(const char *file, const char *title)
|
|
||||||
{
|
|
||||||
FILE *fp = fopen(file, "w");
|
|
||||||
if (NULL != fp)
|
|
||||||
{
|
|
||||||
unsigned i;
|
|
||||||
fprintf(fp, "%s\n", title);
|
|
||||||
for (i = 0; i < strlen(title); i++)
|
|
||||||
fputc('-', fp);
|
|
||||||
fprintf(fp, "\n\n");
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* CFileLogger(debugLogFile, errorLogFile):
|
|
||||||
*
|
|
||||||
* Constructor. Specifies debug and error log files to use.
|
|
||||||
*/
|
|
||||||
CFileLogger(const char *debugLogFile, const char *errorLogFile) :
|
|
||||||
m_debugLogFile(debugLogFile), m_errorLogFile(errorLogFile)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const char *m_debugLogFile;
|
std::mutex m_mtx; // needed because we may close/reopen files and logging must be thread-safe
|
||||||
const char *m_errorLogFile;
|
LogLevel m_logLevel;
|
||||||
|
const std::vector<std::string> m_logFilenames;
|
||||||
|
std::vector<std::ofstream> m_logFiles;
|
||||||
|
std::vector<FILE *> m_systemFiles;
|
||||||
|
|
||||||
|
void ReopenFiles(std::ios_base::openmode mode);
|
||||||
|
void WriteToFiles(const char *str);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CSystemLogger:
|
||||||
|
*
|
||||||
|
* Logs to the system log.
|
||||||
|
*/
|
||||||
|
class CSystemLogger: public CLogger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void DebugLog(const char *fmt, va_list vl);
|
||||||
|
void InfoLog(const char *fmt, va_list vl);
|
||||||
|
void ErrorLog(const char *fmt, va_list vl);
|
||||||
|
CSystemLogger(LogLevel level);
|
||||||
|
|
||||||
|
private:
|
||||||
|
LogLevel m_logLevel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -292,17 +267,27 @@ extern void InfoLog(const char *fmt, ...);
|
||||||
* Sets the logger object to use.
|
* Sets the logger object to use.
|
||||||
*
|
*
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* Logger New logger object. If NULL, log messages will not be output.
|
* Logger Unique pointer to a new logger object. If null pointer, log
|
||||||
|
* messages will not be output.
|
||||||
*/
|
*/
|
||||||
extern void SetLogger(CLogger *Logger);
|
extern void SetLogger(std::shared_ptr<CLogger> logger);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GetLogger(void):
|
* GetLogger(void):
|
||||||
*
|
*
|
||||||
* Returns:
|
* Returns:
|
||||||
* Current logger object (NULL if none has been set).
|
* Current logger object (null pointer if not set).
|
||||||
*/
|
*/
|
||||||
extern CLogger *GetLogger(void);
|
extern std::shared_ptr<CLogger> GetLogger(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CreateLogger(config):
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* A logger object that satisfies the requirements specified in the passed
|
||||||
|
* configuration or an empty pointer if an unrecoverable error occurred.
|
||||||
|
*/
|
||||||
|
std::shared_ptr<CLogger> CreateLogger(const Util::Config::Node &config);
|
||||||
|
|
||||||
|
|
||||||
#endif // INCLUDED_LOGGER_H
|
#endif // INCLUDED_LOGGER_H
|
||||||
|
|
|
@ -72,10 +72,6 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
// Log file names
|
|
||||||
#define DEBUG_LOG_FILE "debug.log"
|
|
||||||
#define ERROR_LOG_FILE "error.log"
|
|
||||||
|
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
Global Run-time Config
|
Global Run-time Config
|
||||||
|
@ -821,9 +817,9 @@ static void SuperSleep(UINT32 time)
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef SUPERMODEL_DEBUGGER
|
#ifdef SUPERMODEL_DEBUGGER
|
||||||
int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *Inputs, COutputs *Outputs, Debugger::CDebugger *Debugger)
|
int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *Inputs, COutputs *Outputs, std::shared_ptr<Debugger::CDebugger> Debugger)
|
||||||
{
|
{
|
||||||
CLogger *oldLogger = 0;
|
std::shared_ptr<CLogger> oldLogger;
|
||||||
#else
|
#else
|
||||||
int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *Inputs, COutputs *Outputs)
|
int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *Inputs, COutputs *Outputs)
|
||||||
{
|
{
|
||||||
|
@ -1430,6 +1426,8 @@ static void Help(void)
|
||||||
puts(" -?, -h, -help, --help Print this help text");
|
puts(" -?, -h, -help, --help Print this help text");
|
||||||
puts(" -print-games List supported games and quit");
|
puts(" -print-games List supported games and quit");
|
||||||
printf(" -game-xml-file=<file> ROM set definition file [Default: %s]\n", s_gameXMLFilePath);
|
printf(" -game-xml-file=<file> ROM set definition file [Default: %s]\n", s_gameXMLFilePath);
|
||||||
|
puts(" -log-output=<outputs> Log output destination(s) [Default: Supermodel.log]");
|
||||||
|
puts(" -log-level=<level> Logging threshold [Default: info]");
|
||||||
puts("");
|
puts("");
|
||||||
puts("Core Options:");
|
puts("Core Options:");
|
||||||
printf(" -ppc-frequency=<freq> PowerPC frequency in MHz [Default: %d]\n", defaultConfig["PowerPCFrequency"].ValueAs<unsigned>());
|
printf(" -ppc-frequency=<freq> PowerPC frequency in MHz [Default: %d]\n", defaultConfig["PowerPCFrequency"].ValueAs<unsigned>());
|
||||||
|
@ -1476,8 +1474,8 @@ static void Help(void)
|
||||||
puts("");
|
puts("");
|
||||||
#ifdef NET_BOARD
|
#ifdef NET_BOARD
|
||||||
puts("Net Options:");
|
puts("Net Options:");
|
||||||
puts(" -no-net Disable net board emulation (default)");
|
puts(" -no-net Disable net board emulation [Default]");
|
||||||
puts(" -net Enable net board emulation (not working ATM - need -no-threads)");
|
puts(" -net Enable net board emulation (requires -no-threads)");
|
||||||
puts("");
|
puts("");
|
||||||
#endif
|
#endif
|
||||||
puts("Input Options:");
|
puts("Input Options:");
|
||||||
|
@ -1503,6 +1501,7 @@ struct ParsedCommandLine
|
||||||
{
|
{
|
||||||
Util::Config::Node config = Util::Config::Node("CommandLine");
|
Util::Config::Node config = Util::Config::Node("CommandLine");
|
||||||
std::vector<std::string> rom_files;
|
std::vector<std::string> rom_files;
|
||||||
|
bool error = false;
|
||||||
bool print_help = false;
|
bool print_help = false;
|
||||||
bool print_games = false;
|
bool print_games = false;
|
||||||
bool print_gl_info = false;
|
bool print_gl_info = false;
|
||||||
|
@ -1513,6 +1512,14 @@ struct ParsedCommandLine
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
std::string gfx_state;
|
std::string gfx_state;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
ParsedCommandLine()
|
||||||
|
{
|
||||||
|
// Logging is special: it is only parsed from the command line and
|
||||||
|
// therefore, defaults are needed early
|
||||||
|
config.Set("LogOutput", "Supermodel.log");
|
||||||
|
config.Set("LogLevel", "info");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static ParsedCommandLine ParseCommandLine(int argc, char **argv)
|
static ParsedCommandLine ParseCommandLine(int argc, char **argv)
|
||||||
|
@ -1534,7 +1541,9 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
|
||||||
{ "-music-volume", "MusicVolume" },
|
{ "-music-volume", "MusicVolume" },
|
||||||
{ "-balance", "Balance" },
|
{ "-balance", "Balance" },
|
||||||
{ "-input-system", "InputSystem" },
|
{ "-input-system", "InputSystem" },
|
||||||
{ "-outputs", "Outputs" }
|
{ "-outputs", "Outputs" },
|
||||||
|
{ "-log-output", "LogOutput" },
|
||||||
|
{ "-log-level", "LogLevel" }
|
||||||
};
|
};
|
||||||
const std::map<std::string, std::pair<std::string, bool>> bool_options
|
const std::map<std::string, std::pair<std::string, bool>> bool_options
|
||||||
{ // -option
|
{ // -option
|
||||||
|
@ -1591,6 +1600,7 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
|
||||||
if (value.length() == 0)
|
if (value.length() == 0)
|
||||||
{
|
{
|
||||||
ErrorLog("Argument to '%s' cannot be blank.", option.c_str());
|
ErrorLog("Argument to '%s' cannot be blank.", option.c_str());
|
||||||
|
cmd_line.error = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto it = valued_options.find(option);
|
auto it = valued_options.find(option);
|
||||||
|
@ -1614,6 +1624,7 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
|
||||||
else if (valued_options.find(arg) != valued_options.end())
|
else if (valued_options.find(arg) != valued_options.end())
|
||||||
{
|
{
|
||||||
ErrorLog("'%s' requires an argument.", argv[i]);
|
ErrorLog("'%s' requires an argument.", argv[i]);
|
||||||
|
cmd_line.error = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1626,7 +1637,10 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
|
||||||
{
|
{
|
||||||
std::vector<std::string> parts = Util::Format(arg).Split('=');
|
std::vector<std::string> parts = Util::Format(arg).Split('=');
|
||||||
if (parts.size() != 2)
|
if (parts.size() != 2)
|
||||||
|
{
|
||||||
ErrorLog("'-res' requires both a width and height (e.g., '-res=496,384').");
|
ErrorLog("'-res' requires both a width and height (e.g., '-res=496,384').");
|
||||||
|
cmd_line.error = true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
unsigned x, y;
|
unsigned x, y;
|
||||||
|
@ -1638,7 +1652,10 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
|
||||||
cmd_line.config.Set("YResolution", yres);
|
cmd_line.config.Set("YResolution", yres);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
ErrorLog("'-res' requires both a width and height (e.g., '-res=496,384').");
|
ErrorLog("'-res' requires both a width and height (e.g., '-res=496,384').");
|
||||||
|
cmd_line.error = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (arg == "-print-gl-info")
|
else if (arg == "-print-gl-info")
|
||||||
|
@ -1658,13 +1675,19 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
|
||||||
{
|
{
|
||||||
std::vector<std::string> parts = Util::Format(arg).Split('=');
|
std::vector<std::string> parts = Util::Format(arg).Split('=');
|
||||||
if (parts.size() != 2)
|
if (parts.size() != 2)
|
||||||
|
{
|
||||||
ErrorLog("'-gfx-state' requires a file name.");
|
ErrorLog("'-gfx-state' requires a file name.");
|
||||||
|
cmd_line.error = true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
cmd_line.gfx_state = parts[1];
|
cmd_line.gfx_state = parts[1];
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
else
|
else
|
||||||
|
{
|
||||||
ErrorLog("Ignoring unrecognized option: %s", argv[i]);
|
ErrorLog("Ignoring unrecognized option: %s", argv[i]);
|
||||||
|
cmd_line.error = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
cmd_line.rom_files.emplace_back(arg);
|
cmd_line.rom_files.emplace_back(arg);
|
||||||
|
@ -1680,10 +1703,6 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
#ifdef SUPERMODEL_DEBUGGER
|
|
||||||
bool cmdEnterDebugger = false;
|
|
||||||
#endif // SUPERMODEL_DEBUGGER
|
|
||||||
|
|
||||||
Title();
|
Title();
|
||||||
if (argc <= 1)
|
if (argc <= 1)
|
||||||
{
|
{
|
||||||
|
@ -1691,17 +1710,30 @@ int main(int argc, char **argv)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create default logger
|
// Before command line is parsed, console logging only
|
||||||
CFileLogger Logger(DEBUG_LOG_FILE, ERROR_LOG_FILE);
|
SetLogger(std::make_shared<CConsoleErrorLogger>());
|
||||||
Logger.ClearLogs();
|
|
||||||
SetLogger(&Logger);
|
|
||||||
InfoLog("Started as:");
|
|
||||||
for (int i = 0; i < argc; i++)
|
|
||||||
InfoLog(" argv[%d] = %s", i, argv[i]);
|
|
||||||
InfoLog("");
|
|
||||||
|
|
||||||
// Load config and parse command line
|
// Load config and parse command line
|
||||||
auto cmd_line = ParseCommandLine(argc, argv);
|
auto cmd_line = ParseCommandLine(argc, argv);
|
||||||
|
if (cmd_line.error)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create logger as specified by command line
|
||||||
|
auto logger = CreateLogger(cmd_line.config);
|
||||||
|
if (!logger)
|
||||||
|
{
|
||||||
|
ErrorLog("Unable to initialize logging system.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
SetLogger(logger);
|
||||||
|
InfoLog("Supermodel Version " SUPERMODEL_VERSION);
|
||||||
|
InfoLog("Started as:");
|
||||||
|
for (int i = 0; i < argc; i++)
|
||||||
|
InfoLog(" argv[%d] = %s", i, argv[i]);
|
||||||
|
|
||||||
|
// Finish processing command line
|
||||||
if (cmd_line.print_help)
|
if (cmd_line.print_help)
|
||||||
{
|
{
|
||||||
Help();
|
Help();
|
||||||
|
@ -1753,7 +1785,6 @@ int main(int argc, char **argv)
|
||||||
Util::Config::MergeINISections(&s_runtime_config, config4, cmd_line.config); // apply command line overrides once more
|
Util::Config::MergeINISections(&s_runtime_config, config4, cmd_line.config); // apply command line overrides once more
|
||||||
}
|
}
|
||||||
LogConfig(s_runtime_config);
|
LogConfig(s_runtime_config);
|
||||||
std::string selectedInputSystem = s_runtime_config["InputSystem"].ValueAs<std::string>();
|
|
||||||
|
|
||||||
// Initialize SDL (individual subsystems get initialized later)
|
// Initialize SDL (individual subsystems get initialized later)
|
||||||
if (SDL_Init(0) != 0)
|
if (SDL_Init(0) != 0)
|
||||||
|
@ -1769,8 +1800,9 @@ int main(int argc, char **argv)
|
||||||
CInputs *Inputs = nullptr;
|
CInputs *Inputs = nullptr;
|
||||||
COutputs *Outputs = nullptr;
|
COutputs *Outputs = nullptr;
|
||||||
#ifdef SUPERMODEL_DEBUGGER
|
#ifdef SUPERMODEL_DEBUGGER
|
||||||
Debugger::CSupermodelDebugger *Debugger = NULL;
|
std::shared_ptr<Debugger::CSupermodelDebugger> Debugger;
|
||||||
#endif // SUPERMODEL_DEBUGGER
|
#endif // SUPERMODEL_DEBUGGER
|
||||||
|
std::string selectedInputSystem = s_runtime_config["InputSystem"].ValueAs<std::string>();
|
||||||
|
|
||||||
// Create a window
|
// Create a window
|
||||||
xRes = 496;
|
xRes = 496;
|
||||||
|
@ -1862,15 +1894,13 @@ int main(int argc, char **argv)
|
||||||
// Create Supermodel debugger unless debugging is disabled
|
// Create Supermodel debugger unless debugging is disabled
|
||||||
if (!cmd_line.disable_debugger)
|
if (!cmd_line.disable_debugger)
|
||||||
{
|
{
|
||||||
Debugger = new Debugger::CSupermodelDebugger(dynamic_cast<CModel3 *>(Model3), Inputs, &Logger);
|
Debugger = std::make_shared<Debugger::CSupermodelDebugger>(dynamic_cast<CModel3 *>(Model3), Inputs, logger);
|
||||||
// If -enter-debugger option was set force debugger to break straightaway
|
// If -enter-debugger option was set force debugger to break straightaway
|
||||||
if (cmdEnterDebugger)
|
if (cmd_line.enter_debugger)
|
||||||
Debugger->ForceBreak(true);
|
Debugger->ForceBreak(true);
|
||||||
}
|
}
|
||||||
// Fire up Supermodel with debugger
|
// Fire up Supermodel with debugger
|
||||||
exitCode = Supermodel(game, &rom_set, Model3, Inputs, Outputs, Debugger);
|
exitCode = Supermodel(game, &rom_set, Model3, Inputs, Outputs, Debugger);
|
||||||
if (Debugger != NULL)
|
|
||||||
delete Debugger;
|
|
||||||
#else
|
#else
|
||||||
// Fire up Supermodel
|
// Fire up Supermodel
|
||||||
exitCode = Supermodel(game, &rom_set, Model3, Inputs, Outputs);
|
exitCode = Supermodel(game, &rom_set, Model3, Inputs, Outputs);
|
||||||
|
|
Loading…
Reference in a new issue