mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-24 22:55:40 +00:00
1bb7de1dd9
vsprintf may change its va_list argument so repeatedly calling it with the same va_list arg is undefined behavior. Fix this by creating a copy of the va_list argument before each vsprintf call.
404 lines
9.1 KiB
C++
404 lines
9.1 KiB
C++
/**
|
|
** 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 std::shared_ptr<CLogger> s_Logger;
|
|
|
|
|
|
std::shared_ptr<CLogger> GetLogger()
|
|
{
|
|
return s_Logger;
|
|
}
|
|
|
|
void SetLogger(std::shared_ptr<CLogger> logger)
|
|
{
|
|
s_Logger = logger;
|
|
}
|
|
|
|
void DebugLog(const char *fmt, ...)
|
|
{
|
|
if (!s_Logger)
|
|
return;
|
|
va_list vl;
|
|
va_start(vl, fmt);
|
|
s_Logger->DebugLog(fmt, vl);
|
|
va_end(vl);
|
|
}
|
|
|
|
void InfoLog(const char *fmt, ...)
|
|
{
|
|
if (!s_Logger)
|
|
return;
|
|
va_list vl;
|
|
va_start(vl, fmt);
|
|
s_Logger->InfoLog(fmt, vl);
|
|
va_end(vl);
|
|
}
|
|
|
|
bool ErrorLog(const char *fmt, ...)
|
|
{
|
|
if (!s_Logger)
|
|
return FAIL;
|
|
va_list vl;
|
|
va_start(vl, fmt);
|
|
s_Logger->ErrorLog(fmt, vl);
|
|
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::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)
|
|
{
|
|
va_list vl_tmp;
|
|
va_copy(vl_tmp, vl);
|
|
logger->DebugLog(fmt, vl_tmp);
|
|
va_end(vl_tmp);
|
|
}
|
|
}
|
|
|
|
void CMultiLogger::InfoLog(const char *fmt, va_list vl)
|
|
{
|
|
for (auto &logger: m_loggers)
|
|
{
|
|
va_list vl_tmp;
|
|
va_copy(vl_tmp, vl);
|
|
logger->InfoLog(fmt, vl_tmp);
|
|
va_end(vl_tmp);
|
|
}
|
|
}
|
|
|
|
void CMultiLogger::ErrorLog(const char *fmt, va_list vl)
|
|
{
|
|
for (auto &logger: m_loggers)
|
|
{
|
|
va_list vl_tmp;
|
|
va_copy(vl_tmp, vl);
|
|
logger->ErrorLog(fmt, vl_tmp);
|
|
va_end(vl_tmp);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
}
|