init
This commit is contained in:
366
software design/components/utils/logger/com/logger.cpp
Normal file
366
software design/components/utils/logger/com/logger.cpp
Normal file
@@ -0,0 +1,366 @@
|
||||
/**
|
||||
* @file logger.cpp
|
||||
* @brief ASF Logger - Implementation of ESP-IDF logging wrapper
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*/
|
||||
|
||||
#include "logger.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <sys/time.h>
|
||||
|
||||
namespace asf {
|
||||
namespace logger {
|
||||
|
||||
// Static configuration
|
||||
static LoggerConfig s_config = {
|
||||
.minLevel = LogLevel::INFO,
|
||||
.enableTimestamp = true,
|
||||
.enableColor = true,
|
||||
.enableId = true,
|
||||
.maxMessageLength = 256,
|
||||
.filePath = nullptr
|
||||
};
|
||||
|
||||
// Color codes for different log levels
|
||||
static const char* COLOR_RESET = "\033[0m";
|
||||
static const char* COLOR_VERBOSE = "\033[37m"; // White
|
||||
static const char* COLOR_DEBUG = "\033[36m"; // Cyan
|
||||
static const char* COLOR_INFO = "\033[32m"; // Green
|
||||
static const char* COLOR_WARNING = "\033[33m"; // Yellow
|
||||
static const char* COLOR_ERROR = "\033[31m"; // Red
|
||||
|
||||
void initialize(const LoggerConfig& config)
|
||||
{
|
||||
s_config = config;
|
||||
|
||||
// Set ESP-IDF log level based on our minimum level
|
||||
esp_log_level_t espLevel = ESP_LOG_NONE;
|
||||
switch (config.minLevel) {
|
||||
case LogLevel::VERBOSE:
|
||||
espLevel = ESP_LOG_VERBOSE;
|
||||
break;
|
||||
case LogLevel::DEBUG:
|
||||
espLevel = ESP_LOG_DEBUG;
|
||||
break;
|
||||
case LogLevel::INFO:
|
||||
espLevel = ESP_LOG_INFO;
|
||||
break;
|
||||
case LogLevel::WARNING:
|
||||
espLevel = ESP_LOG_WARN;
|
||||
break;
|
||||
case LogLevel::ERROR:
|
||||
espLevel = ESP_LOG_ERROR;
|
||||
break;
|
||||
case LogLevel::NONE:
|
||||
espLevel = ESP_LOG_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
esp_log_level_set("*", espLevel);
|
||||
}
|
||||
|
||||
void setLogLevel(LogLevel level)
|
||||
{
|
||||
s_config.minLevel = level;
|
||||
|
||||
// Update ESP-IDF log level
|
||||
LoggerConfig tempConfig = s_config;
|
||||
tempConfig.minLevel = level;
|
||||
initialize(tempConfig);
|
||||
}
|
||||
|
||||
LogLevel getLogLevel()
|
||||
{
|
||||
return s_config.minLevel;
|
||||
}
|
||||
|
||||
void enableTimestamp(bool enable)
|
||||
{
|
||||
s_config.enableTimestamp = enable;
|
||||
}
|
||||
|
||||
void enableColor(bool enable)
|
||||
{
|
||||
s_config.enableColor = enable;
|
||||
}
|
||||
|
||||
void enableId(bool enable)
|
||||
{
|
||||
s_config.enableId = enable;
|
||||
}
|
||||
|
||||
void enableFileLogging(const char* filePath)
|
||||
{
|
||||
s_config.filePath = filePath;
|
||||
}
|
||||
|
||||
const char* getIsoTimestamp(char* buffer, size_t bufferSize)
|
||||
{
|
||||
if (buffer == nullptr || bufferSize < 24) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Get current time with microseconds
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, nullptr);
|
||||
|
||||
struct tm* timeinfo = localtime(&tv.tv_sec);
|
||||
|
||||
// Format: YYYY-MM-DDTHH:MM:SS.sssZ
|
||||
snprintf(buffer, bufferSize, "%04d-%02d-%02dT%02d:%02d:%02d.%03ldZ",
|
||||
timeinfo->tm_year + 1900,
|
||||
timeinfo->tm_mon + 1,
|
||||
timeinfo->tm_mday,
|
||||
timeinfo->tm_hour,
|
||||
timeinfo->tm_min,
|
||||
timeinfo->tm_sec,
|
||||
tv.tv_usec / 1000);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void log(const char* tag, uint32_t id, LogLevel level, Criticality criticality, const char* format, ...)
|
||||
{
|
||||
// Check if we should log this level
|
||||
if (level < s_config.minLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tag == nullptr || format == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare message buffer
|
||||
char messageBuffer[s_config.maxMessageLength];
|
||||
|
||||
// Format the user message
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(messageBuffer, sizeof(messageBuffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
// Prepare final output buffer
|
||||
char outputBuffer[s_config.maxMessageLength + 128]; // Extra space for timestamp, tag, etc.
|
||||
char timestampBuffer[32];
|
||||
|
||||
// Build the formatted log message
|
||||
const char* colorStart = s_config.enableColor ? logLevelToColor(level) : "";
|
||||
const char* colorEnd = s_config.enableColor ? COLOR_RESET : "";
|
||||
const char* timestamp = s_config.enableTimestamp ? getIsoTimestamp(timestampBuffer, sizeof(timestampBuffer)) : "";
|
||||
const char* levelStr = logLevelToString(level);
|
||||
const char* critStr = criticalityToString(criticality);
|
||||
|
||||
if (s_config.enableTimestamp && s_config.enableId) {
|
||||
snprintf(outputBuffer, sizeof(outputBuffer), "%s%s : %s[%s][%s] : %lu : %s%s",
|
||||
colorStart, timestamp, tag, levelStr, critStr, id, messageBuffer, colorEnd);
|
||||
} else if (s_config.enableTimestamp && !s_config.enableId) {
|
||||
snprintf(outputBuffer, sizeof(outputBuffer), "%s%s : %s[%s][%s] : %s%s",
|
||||
colorStart, timestamp, tag, levelStr, critStr, messageBuffer, colorEnd);
|
||||
} else if (!s_config.enableTimestamp && s_config.enableId) {
|
||||
snprintf(outputBuffer, sizeof(outputBuffer), "%s%s[%s][%s] : %lu : %s%s",
|
||||
colorStart, tag, levelStr, critStr, id, messageBuffer, colorEnd);
|
||||
} else {
|
||||
snprintf(outputBuffer, sizeof(outputBuffer), "%s%s[%s][%s] : %s%s",
|
||||
colorStart, tag, levelStr, critStr, messageBuffer, colorEnd);
|
||||
}
|
||||
|
||||
// Output using ESP-IDF logging system
|
||||
switch (level) {
|
||||
case LogLevel::VERBOSE:
|
||||
ESP_LOGV("ASF", "%s", outputBuffer);
|
||||
break;
|
||||
case LogLevel::DEBUG:
|
||||
ESP_LOGD("ASF", "%s", outputBuffer);
|
||||
break;
|
||||
case LogLevel::INFO:
|
||||
ESP_LOGI("ASF", "%s", outputBuffer);
|
||||
break;
|
||||
case LogLevel::WARNING:
|
||||
ESP_LOGW("ASF", "%s", outputBuffer);
|
||||
break;
|
||||
case LogLevel::ERROR:
|
||||
ESP_LOGE("ASF", "%s", outputBuffer);
|
||||
break;
|
||||
case LogLevel::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
// Write to file if enabled
|
||||
if (s_config.filePath != nullptr) {
|
||||
FILE* f = fopen(s_config.filePath, "a");
|
||||
if (f != nullptr) {
|
||||
// Re-format without color codes for file output
|
||||
char fileBuffer[s_config.maxMessageLength + 128];
|
||||
if (s_config.enableTimestamp && s_config.enableId) {
|
||||
snprintf(fileBuffer, sizeof(fileBuffer), "%s : %s[%s][%s] : %lu : %s\n",
|
||||
timestamp, tag, levelStr, critStr, id, messageBuffer);
|
||||
} else if (s_config.enableTimestamp && !s_config.enableId) {
|
||||
snprintf(fileBuffer, sizeof(fileBuffer), "%s : %s[%s][%s] : %s\n",
|
||||
timestamp, tag, levelStr, critStr, messageBuffer);
|
||||
} else if (!s_config.enableTimestamp && s_config.enableId) {
|
||||
snprintf(fileBuffer, sizeof(fileBuffer), "%s[%s][%s] : %lu : %s\n",
|
||||
tag, levelStr, critStr, id, messageBuffer);
|
||||
} else {
|
||||
snprintf(fileBuffer, sizeof(fileBuffer), "%s[%s][%s] : %s\n",
|
||||
tag, levelStr, critStr, messageBuffer);
|
||||
}
|
||||
fprintf(f, "%s", fileBuffer);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void logVerbose(const char* tag, uint32_t id, Criticality criticality, const char* format, ...)
|
||||
{
|
||||
if (LogLevel::VERBOSE < s_config.minLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
char messageBuffer[s_config.maxMessageLength];
|
||||
vsnprintf(messageBuffer, sizeof(messageBuffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
log(tag, id, LogLevel::VERBOSE, criticality, "%s", messageBuffer);
|
||||
}
|
||||
|
||||
void logDebug(const char* tag, uint32_t id, Criticality criticality, const char* format, ...)
|
||||
{
|
||||
if (LogLevel::DEBUG < s_config.minLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
char messageBuffer[s_config.maxMessageLength];
|
||||
vsnprintf(messageBuffer, sizeof(messageBuffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
log(tag, id, LogLevel::DEBUG, criticality, "%s", messageBuffer);
|
||||
}
|
||||
|
||||
void logInfo(const char* tag, uint32_t id, Criticality criticality, const char* format, ...)
|
||||
{
|
||||
if (LogLevel::INFO < s_config.minLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
char messageBuffer[s_config.maxMessageLength];
|
||||
vsnprintf(messageBuffer, sizeof(messageBuffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
log(tag, id, LogLevel::INFO, criticality, "%s", messageBuffer);
|
||||
}
|
||||
|
||||
void logWarning(const char* tag, uint32_t id, Criticality criticality, const char* format, ...)
|
||||
{
|
||||
if (LogLevel::WARNING < s_config.minLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
char messageBuffer[s_config.maxMessageLength];
|
||||
vsnprintf(messageBuffer, sizeof(messageBuffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
log(tag, id, LogLevel::WARNING, criticality, "%s", messageBuffer);
|
||||
}
|
||||
|
||||
void logError(const char* tag, uint32_t id, Criticality criticality, const char* format, ...)
|
||||
{
|
||||
if (LogLevel::ERROR < s_config.minLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
||||
char messageBuffer[s_config.maxMessageLength];
|
||||
vsnprintf(messageBuffer, sizeof(messageBuffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
log(tag, id, LogLevel::ERROR, criticality, "%s", messageBuffer);
|
||||
}
|
||||
|
||||
LoggerConfig getDefaultConfig()
|
||||
{
|
||||
LoggerConfig config = {};
|
||||
config.minLevel = LogLevel::INFO;
|
||||
config.enableTimestamp = true;
|
||||
config.enableColor = true;
|
||||
config.enableId = true;
|
||||
config.maxMessageLength = 256;
|
||||
config.filePath = nullptr;
|
||||
return config;
|
||||
}
|
||||
|
||||
const char* logLevelToString(LogLevel level)
|
||||
{
|
||||
switch (level) {
|
||||
case LogLevel::VERBOSE:
|
||||
return "VERBOSE";
|
||||
case LogLevel::DEBUG:
|
||||
return "DEBUG";
|
||||
case LogLevel::INFO:
|
||||
return "INFO";
|
||||
case LogLevel::WARNING:
|
||||
return "WARNING";
|
||||
case LogLevel::ERROR:
|
||||
return "ERROR";
|
||||
case LogLevel::NONE:
|
||||
return "NONE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
const char* logLevelToColor(LogLevel level)
|
||||
{
|
||||
switch (level) {
|
||||
case LogLevel::VERBOSE:
|
||||
return COLOR_VERBOSE;
|
||||
case LogLevel::DEBUG:
|
||||
return COLOR_DEBUG;
|
||||
case LogLevel::INFO:
|
||||
return COLOR_INFO;
|
||||
case LogLevel::WARNING:
|
||||
return COLOR_WARNING;
|
||||
case LogLevel::ERROR:
|
||||
return COLOR_ERROR;
|
||||
case LogLevel::NONE:
|
||||
return COLOR_RESET;
|
||||
default:
|
||||
return COLOR_RESET;
|
||||
}
|
||||
}
|
||||
|
||||
const char* criticalityToString(Criticality criticality)
|
||||
{
|
||||
switch (criticality) {
|
||||
case Criticality::LOW:
|
||||
return "LOW";
|
||||
case Criticality::MEDIUM:
|
||||
return "MEDIUM";
|
||||
case Criticality::HIGH:
|
||||
return "HIGH";
|
||||
case Criticality::VERY_HIGH:
|
||||
return "VERY_HIGH";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace logger
|
||||
} // namespace asf
|
||||
226
software design/components/utils/logger/com/logger.hpp
Normal file
226
software design/components/utils/logger/com/logger.hpp
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* @file logger.hpp
|
||||
* @brief ASF Logger - Wrapper for ESP-IDF logging functionality
|
||||
* @author Mahmoud Elmohtady
|
||||
* @company Nabd solutions - ASF
|
||||
* @copyright Copyright (c) 2025
|
||||
*
|
||||
* @details
|
||||
* Abstract logger class for the application and device driver layers
|
||||
* - Does not depend directly on the low-level logging mechanism provided by platform
|
||||
* - Supports logging levels (info, warn, debug, error, verbose)
|
||||
* - Formats messages with ISO timestamp, TAG, level, and unique ID
|
||||
* - Low overhead design for minimal performance impact
|
||||
* - Easily included in all modules without compile/linking errors
|
||||
* - Efficient in compilation time and flash space
|
||||
*
|
||||
* Design: Namespace with free functions for simplicity and zero overhead
|
||||
*/
|
||||
|
||||
#ifndef LOGGER_HPP
|
||||
#define LOGGER_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdarg>
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
/**
|
||||
* @brief Logger namespace containing all logging functionality
|
||||
*/
|
||||
namespace asf {
|
||||
namespace logger {
|
||||
|
||||
/**
|
||||
* @brief Log level enumeration
|
||||
*/
|
||||
enum class LogLevel : uint8_t
|
||||
{
|
||||
VERBOSE = 0, ///< Verbose level (most detailed)
|
||||
DEBUG = 1, ///< Debug level
|
||||
INFO = 2, ///< Information level
|
||||
WARNING = 3, ///< Warning level
|
||||
ERROR = 4, ///< Error level
|
||||
NONE = 5 ///< No logging
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Log criticality enumeration
|
||||
*/
|
||||
enum class Criticality : uint8_t
|
||||
{
|
||||
LOW, ///< Low criticality
|
||||
MEDIUM, ///< Medium criticality
|
||||
HIGH, ///< High criticality
|
||||
VERY_HIGH ///< Very high criticality
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Logger configuration structure
|
||||
*/
|
||||
struct LoggerConfig
|
||||
{
|
||||
LogLevel minLevel; ///< Minimum log level to display
|
||||
bool enableTimestamp; ///< Enable ISO timestamp in output
|
||||
bool enableColor; ///< Enable color output
|
||||
bool enableId; ///< Enable unique ID in output
|
||||
uint32_t maxMessageLength; ///< Maximum message length
|
||||
const char* filePath; ///< Path to log file (nullptr to disable file logging)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize the logger with configuration
|
||||
* @param config Logger configuration
|
||||
*/
|
||||
void initialize(const LoggerConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Set minimum log level
|
||||
* @param level Minimum log level to display
|
||||
*/
|
||||
void setLogLevel(LogLevel level);
|
||||
|
||||
/**
|
||||
* @brief Get current minimum log level
|
||||
* @return Current minimum log level
|
||||
*/
|
||||
LogLevel getLogLevel();
|
||||
|
||||
/**
|
||||
* @brief Enable or disable timestamp in log output
|
||||
* @param enable True to enable, false to disable
|
||||
*/
|
||||
void enableTimestamp(bool enable);
|
||||
|
||||
/**
|
||||
* @brief Enable or disable color in log output
|
||||
* @param enable True to enable, false to disable
|
||||
*/
|
||||
void enableColor(bool enable);
|
||||
|
||||
/**
|
||||
* @brief Enable or disable ID in log output
|
||||
* @param enable True to enable, false to disable
|
||||
*/
|
||||
void enableId(bool enable);
|
||||
|
||||
/**
|
||||
* @brief Enable file logging
|
||||
* @param filePath Path to the log file (e.g., "/ESP/log.txt")
|
||||
*/
|
||||
void enableFileLogging(const char* filePath);
|
||||
|
||||
/**
|
||||
* @brief Get ISO 8601 formatted timestamp
|
||||
* @param buffer Buffer to store timestamp
|
||||
* @param bufferSize Size of buffer
|
||||
* @return Pointer to buffer
|
||||
*/
|
||||
const char* getIsoTimestamp(char* buffer, size_t bufferSize);
|
||||
|
||||
/**
|
||||
* @brief Main logging function
|
||||
* @param tag Log tag (module name)
|
||||
* @param id Unique message ID
|
||||
* @param level Log level
|
||||
* @param criticality Message criticality
|
||||
* @param format Printf-style format string
|
||||
* @param ... Variable arguments
|
||||
*/
|
||||
void log(const char* tag, uint32_t id, LogLevel level, Criticality criticality, const char* format, ...);
|
||||
|
||||
/**
|
||||
* @brief Log verbose message
|
||||
* @param tag Log tag (module name)
|
||||
* @param id Unique message ID
|
||||
* @param criticality Message criticality
|
||||
* @param format Printf-style format string
|
||||
* @param ... Variable arguments
|
||||
*/
|
||||
void logVerbose(const char* tag, uint32_t id, Criticality criticality, const char* format, ...);
|
||||
|
||||
/**
|
||||
* @brief Log debug message
|
||||
* @param tag Log tag (module name)
|
||||
* @param id Unique message ID
|
||||
* @param criticality Message criticality
|
||||
* @param format Printf-style format string
|
||||
* @param ... Variable arguments
|
||||
*/
|
||||
void logDebug(const char* tag, uint32_t id, Criticality criticality, const char* format, ...);
|
||||
|
||||
/**
|
||||
* @brief Log info message
|
||||
* @param tag Log tag (module name)
|
||||
* @param id Unique message ID
|
||||
* @param criticality Message criticality
|
||||
* @param format Printf-style format string
|
||||
* @param ... Variable arguments
|
||||
*/
|
||||
void logInfo(const char* tag, uint32_t id, Criticality criticality, const char* format, ...);
|
||||
|
||||
/**
|
||||
* @brief Log warning message
|
||||
* @param tag Log tag (module name)
|
||||
* @param id Unique message ID
|
||||
* @param criticality Message criticality
|
||||
* @param format Printf-style format string
|
||||
* @param ... Variable arguments
|
||||
*/
|
||||
void logWarning(const char* tag, uint32_t id, Criticality criticality, const char* format, ...);
|
||||
|
||||
/**
|
||||
* @brief Log error message
|
||||
* @param tag Log tag (module name)
|
||||
* @param id Unique message ID
|
||||
* @param criticality Message criticality
|
||||
* @param format Printf-style format string
|
||||
* @param ... Variable arguments
|
||||
*/
|
||||
void logError(const char* tag, uint32_t id, Criticality criticality, const char* format, ...);
|
||||
|
||||
/**
|
||||
* @brief Get default logger configuration
|
||||
* @return Default logger configuration
|
||||
*/
|
||||
LoggerConfig getDefaultConfig();
|
||||
|
||||
/**
|
||||
* @brief Convert log level to string
|
||||
* @param level Log level
|
||||
* @return String representation of log level
|
||||
*/
|
||||
const char* logLevelToString(LogLevel level);
|
||||
|
||||
/**
|
||||
* @brief Convert log level to color code
|
||||
* @param level Log level
|
||||
* @return ANSI color code string
|
||||
*/
|
||||
const char* logLevelToColor(LogLevel level);
|
||||
|
||||
/**
|
||||
* @brief Convert criticality to string
|
||||
* @param criticality Message criticality
|
||||
* @return String representation of criticality
|
||||
*/
|
||||
const char* criticalityToString(Criticality criticality);
|
||||
|
||||
} // namespace logger
|
||||
} // namespace asf
|
||||
|
||||
// Convenience macros for easier usage
|
||||
#define ASF_LOG_VERBOSE(tag, id, crit, format, ...) asf::logger::logVerbose(tag, id, crit, format, ##__VA_ARGS__)
|
||||
#define ASF_LOG_DEBUG(tag, id, crit, format, ...) asf::logger::logDebug(tag, id, crit, format, ##__VA_ARGS__)
|
||||
#define ASF_LOG_INFO(tag, id, crit, format, ...) asf::logger::logInfo(tag, id, crit, format, ##__VA_ARGS__)
|
||||
#define ASF_LOG_WARNING(tag, id, crit, format, ...) asf::logger::logWarning(tag, id, crit, format, ##__VA_ARGS__)
|
||||
#define ASF_LOG_ERROR(tag, id, crit, format, ...) asf::logger::logError(tag, id, crit, format, ##__VA_ARGS__)
|
||||
|
||||
// Short form macros
|
||||
#define ASF_LOGV(tag, id, crit, format, ...) ASF_LOG_VERBOSE(tag, id, crit, format, ##__VA_ARGS__)
|
||||
#define ASF_LOGD(tag, id, crit, format, ...) ASF_LOG_DEBUG(tag, id, crit, format, ##__VA_ARGS__)
|
||||
#define ASF_LOGI(tag, id, crit, format, ...) ASF_LOG_INFO(tag, id, crit, format, ##__VA_ARGS__)
|
||||
#define ASF_LOGW(tag, id, crit, format, ...) ASF_LOG_WARNING(tag, id, crit, format, ##__VA_ARGS__)
|
||||
#define ASF_LOGE(tag, id, crit, format, ...) ASF_LOG_ERROR(tag, id, crit, format, ##__VA_ARGS__)
|
||||
|
||||
#endif // LOGGER_HPP
|
||||
Reference in New Issue
Block a user