I've created a simple C++ logging class. It outputs various loglevels, and adjusts output color accordingly. I'm interested in any tips to improve my coding style.
I am especially concerned with my use of #define
's in the bottom of Logger.hpp. I was unable to find another way to pass __FILE__
and __LINE__
to the function (aside from doing it manually ofcourse), but I would not be suprised if this approach is bad practise.
ConsoleColor.hpp
#include <Windows.h>
namespace AGE
{
enum class BackgroundColor : int
{
Black = 0,
Blue = 16,
Green = 32,
Cyan = 48,
Red = 64,
Magenta = 80,
Brown = 96,
White = 112,
};
enum class ForegroundColor : int
{
Black = 0,
Blue,
Green,
Cyan,
Red,
Magenta,
Brown,
White,
Gray,
Intense_blue,
Intense_green,
Intense_Cyan,
Intense_red,
Intense_magenta,
Yellow,
Intense_white,
};
// Used to combine background and foreground colors
WORD operator &(const BackgroundColor& left, const ForegroundColor& right)
{
return static_cast<WORD>(static_cast<int>(left) + static_cast<int>(right));
}
}
LoggerSettings.hpp
#pragma once
namespace AGE
{
struct LoggerSettings
{
explicit LoggerSettings(bool should_print_level = true, bool should_print_file_and_line = true,
bool should_print_color = true, bool should_print_time = true) :
print_level(should_print_color),
print_file_and_line(should_print_file_and_line),
print_color(should_print_color),
print_time(should_print_time)
{}
// TODO add ability to display time in Logger.hpp/cpp
bool print_level;
bool print_file_and_line;
bool print_color;
bool print_time;
};
}
Logger.hpp
#pragma once
#include "LoggerSettings.hpp"
#include <Windows.h>
#include <string>
namespace AGE
{
enum class LogLevel
{
Trace,
Info,
Warn,
Error,
Fatal
};
class Logger
{
public:
Logger();
void set_log_level(const LogLevel level);
LoggerSettings& get_settings();
void explicit_trace(const std::string_view message, const char* file, int line);
void explicit_info (const std::string_view message, const char* file, int line);
void explicit_warn (const std::string_view message, const char* file, int line);
void explicit_error(const std::string_view message, const char* file, int line);
void explicit_fatal(const std::string_view message, const char* file, int line);
private:
void log(const LogLevel level, const std::string_view message, const char* file, int line);
WORD get_color(const LogLevel level);
std::string get_error_string(const LogLevel level);
std::string get_file_name(const char* filepath);
const HANDLE m_console_handle;
LogLevel m_current_log_level;
LoggerSettings m_settings;
};
}
#define trace(message) AGE::Logger::explicit_trace(message, __FILE__, __LINE__)
#define info(message) AGE::Logger::explicit_info(message, __FILE__, __LINE__)
#define warn(message) AGE::Logger::explicit_warn(message, __FILE__, __LINE__)
#define error(message) AGE::Logger::explicit_error(message, __FILE__, __LINE__)
#define fatal(message) AGE::Logger::explicit_fatal(message, __FILE__, __LINE__)
Logger.cpp
#include "Logger.hpp"
#include "ConsoleColor.hpp"
#include <iostream>
#include <cstdint>
namespace AGE
{
Logger::Logger() :
m_console_handle (GetStdHandle(STD_OUTPUT_HANDLE)),
m_current_log_level(LogLevel::Trace)
{
m_settings = LoggerSettings();
}
void Logger::set_log_level(LogLevel level)
{
m_current_log_level = level;
}
LoggerSettings& Logger::get_settings()
{
return m_settings;
}
void Logger::explicit_trace(const std::string_view message, const char* file, int line)
{
if (m_current_log_level > LogLevel::Trace)
{
return;
}
log(LogLevel::Trace, message, file, line);
}
void Logger::explicit_info(const std::string_view message, const char* file, int line)
{
if (m_current_log_level > LogLevel::Info)
{
return;
}
log(LogLevel::Info, message, file, line);
}
void Logger::explicit_warn(const std::string_view message, const char* file, int line)
{
if (m_current_log_level > LogLevel::Warn)
{
return;
}
log(LogLevel::Warn, message, file, line);
}
void Logger::explicit_error(const std::string_view message, const char* file, int line)
{
if (m_current_log_level > LogLevel::Error)
{
return;
}
log(LogLevel::Error, message, file, line);
}
void Logger::explicit_fatal(const std::string_view message, const char* file, int line)
{
if (m_current_log_level > LogLevel::Fatal)
{
return;
}
log(LogLevel::Fatal, message, file, line);
}
void Logger::log(const LogLevel level, const std::string_view message, const char* file, int line)
{
if (m_settings.print_color)
{
WORD color = get_color(level);
SetConsoleTextAttribute(m_console_handle, color);
}
if (m_settings.print_level)
{
std::cout << "[" << get_error_string(level) << "]";
}
if (m_settings.print_file_and_line)
{
std::cout << "(" << "In " << get_file_name(file) << " at line " << line << ") ";
}
std::cout << message << std::endl;
SetConsoleTextAttribute(m_console_handle, BackgroundColor::Black & ForegroundColor::White);
}
WORD Logger::get_color(const LogLevel level)
{
switch (level)
{
default:
return BackgroundColor::Black & ForegroundColor::White;
break;
case LogLevel::Trace:
return BackgroundColor::Black & ForegroundColor::Intense_white;
break;
case LogLevel::Info:
return BackgroundColor::Black & ForegroundColor::Intense_green;
break;
case LogLevel::Warn:
return BackgroundColor::Black & ForegroundColor::Intense_red;
break;
case LogLevel::Error:
return BackgroundColor::Black & ForegroundColor::Intense_Cyan;
break;
case LogLevel::Fatal:
return BackgroundColor::Black & ForegroundColor::Intense_magenta;
break;
}
}
std::string Logger::get_error_string(const LogLevel level)
{
switch (level)
{
default:
return std::string();
break;
case LogLevel::Trace:
return std::string("Trace");
break;
case LogLevel::Info:
return std::string("Info");
break;
case LogLevel::Warn:
return std::string("Warning");
break;
case LogLevel::Error:
return std::string("Error");
break;
case LogLevel::Fatal:
return std::string("Fatal");
break;
}
}
std::string Logger::get_file_name(const char* filepath)
{
std::string path = std::string(filepath);
int index = 0;
for (int i = static_cast<int>(path.size()) - 1; i > 0; i--)
{
if (path.at(i) == '\\')
{
index = i;
break;
}
}
// For some reason the compiler complains if i dont cast to 64bit int
return std::string(path, static_cast<int64_t>(index) + 1, path.size() - 1);
}
}