RTTI in Logging and Debugging
How can I use RTTI to implement a robust logging system that provides detailed type information for debugging?
Implementing a robust logging system using RTTI can greatly enhance debugging by providing detailed type information. Here's an approach to create such a system:
Define a base Loggable
class:
#include <typeinfo>
#include <string>
class Loggable {
public:
virtual ~Loggable() = default;
virtual std::string getLogInfo() const {
return std::string("Type: ") +
typeid(*this).name();
}
};
Implement a Logger
class:
#include <typeinfo>
#include <string>
#include <chrono>
#include <iostream>
class Loggable {/*...*/}
class Logger {
public:
enum class LogLevel {
DEBUG, INFO, WARNING, ERROR
};
static void log(
LogLevel level,
const Loggable& obj,
const std::string& message
) {
std::ostringstream oss;
oss << getCurrentTimestamp() << " ["
<< getLevelString(level) << "] "
<< obj.getLogInfo() << " - " << message;
std::cout << oss.str() << std::endl;
}
static std::string getCurrentTimestamp() {
using namespace std::chrono;
auto now = system_clock::now();
auto time = system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(
std::localtime(&time), "%Y-%m-%d %H:%M:%S"
);
return ss.str();
}
static std::string getLevelString(
LogLevel level) {
switch (level) {
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
default:
return "UNKNOWN";
}
}
};
Create loggable classes:
#include <typeinfo>
#include <string>
#include <chrono>
#include <iostream>
class Loggable {/*...*/}
class Logger {/*...*/}
class Player : public Loggable {
public:
Player(const std::string& name, int health)
: name_(name), health_(health) {}
std::string getLogInfo() const override {
std::ostringstream oss;
oss << Loggable::getLogInfo() << ", Name: "
<< name_ << ", Health: " << health_;
return oss.str();
}
void takeDamage(int Damage) {
// ...
}
private:
std::string name_;
int health_;
};
class Item : public Loggable {
public:
Item(const std::string& name, int value)
: name_(name), value_(value) {}
std::string getLogInfo() const override {
std::ostringstream oss;
oss << Loggable::getLogInfo() << ", Name: "
<< name_ << ", Value: " << value_;
return oss.str();
}
private:
std::string name_;
int value_;
};
Implement a template function for logging types that do not inherit from Loggable
:
#include <typeinfo>
#include <string>
#include <chrono>
#include <iostream>
class Loggable {/*...*/}
class Logger {/*...*/}
class Player : public Loggable {/*...*/}
class Item : public Loggable {/*...*/}
template <typename T>
void logValue(
Logger::LogLevel level,
const T& value,
const std::string& message
) {
std::ostringstream oss;
oss << "Type: " << typeid(value).name()
<< ", Value: " << value;
std::cout << Logger::getCurrentTimestamp()
<< " [" << Logger::getLevelString(level)
<< "] " << oss.str() << " - "
<< message << std::endl;
}
Use the logging system:
#include <typeinfo>
#include <string>
#include <chrono>
#include <iostream>
class Loggable {/*...*/}
class Logger {/*...*/}
class Player : public Loggable {/*...*/}
class Item : public Loggable {/*...*/}
void logValue(/*...*/) {/*...*/}
int main() {
Player player("Alice", 100);
Logger::log(Logger::LogLevel::INFO,
player, "Player created");
Item sword("Excalibur", 1000);
Logger::log(Logger::LogLevel::DEBUG,
sword, "Item acquired");
player.takeDamage(20);
Logger::log(Logger::LogLevel::WARNING,
player, "Player took damage");
int score = 5000;
logValue(Logger::LogLevel::INFO, score,
"Current score");
std::string gameState = "Level 2";
logValue(Logger::LogLevel::DEBUG, gameState,
"Game state changed");
try {
throw std::runtime_error("Game crash");
} catch (const std::exception& e) {
Logger::log(Logger::LogLevel::ERROR,
player, std::string("Exception: ") + e.what());
}
}
2024-06-24 03:54:24 [INFO] Type: class Player, Name: Alice, Health: 100 - Player created
2024-06-24 03:54:24 [DEBUG] Type: class Item, Name: Excalibur, Value: 1000 - Item acquired
2024-06-24 03:54:24 [WARNING] Type: class Player, Name: Alice, Health: 100 - Player took damage
2024-06-24 03:54:24 [INFO] Type: int, Value: 5000 - Current score
2024-06-24 03:54:24 [DEBUG] Type: class std::string, Value: Level 2 - Game state changed
2024-06-24 03:54:24 [ERROR] Type: class Player, Name: Alice, Health: 100 - Exception: Game crash
This logging system uses RTTI in several ways:
- The base
Loggable
class usestypeid
to provide the type name of derived classes. - The
logValue
template function usestypeid
to get type information for non-Loggable types. - The
Logger
class provides a consistent interface for logging both Loggable and non-Loggable types.
Benefits of this approach:
- Detailed type information: The system provides the actual type of objects being logged, which is especially useful for polymorphic types.
- Extensibility: New Loggable classes can easily be added by inheriting from the Loggable base class and overriding the
getLogInfo
method. - Flexibility: The system can log both Loggable objects and primitive types.
- Consistency: All log entries include timestamps, log levels, and type information.
Considerations:
- Performance: RTTI operations have a runtime cost. In performance-critical sections, you might want to disable detailed logging or use compile-time alternatives.
- Readability: The
typeid().name()
output is implementation-defined and might not be human-readable. You might need to use a demangling function for better readability. - Memory usage: Storing detailed type information for all objects might increase memory usage. Consider implementing a way to control logging verbosity.
To further enhance this system, you could:
- Add file output capabilities to the Logger class.
- Implement log filtering based on log levels or object types.
- Use a thread-safe logging mechanism for multi-threaded applications.
- Integrate with a more comprehensive logging framework like spdlog or Boost.Log.
By using RTTI in your logging system, you can create a powerful debugging tool that provides detailed type information at runtime, greatly aiding in the development and maintenance of complex C++ applications.
Run Time Type Information (RTTI) and typeid()
Learn to identify and react to object types at runtime using RTTI, dynamic casting and the typeid()
operator