我不能 100% 确定这是否是您直接寻找的内容,但关于您所做的声明:
But in a big project a function can throw a lot of different exception.
在构建 3D 图形引擎时,我曾在一个大型项目中使用过这种设计。不过,这确实需要一些课程。需要注意的另一件事是,我的代码在设计时考虑了特定于 Windows 的情况。我已经评论了可以用您的特定操作系统实现替换的特定于窗口的代码部分。另一件事是我在许多输出消息中使用了__FUNCTION__,您可以将其替换为__PRETTY_FUNCTION__,因为出于某种原因,Windows 和 Visual Studio 不喜欢它...您还可以替换我使用的命名空间您自己的命名空间名称。如果这不能直接帮助您,那么至少整体设计可能是有用的。您可以创建一个新项目并按原样使用它来查看生成的控制台消息和写入的日志文件。您可以从中获取一些概念,或者您可以扩展和集成您自己的特定错误消息和异常类型,这些错误消息和异常类型会被抛出到这些类中,使其适合您自己的目的。
以下类集成在一起,其中一些可以独立使用或组合使用,并且对于多线程而言应该是线程安全的。
- ExceptionHandler - 它使用 Logger 类,用于处理抛出和/或发送到 Logger 的消息。
- Logger - Singleton 的派生类。它记录到控制台、文本文件或两者。它使用 TextFileWriter 写入文本文件。
- Singleton - 所有 Singleton Type 类都派生自的基类。
- TextFileWriter - FileHandler 的派生类。它写入一个文本文件。
- FileHandler - 所有文件处理类型类派生自的基类。
使用上述类的示例程序:
#include "Logger.h"
#include "ExceptionHandler.h"
#include <sstream>
#include <iostream>
enum ReturnCode {
ReturnError = -1,
ReturnOkay = 0
};
// helper function
void quitMessage() {
std::cout << "\nPress any key and enter to quit.\n";
std::cin.get();
}
// Example Class that uses the Logger & ExceptionHandler.
class Foo {
public:
Foo() = default;
void printFooUsingExceptionHandler() {
using namespace linx;
throw ExceptionHandler( __FUNCTION__ + std::string( " using ExceptionHandler" ) );
// Or can be used this way
// std::ostringstream stream;
// stream << __FUNCTION__ << " using ExceptionHandler";
// throw ExceptionHandler( stream );
}
void printFooNotUsingExceptionHandlerButUsingLogger() {
using namespace linx;
std::ostringstream stream;
stream << __FUNCTION__ << " logging to logger, but not using ExceptionHandler";
Logger::log( stream, Logger::TypeWarning ); // Can use TypeInfo, TypeWarning or TypeError pending on the use case.
}
};
int main() {
namespace lx = linx;
using namespace lx;
try {
Logger logger( "logger.txt" );
// last param is ommited as it is defaults to TypeInfo
Logger::log( "This is basic info" );
Logger::log( "This is a warning", Logger::TypeWarning );
Logger::log( "This is an error", Logger::TypeError );
Logger::log( "Basic console message", Logger::TypeConsole );
// Example of a class using the ExceptionHandler & Logger classes
Foo f;
// call this first; log to message but don't throw excpetion
f.printFooNotUsingExceptionHandlerButUsingLogger();
// call this to throw exception
f.printFooUsingExceptionHandler();
} catch( ExceptionHandler& e ) {
std::cout << "Exception Thrown: " << e.getMessage() << std::endl;
quitMessage();
return ReturnError;
} catch( ... ) {
std::cout << __FUNCTION__ << " Caught Unknown Exception" << std::endl;
quitMessage();
return ReturnError;
}
quitMessage();
return ReturnOkay;
}
类:
ExceptionHandler.h
#ifndef EXCEPTION_HANDLER_H
#define EXCEPTION_HANDLER_H
#include <string>
#include <sstream>
namespace linx {
class ExceptionHandler final {
private:
std::string _message;
public:
explicit ExceptionHandler( const std::string& message, bool saveInLog = true );
explicit ExceptionHandler( const std::ostringstream& streamMessage, bool saveInLog = true );
~ExceptionHandler() = default;
ExceptionHandler( const ExceptionHandler& c ) = default;
ExceptionHandler& operator=( const ExceptionHandler& c ) = delete;
const std::string& getMessage() const;
};
} // namespace linx
#endif // !EXCEPTION_HANDLER_H
ExceptionHandler.cpp
#include "ExceptionHandler.h"
#include "Logger.h"
namespace linx {
ExceptionHandler::ExceptionHandler( const std::string& message, bool saveInLog ) :
_message( message ) {
if( saveInLog ) {
Logger::log( _message, Logger::TypeError );
}
}
ExceptionHandler::ExceptionHandler( const std::ostringstream& streamMessage, bool saveInLog ) :
_message( streamMessage.str() ) {
if( saveInLog ) {
Logger::log( _message, Logger::TypeError );
}
}
const std::string& ExceptionHandler::getMessage() const {
return _message;
}
} // namespace linx
Logger.h
#include <sstream>
#include <array>
// For Windows Console Output
#define VC_EXTRALEAN
#include <Windows.h>
namespace linx {
class Logger final : public Singleton {
public:
enum LoggerType {
TypeInfo = 0,
TypeWarning,
TypeError,
TypeConsole
};
private:
std::string _logFilename;
unsigned _maxCharLength;
std::array<std::string, 4> _logTypes;
const std::string _unknownLogType;
// This is for Windows Console Output - Can substitue with OS type
HANDLE _hConsoleOutput;
WORD _consoleDefualtColor;
// ---------------------------------------------------------------
public:
explicit Logger( const std::string& logFilename );
virtual ~Logger();
static void log( const std::string& text, LoggerType logType = TypeInfo );
static void log( const std::ostringstream& text, LoggerType logType = TypeInfo );
static void log( const char* text, LoggerType logType = TypeInfo );
};
} // namespace linx
#endif // !LOGGER_H
Logger.cpp
#include "Logger.h"
#include "TextFileWriter.h"
#include <iostream>
#include <iomanip>
#include <mutex>
// #include <shared_mutex>
namespace linx {
static Logger* s_pLogger = nullptr;
std::mutex g_mutex; // also removed the static storage qualifier.
//static std::shared_mutex s_mutex; // this was a wrong implementation
// White Text On Red Background
static const WORD WHITE_ON_RED = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY | BACKGROUND_RED;
Logger::Logger( const std::string& logFilename ) :
Singleton( TypeLogger ),
_logFilename( logFilename ),
_maxCharLength( 0 ),
_unknownLogType( "UNKNOWN" ) {
// Order must match types defined in Logger::LoggerType enum
_logTypes[0] = "Info";
_logTypes[1] = "Warning";
_logTypes[2] = "Error";
_logTypes[3] = ""; // Console
// Find widest log type string
_maxCharLength = _unknownLogType.size();
for ( const std::string& logType : _logTypes ) {
if( _maxCharLength < logType.size() ) {
_maxCharLength = logType.size();
}
}
// this was wrong
//std::shared_lock<std::shared_mutex> lock( s_mutex );
{ // scope for lock_guard
std::lock_guard<std::mutex> lock( g_mutex );
// Start Log File
TextFileWriter file( _logFilename, false, false );
// Prepare Console - Windows Console (can substitute with your OS)
_hConsoleOutput = GetStdHandle( STD_OUTPUT_HANDLE );
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
GetConsoleScreenBufferInfo( _hConsoleOutput, &consoleInfo );
_consoleDefualtColor = consoleInfo.wAttributes;
// End - Windows Console specific
s_pLogger = this;
} // end scope: lock_guard must be destroyed here
// Must destroy lock_guard to unlock mutex before calling this function
// this function is derived from Singleton but it calls Logger::log() static method which in turn uses the same mutex to lock.
logMemoryAllocation( true );
}
Logger::~Logger() {
logMemoryAllocation( false );
s_pLogger = nullptr;
}
void Logger::log( const std::string& text, LoggerType logType ) {
log( text.c_str(), logType );
}
void Logger::log( const std::ostringstream& text, LoggerType logType ) {
log( text.str().c_str(), logType );
}
void Logger::log( const char* text, LoggerType logType ) {
if( nullptr == s_pLogger ) {
std::cout << "Logger has not been initialized, can not log " << text << '\n';
return;
}
// this is wrong
//std::shared_lock<std::shared_mutex> lock( s_mutex );
std::lock_guard<std::mutex> lock( g_mutex );
std::ostringstream stream;
// Default White Text On Red Background
WORD textColor = WHITE_ON_RED;
// Choose log type text string, display "UNKNOWN" if logType is out of range
stream << std::setfill( ' ' ) << std::setw( s_pLogger->_maxCharLength );
try {
if( TypeConsole != logType ) {
stream << s_pLogger->_logTypes.at( logType );
}
if( TypeWarning == logType ) {
// Yellow
textColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN;
} else if( TypeInfo == logType ) {
// Green
textColor = FOREGROUND_GREEN;
} else if( TypeConsole == logType ) {
// Cyan
textColor = FOREGROUND_GREEN | FOREGROUND_BLUE;
}
} catch( ... ) {
stream << s_pLogger->_unknownLogType;
}
// Date & Time
if( TypeConsole != logType ) {
SYSTEMTIME time;
GetLocalTime( &time );
stream << " [" << time.wYear << '.'
<< std::setfill( '0' ) << std::setw( 2 ) << time.wMonth << '.'
<< std::setfill( '0' ) << std::setw( 2 ) << time.wDay << ' '
<< std::setfill( ' ' ) << std::setw( 2 ) << time.wHour << ':'
<< std::setfill( '0' ) << std::setw( 2 ) << time.wMinute << ':'
<< std::setfill( '0' ) << std::setw( 2 ) << time.wSecond << '.'
<< std::setfill( '0' ) << std::setw( 3 ) << time.wMilliseconds << "] ";
}
stream << text << '\n';
// Log Message
SetConsoleTextAttribute( s_pLogger->_hConsoleOutput, textColor );
std::cout << stream.str();
// Save same message to file
try {
TextFileWriter file( s_pLogger->_logFilename, true, false );
file.write( stream.str() );
} catch( ... ) {
// Ignore, not saved in log file
std::cout << __FUNCTION__ << " failed to write to file: " << stream.str() << '\n';
}
// Reset to default color
SetConsoleTextAttribute( s_pLogger->_hConsoleOutput, s_pLogger->_consoleDefualtColor );
}
} // namespace linx
Singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H
namespace linx {
class Singleton {
public:
enum SingletonType {
TypeLogger = 0, // MUST BE FIRST!
};
private:
SingletonType _type;
public:
Singleton( const Singleton& c ) = delete;
Singleton& operator=( const Singleton& c ) = delete;
virtual ~Singleton();
protected:
explicit Singleton( SingletonType type );
void logMemoryAllocation( bool isAllocated ) const;
};
} // namespace linx
#endif // !SINGLETON_H
Single.cpp
#include "Singleton.h"
#include "Logger.h"
#include "ExceptionHandler.h"
#include <string>
#include <array>
namespace linx {
struct SingletonInfo {
const std::string singletonName;
bool isConstructed;
SingletonInfo( const std::string& singletonNameIn ) :
singletonName( singletonNameIn ),
isConstructed( false ) {}
};
// Order must match types defined in Singleton::SingletonType
static std::array<SingletonInfo, 1> s_aSingletons = { SingletonInfo( "Logger" ) };
Singleton::Singleton( SingletonType type ) :
_type( type ) {
bool saveInLog = s_aSingletons.at( TypeLogger ).isConstructed;
try {
if( !s_aSingletons.at( type ).isConstructed ) {
// Test Initialize Order
for( int i = 0; i < type; ++i ) {
if( !s_aSingletons.at( i ).isConstructed ) {
throw ExceptionHandler( s_aSingletons.at( i ).singletonName +
" must be constructed before constructing " +
s_aSingletons.at( type ).singletonName,
saveInLog );
}
}
s_aSingletons.at( type ).isConstructed = true;
} else {
throw ExceptionHandler( s_aSingletons.at( type ).singletonName +
" can only be constructed once.",
saveInLog );
}
} catch( std::exception& ) {
// type is out of range
std::ostringstream stream;
stream << __FUNCTION__ << " Invalid Singleton Type specified: " << type;
throw ExceptionHandler( stream, saveInLog );
}
}
Singleton::~Singleton() {
s_aSingletons.at( _type ).isConstructed = false;
}
void Singleton::logMemoryAllocation( bool isAllocated ) const {
if( isAllocated ) {
Logger::log( "Created " + s_aSingletons.at( _type ).singletonName );
} else {
Logger::log( "Destroyed " + s_aSingletons.at( _type ).singletonName );
}
}
} // namespace linx
TextFileWriter
#ifndef TEXT_FILE_WRITER_H
#define TEXT_FILE_WRITER_H
#include "FileHandler.h"
namespace linx {
class TextFileWriter : public FileHandler {
public:
explicit TextFileWriter( const std::string& filename, bool appendToFile, bool saveExceptionInLog = true );
virtual ~TextFileWriter() = default;
void write( const std::string& str );
TextFileWriter( const TextFileWriter& ) = delete;
TextFileWriter& operator=( const TextFileWriter& ) = delete;
};
} // namespace linx
#endif // !TEXT_FILE_WRITER_H
TextFileWriter.cpp
#include "TextFileWriter.h"
namespace linx {
TextFileWriter::TextFileWriter( const std::string& filename, bool appendToFile, bool saveExceptionInLog ) :
FileHandler( filename, saveExceptionInLog ) {
_file.open( _filenameWithPath.c_str(),
std::ios_base::out | (appendToFile ? std::ios_base::app : std::ios_base::trunc) );
if( !_file.is_open() ) {
throwError( __FUNCTION__ + std::string( " can not open file for writing" ) );
}
}
void TextFileWriter::write( const std::string& str ) {
_file << str;
}
} // namespace linx
FileHandler.h
#ifndef FILE_HANDLER_H
#define FILE_HANDLER_H
#include <string>
#include <sstream>
#include <fstream>
namespace linx {
class FileHandler {
private:
bool _saveExceptionInLog;
protected:
std::fstream _file;
std::string _filePath;
std::string _filenameWithPath;
public:
virtual ~FileHandler();
FileHandler( const FileHandler& c ) = delete;
FileHandler& operator=( const FileHandler& c ) = delete;
protected:
FileHandler( const std::string& filename, bool saveExceptionInLog );
void throwError( const std::string& message ) const;
void throwError( const std::ostringstream& message ) const;
bool getString( std::string& str, bool appendPath );
};
} // namespace linx
#endif // !FILE_HANDLER_H
FileHandler.cpp
#include "ExceptionHandler.h"
#include "Logger.h"
namespace linx {
ExceptionHandler::ExceptionHandler( const std::string& message, bool saveInLog ) :
_message( message ) {
if( saveInLog ) {
Logger::log( _message, Logger::TypeError );
}
}
ExceptionHandler::ExceptionHandler( const std::ostringstream& streamMessage, bool saveInLog ) :
_message( streamMessage.str() ) {
if( saveInLog ) {
Logger::log( _message, Logger::TypeError );
}
}
const std::string& ExceptionHandler::getMessage() const {
return _message;
}
} // namespace linx