- 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:
SpinDizzy 2020-08-31 09:28:35 +00:00
parent 6491ed46a7
commit 60c923f45f
5 changed files with 554 additions and 190 deletions

View file

@ -6,7 +6,7 @@
** 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
** 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.
**
@ -42,7 +42,7 @@ namespace Debugger
static char s_mSlotStr[32][20];
static char s_sSlotStr[32][20];
CCPUDebug *CSupermodelDebugger::CreateMainBoardCPUDebug(::CModel3 *model3)
{
CPPCDebug *cpu = new CPPCDebug("MainPPC");
@ -254,12 +254,12 @@ namespace Debugger
{
CSoundBoard *sndBrd = model3->GetSoundBoard();
CDSB *dsb = sndBrd->GetDSB();
CDSB1 *dsb1 = dynamic_cast<CDSB1*>(dsb);
if (dsb1 != NULL)
{
CZ80Debug *cpu = new CZ80Debug("DSBZ80", dsb1->GetZ80());
// Regions
cpu->AddRegion(0x0000, 0x7FFF, true, true, "ROM");
cpu->AddRegion(0x8000, 0xFFFF, false, false, "RAM");
@ -267,12 +267,12 @@ namespace Debugger
// TODO - rename some I/O ports
return cpu;
}
CDSB2 *dsb2 = dynamic_cast<CDSB2*>(dsb);
if (dsb2 != NULL)
{
CMusashi68KDebug *cpu = new CMusashi68KDebug("DSB68K", dsb2->GetM68K());
// Regions
cpu->AddRegion(0x000000, 0x020000, true, true, "ROM");
cpu->AddRegion(0xF00000, 0xF10000, false, false, "RAM");
@ -290,7 +290,7 @@ namespace Debugger
if (!drvBrd->IsAttached())
return NULL;
CZ80Debug *cpu = new CZ80Debug("DrvZ80", drvBrd->GetZ80());
// Regions
cpu->AddRegion(0x0000, 0x7FFF, true, true, "ROM");
cpu->AddRegion(0xE000, 0xFFFF, false, false, "RAM");
@ -323,8 +323,8 @@ namespace Debugger
}
#endif
CSupermodelDebugger::CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, ::CLogger *logger) :
CConsoleDebugger(), m_model3(model3), m_inputs(inputs), m_logger(logger),
CSupermodelDebugger::CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, std::shared_ptr<CLogger> logger) :
CConsoleDebugger(), m_model3(model3), m_inputs(inputs), m_logger(logger),
m_loadEmuState(false), m_saveEmuState(false), m_resetEmu(false)
{
//
@ -337,7 +337,7 @@ namespace Debugger
// Add main board CPU
cpu = CreateMainBoardCPUDebug(m_model3);
if (cpu) AddCPU(cpu);
// Add sound board CPU (if attached)
cpu = CreateSoundBoardCPUDebug(m_model3);
if (cpu) AddCPU(cpu);
@ -345,7 +345,7 @@ namespace Debugger
// Add sound daughter board CPU (if attached)
cpu = CreateDSBCPUDebug(m_model3);
if (cpu) AddCPU(cpu);
// Add drive board CPU (if attached)
cpu = CreateDriveBoardCPUDebug(m_model3);
if (cpu) AddCPU(cpu);
@ -533,7 +533,7 @@ namespace Debugger
append = false;
else if (CheckToken(token, "a", "append"))
append = true;
else
else
{
Print("Enter a valid mode (s)et or (a)ppend.\n");
return false;
@ -541,7 +541,7 @@ namespace Debugger
Print("Configure input %s [%s]: %s...", input->label, input->GetMapping(), (append ? "Appending" : "Setting"));
fflush(stdout); // required on terminals that use buffering
// Configure the input
if (input->Configure(append, "KEY_ESCAPE"))
Print(" %s\n", input->GetMapping());
@ -572,11 +572,11 @@ namespace Debugger
Print(fmt, "sip", "setinput", "(<id>|<label>) <mapping>");
Print(fmt, "rip", "resetinput", "(<id>|<label>)");
Print(fmt, "cip", "configinput", "(<id>|<label>) [(s)et|(a)ppend]");
Print(fmt, "caip", "configallinputs", "");
Print(fmt, "caip", "configallinputs", "");
return false;
}
else
return CConsoleDebugger::ProcessToken(token, cmd);
return CConsoleDebugger::ProcessToken(token, cmd);
}
bool CSupermodelDebugger::InputIsValid(::CInput *input)
@ -604,7 +604,7 @@ namespace Debugger
mappingWidth = std::max<size_t>(mappingWidth, strlen(input->GetMapping()));
}
mappingWidth = std::min<size_t>(mappingWidth, 20);
// Print labels, mappings and values for each input
const char *groupLabel = NULL;
char idAndLabel[255];
@ -645,13 +645,13 @@ namespace Debugger
char fileName[25];
sprintf(fileName, "Debug/%s.ds", m_model3->GetGame().name.c_str());
SaveState(fileName);
CConsoleDebugger::Detaching();
}
bool CSupermodelDebugger::LoadModel3State(const char *fileName)
{
// Open file and find header
// Open file and find header
CBlockFile state;
if (state.Load(fileName) != OKAY)
return false;
@ -713,7 +713,7 @@ namespace Debugger
else
CConsoleDebugger::DebugLog(fmt, vl);
}
void CSupermodelDebugger::InfoLog(const char *fmt, va_list vl)
{
// Use the supplied logger, if any
@ -722,7 +722,7 @@ namespace Debugger
else
CConsoleDebugger::InfoLog(fmt, vl);
}
void CSupermodelDebugger::ErrorLog(const char *fmt, va_list vl)
{
// Use the supplied logger, if any

View file

@ -6,7 +6,7 @@
** 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
** 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.
**
@ -49,7 +49,7 @@ namespace Debugger
private:
::CModel3 *m_model3;
::CInputs *m_inputs;
::CLogger *m_logger;
std::shared_ptr<CLogger> m_logger;
bool m_loadEmuState;
bool m_saveEmuState;
@ -73,7 +73,7 @@ namespace Debugger
public:
static CCPUDebug *CreateMainBoardCPUDebug(::CModel3 *model3);
static CCPUDebug *CreateSoundBoardCPUDebug(::CModel3 *model3);
static CCPUDebug *CreateDSBCPUDebug(::CModel3 *model3);
@ -83,10 +83,10 @@ namespace Debugger
static CCPUDebug *CreateNetBoardCPUDebug(::CModel3 *model3);
#endif
CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, ::CLogger *logger);
CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, std::shared_ptr<CLogger> logger);
void Poll();
bool LoadModel3State(const char *fileName);
bool SaveModel3State(const char *fileName);
@ -94,9 +94,9 @@ namespace Debugger
void ResetModel3();
void DebugLog(const char *fmt, va_list vl);
void InfoLog(const char *fmt, va_list vl);
void ErrorLog(const char *fmt, va_list vl);
};
}

View file

@ -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 <set>
#ifdef _WIN32
#include <windows.h>
#else
#include <syslog.h>
#endif
// 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;
}
void SetLogger(CLogger *Logger)
void SetLogger(std::shared_ptr<CLogger> logger)
{
s_Logger = Logger;
s_Logger = logger;
}
void DebugLog(const char *fmt, ...)
{
if (s_Logger == NULL)
if (!s_Logger)
return;
va_list vl;
va_start(vl, fmt);
@ -25,7 +55,7 @@ void DebugLog(const char *fmt, ...)
void InfoLog(const char *fmt, ...)
{
if (s_Logger == NULL)
if (!s_Logger)
return;
va_list vl;
va_start(vl, fmt);
@ -35,7 +65,7 @@ void InfoLog(const char *fmt, ...)
bool ErrorLog(const char *fmt, ...)
{
if (s_Logger == NULL)
if (!s_Logger)
return FAIL;
va_list vl;
va_start(vl, fmt);
@ -43,3 +73,322 @@ bool ErrorLog(const char *fmt, ...)
va_end(vl);
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)
{
}

View file

@ -1,12 +1,13 @@
/**
** Supermodel
** 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.
**
** 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
** 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.
**
@ -23,17 +24,22 @@
* Logger.h
*
* Header file for message logging. The OSD code is expected to set up a
* default logger (CFileLogger).
* default logger (CFileLogger).
*/
#ifndef INCLUDED_LOGGER_H
#define INCLUDED_LOGGER_H
#include "Types.h"
#include "Version.h"
#include "Util/NewConfig.h"
#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <fstream>
#include <memory>
#include <mutex>
#include <vector>
/******************************************************************************
@ -42,7 +48,7 @@
/*
* CLogger
*
*
* Abstract class that receives log messages from Supermodel's log functions.
* The logger object handles actual output of messages. Use the function-based
* message logging interface to generate messages.
@ -50,15 +56,24 @@
class CLogger
{
public:
// Log level in ascending order
enum LogLevel: int
{
All = 0,
Debug,
Info,
Error
};
/*
* DebugLog(fmt, ...):
* DebugLog(fmt, vl):
*
* Prints to debug log. If DEBUG is not defined, will end up doing nothing.
* Prints to debug log. If DEBUG is not defined, will end up doing nothing.
*
* Parameters:
* fmt printf()-style format string.
* ... Variable number of parameters, corresponding to format
* ... Variable number of parameters, corresponding to format
* string.
* vl Variable arguments already stored in a list.
*/
@ -69,7 +84,7 @@ public:
DebugLog(fmt, vl);
va_end(vl);
}
virtual void DebugLog(const char *fmt, va_list vl) = 0;
/*
@ -81,7 +96,7 @@ public:
*
* Parameters:
* fmt printf()-style format string.
* ... Variable number of parameters, corresponding to format
* ... Variable number of parameters, corresponding to format
* string.
* vl Variable arguments already stored in a list.
*/
@ -92,7 +107,7 @@ public:
InfoLog(fmt, vl);
va_end(vl);
}
virtual void InfoLog(const char *fmt, va_list vl) = 0;
/*
@ -103,7 +118,7 @@ public:
*
* Parameters:
* fmt printf()-style format string.
* ... Variable number of parameters, corresponding to format
* ... Variable number of parameters, corresponding to format
* string.
* vl Variable arguments already stored in a list.
*/
@ -114,132 +129,92 @@ public:
ErrorLog(fmt, vl);
va_end(vl);
}
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:
*
*
* Default logger that logs to debug and error log files. Files are opened and
* closed for each message in order to preserve contents in case of program
* crash.
*/
class CFileLogger : public CLogger
*/
class CFileLogger: public CLogger
{
public:
void DebugLog(const char *fmt, va_list vl)
{
#ifdef DEBUG
char string[1024];
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)
{
}
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(LogLevel level, std::vector<std::string> filenames);
CFileLogger(LogLevel level, std::vector<std::string> filenames, std::vector<FILE *> systemFiles);
private:
const char *m_debugLogFile;
const char *m_errorLogFile;
std::mutex m_mtx; // needed because we may close/reopen files and logging must be thread-safe
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;
};
/******************************************************************************
Log Functions
Message logging interface. All messages are passed to the currently active
Message logging interface. All messages are passed to the currently active
logger object.
******************************************************************************/
@ -260,7 +235,7 @@ extern void DebugLog(const char *fmt, ...);
* ErrorLog(fmt, ...):
*
* Prints error information. Errors need not require program termination and
* may simply be informative warnings to the user. Newlines should not be
* may simply be informative warnings to the user. Newlines should not be
* included in the format string -- they are automatically added at the end of
* a line.
*
@ -292,17 +267,27 @@ extern void InfoLog(const char *fmt, ...);
* Sets the logger object to use.
*
* 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):
*
* 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

View file

@ -72,10 +72,6 @@
#include <iostream>
// Log file names
#define DEBUG_LOG_FILE "debug.log"
#define ERROR_LOG_FILE "error.log"
/******************************************************************************
Global Run-time Config
@ -821,9 +817,9 @@ static void SuperSleep(UINT32 time)
******************************************************************************/
#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
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(" -print-games List supported games and quit");
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("Core Options:");
printf(" -ppc-frequency=<freq> PowerPC frequency in MHz [Default: %d]\n", defaultConfig["PowerPCFrequency"].ValueAs<unsigned>());
@ -1449,7 +1447,7 @@ static void Help(void)
puts(" -no-vsync Do not lock to vertical refresh rate");
puts(" -show-fps Display frame rate in window title bar");
puts(" -crosshairs=<n> Crosshairs configuration for gun games:");
puts(" 0=none [Default], 1=P1 only, 2=P2 only, 3=P1 & P2");
puts(" 0=none [Default], 1=P1 only, 2=P2 only, 3=P1 & P2");
puts(" -new3d New 3D engine by Ian Curtis [Default]");
puts(" -quad-rendering Enable proper quad rendering");
puts(" -legacy3d Legacy 3D engine (faster but less accurate)");
@ -1476,8 +1474,8 @@ static void Help(void)
puts("");
#ifdef NET_BOARD
puts("Net Options:");
puts(" -no-net Disable net board emulation (default)");
puts(" -net Enable net board emulation (not working ATM - need -no-threads)");
puts(" -no-net Disable net board emulation [Default]");
puts(" -net Enable net board emulation (requires -no-threads)");
puts("");
#endif
puts("Input Options:");
@ -1503,6 +1501,7 @@ struct ParsedCommandLine
{
Util::Config::Node config = Util::Config::Node("CommandLine");
std::vector<std::string> rom_files;
bool error = false;
bool print_help = false;
bool print_games = false;
bool print_gl_info = false;
@ -1513,6 +1512,14 @@ struct ParsedCommandLine
#ifdef DEBUG
std::string gfx_state;
#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)
@ -1534,7 +1541,9 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
{ "-music-volume", "MusicVolume" },
{ "-balance", "Balance" },
{ "-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
{ // -option
@ -1557,7 +1566,7 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
{ "-show-fps", { "ShowFrameRate", true } },
{ "-no-fps", { "ShowFrameRate", false } },
{ "-new3d", { "New3DEngine", true } },
{ "-quad-rendering", { "QuadRendering", true } },
{ "-quad-rendering", { "QuadRendering", true } },
{ "-legacy3d", { "New3DEngine", false } },
{ "-no-flip-stereo", { "FlipStereo", false } },
{ "-flip-stereo", { "FlipStereo", true } },
@ -1568,8 +1577,8 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
{ "-legacy-scsp", { "LegacySoundDSP", true } },
{ "-new-scsp", { "LegacySoundDSP", false } },
#ifdef NET_BOARD
{ "-net", { "EmulateNet", true } },
{ "-no-net", { "EmulateNet", false } },
{ "-net", { "EmulateNet", true } },
{ "-no-net", { "EmulateNet", false } },
#endif
#ifdef SUPERMODEL_WIN32
{ "-no-force-feedback", { "ForceFeedback", false } },
@ -1591,6 +1600,7 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
if (value.length() == 0)
{
ErrorLog("Argument to '%s' cannot be blank.", option.c_str());
cmd_line.error = true;
continue;
}
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())
{
ErrorLog("'%s' requires an argument.", argv[i]);
cmd_line.error = true;
continue;
}
}
@ -1626,7 +1637,10 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
{
std::vector<std::string> parts = Util::Format(arg).Split('=');
if (parts.size() != 2)
{
ErrorLog("'-res' requires both a width and height (e.g., '-res=496,384').");
cmd_line.error = true;
}
else
{
unsigned x, y;
@ -1638,7 +1652,10 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
cmd_line.config.Set("YResolution", yres);
}
else
{
ErrorLog("'-res' requires both a width and height (e.g., '-res=496,384').");
cmd_line.error = true;
}
}
}
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('=');
if (parts.size() != 2)
{
ErrorLog("'-gfx-state' requires a file name.");
cmd_line.error = true;
}
else
cmd_line.gfx_state = parts[1];
}
#endif
else
{
ErrorLog("Ignoring unrecognized option: %s", argv[i]);
cmd_line.error = true;
}
}
else
cmd_line.rom_files.emplace_back(arg);
@ -1680,10 +1703,6 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv)
int main(int argc, char **argv)
{
#ifdef SUPERMODEL_DEBUGGER
bool cmdEnterDebugger = false;
#endif // SUPERMODEL_DEBUGGER
Title();
if (argc <= 1)
{
@ -1691,17 +1710,30 @@ int main(int argc, char **argv)
return 0;
}
// Create default logger
CFileLogger Logger(DEBUG_LOG_FILE, ERROR_LOG_FILE);
Logger.ClearLogs();
SetLogger(&Logger);
InfoLog("Started as:");
for (int i = 0; i < argc; i++)
InfoLog(" argv[%d] = %s", i, argv[i]);
InfoLog("");
// Before command line is parsed, console logging only
SetLogger(std::make_shared<CConsoleErrorLogger>());
// Load config and parse command line
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)
{
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
}
LogConfig(s_runtime_config);
std::string selectedInputSystem = s_runtime_config["InputSystem"].ValueAs<std::string>();
// Initialize SDL (individual subsystems get initialized later)
if (SDL_Init(0) != 0)
@ -1769,8 +1800,9 @@ int main(int argc, char **argv)
CInputs *Inputs = nullptr;
COutputs *Outputs = nullptr;
#ifdef SUPERMODEL_DEBUGGER
Debugger::CSupermodelDebugger *Debugger = NULL;
std::shared_ptr<Debugger::CSupermodelDebugger> Debugger;
#endif // SUPERMODEL_DEBUGGER
std::string selectedInputSystem = s_runtime_config["InputSystem"].ValueAs<std::string>();
// Create a window
xRes = 496;
@ -1862,15 +1894,13 @@ int main(int argc, char **argv)
// Create Supermodel debugger unless debugging is disabled
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 (cmdEnterDebugger)
if (cmd_line.enter_debugger)
Debugger->ForceBreak(true);
}
// Fire up Supermodel with debugger
exitCode = Supermodel(game, &rom_set, Model3, Inputs, Outputs, Debugger);
if (Debugger != NULL)
delete Debugger;
#else
// Fire up Supermodel
exitCode = Supermodel(game, &rom_set, Model3, Inputs, Outputs);