New upstream version 8.1.0
This commit is contained in:
331
client_module/source/app/log/Logger.c
Normal file
331
client_module/source/app/log/Logger.c
Normal file
@@ -0,0 +1,331 @@
|
||||
#include <common/threading/Thread.h>
|
||||
#include <common/toolkit/MessagingTk.h>
|
||||
#include <common/nodes/Node.h>
|
||||
#include <filesystem/FhgfsOpsSuper.h>
|
||||
#include <filesystem/FhgfsInode.h>
|
||||
#include <toolkit/NoAllocBufferStore.h>
|
||||
#include "Logger.h"
|
||||
|
||||
|
||||
#define LOG_TOPIC_GENERAL_STR "general"
|
||||
#define LOG_TOPIC_CONN_STR "conn"
|
||||
#define LOG_TOPIC_COMMKIT_STR "commkit"
|
||||
#define LOG_TOPIC_UNKNOWN_STR "<unknown>" /* for unknown/invalid log topics */
|
||||
|
||||
|
||||
|
||||
void Logger_init(Logger* this, App* app, Config* cfg)
|
||||
{
|
||||
int i;
|
||||
|
||||
this->app = app;
|
||||
|
||||
for(i=0; i < LogTopic_LAST; i++)
|
||||
this->logLevels[i] = Config_getLogLevel(cfg);
|
||||
|
||||
this->logFormattedBuf = (char*)os_kmalloc(LOGGER_LOGBUF_SIZE);
|
||||
this->logContextBuf = (char*)os_kmalloc(LOGGER_LOGBUF_SIZE);
|
||||
|
||||
this->clientID = NULL;
|
||||
|
||||
Mutex_init(&this->outputMutex);
|
||||
|
||||
|
||||
// Note: The follwing guys exist to avoid deadlocks that would occur when log messages are
|
||||
// created (by the same thread) while we're already trying to send a log message to the
|
||||
// helper daemon (e.g. the messages of the NodeConnPool). Such messages will be discarded.
|
||||
this->currentOutputPID = LOGGER_PID_NOCURRENTOUTPUT;
|
||||
Mutex_init(&this->multiLockMutex);
|
||||
}
|
||||
|
||||
Logger* Logger_construct(App* app, Config* cfg)
|
||||
{
|
||||
Logger* this = (Logger*)os_kmalloc(sizeof(Logger) );
|
||||
|
||||
Logger_init(this, app, cfg);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void Logger_uninit(Logger* this)
|
||||
{
|
||||
SAFE_KFREE(this->clientID);
|
||||
|
||||
SAFE_KFREE(this->logContextBuf);
|
||||
SAFE_KFREE(this->logFormattedBuf);
|
||||
|
||||
Mutex_uninit(&this->multiLockMutex);
|
||||
Mutex_uninit(&this->outputMutex);
|
||||
}
|
||||
|
||||
void Logger_destruct(Logger* this)
|
||||
{
|
||||
Logger_uninit(this);
|
||||
|
||||
kfree(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just print a log message with formatting similar to printk().
|
||||
*
|
||||
* @param level LogLevel_... value
|
||||
* @param context the context from which this msg was printed (e.g. the calling function).
|
||||
* @param msg the log message with formatting, e.g. "%s".
|
||||
*/
|
||||
void Logger_logFormatted(Logger* this, LogLevel level, const char* context, const char* msgFormat,
|
||||
...)
|
||||
{
|
||||
// note: cannot be inlined because of variable arg list
|
||||
|
||||
va_list ap;
|
||||
|
||||
if(level > this->logLevels[LogTopic_GENERAL])
|
||||
return;
|
||||
|
||||
va_start(ap, msgFormat);
|
||||
|
||||
__Logger_logTopFormattedGranted(this, LogTopic_GENERAL, level, context, msgFormat, ap);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void Logger_logTopFormatted(Logger* this, LogTopic logTopic, LogLevel level, const char* context,
|
||||
const char* msgFormat, ...)
|
||||
{
|
||||
// note: cannot be inlined because of variable arg list
|
||||
|
||||
va_list ap;
|
||||
|
||||
if(level > this->logLevels[logTopic])
|
||||
return;
|
||||
|
||||
va_start(ap, msgFormat);
|
||||
|
||||
__Logger_logTopFormattedGranted(this, logTopic, level, context, msgFormat, ap);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log with EntryID
|
||||
*
|
||||
* Note: This takes an EntryInfo read-lock. Must be used only if there is no risk of deadlock.
|
||||
*/
|
||||
void Logger_logTopFormattedWithEntryID(struct inode* inode, LogTopic logTopic, LogLevel level,
|
||||
const char* logContext, const char* msgFormat, ...)
|
||||
{
|
||||
char* newMsg;
|
||||
App* app = FhgfsOps_getApp(inode->i_sb);
|
||||
Logger* log = App_getLogger(app);
|
||||
FhgfsInode* fhgfsInode = BEEGFS_INODE(inode);
|
||||
const EntryInfo* entryInfo = FhgfsInode_getEntryInfo(fhgfsInode);
|
||||
va_list ap;
|
||||
|
||||
FhgfsInode_entryInfoReadLock(fhgfsInode); // L O C K entryInfo
|
||||
|
||||
va_start(ap, msgFormat);
|
||||
|
||||
newMsg = os_kmalloc(LOGGER_LOGBUF_SIZE);
|
||||
if (newMsg)
|
||||
snprintf(newMsg, LOGGER_LOGBUF_SIZE, "entryID: %s %s ", EntryInfo_getEntryID(entryInfo),
|
||||
msgFormat);
|
||||
else // malloc failed. Likely an out memory situation, we still try to print msgFormat
|
||||
newMsg = (char*)msgFormat;
|
||||
|
||||
Logger_logTopFormattedVA(log, logTopic, level, logContext, newMsg, ap);
|
||||
va_end(ap);
|
||||
|
||||
FhgfsInode_entryInfoReadUnlock(fhgfsInode); // U N L O C K entryInfo
|
||||
|
||||
if(newMsg != msgFormat)
|
||||
kfree(newMsg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Just print a log message. Similar to Logger_logFormatted(), but take a va_list already
|
||||
*/
|
||||
void Logger_logFormattedVA(Logger* this, LogLevel level, const char* context, const char* msgFormat,
|
||||
va_list ap)
|
||||
{
|
||||
if(level > this->logLevels[LogTopic_GENERAL])
|
||||
return;
|
||||
|
||||
__Logger_logTopFormattedGranted(this, LogTopic_GENERAL, level, context, msgFormat, ap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just print a log message. Similar to Logger_logTopFormatted(), but take a va_list already
|
||||
*/
|
||||
void Logger_logTopFormattedVA(Logger* this, LogTopic logTopic, LogLevel level, const char* context,
|
||||
const char* msgFormat, va_list ap)
|
||||
{
|
||||
if(level > this->logLevels[logTopic])
|
||||
return;
|
||||
|
||||
__Logger_logTopFormattedGranted(this, logTopic, level, context, msgFormat, ap);
|
||||
}
|
||||
|
||||
void Logger_logErrFormatted(Logger* this, const char* context, const char* msgFormat, ...)
|
||||
{
|
||||
// note: cannot be inlined because of variable arg list
|
||||
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msgFormat);
|
||||
|
||||
__Logger_logTopFormattedGranted(this, LogTopic_GENERAL, Log_ERR, context, msgFormat, ap);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void Logger_logTopErrFormatted(Logger* this, LogTopic logTopic, const char* context,
|
||||
const char* msgFormat, ...)
|
||||
{
|
||||
// note: cannot be inlined because of variable arg list
|
||||
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msgFormat);
|
||||
|
||||
__Logger_logTopFormattedGranted(this, logTopic, Log_ERR, context, msgFormat, ap);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to the standard log.
|
||||
*
|
||||
* @param level log level (Log_... value)
|
||||
* @param msg the message
|
||||
*/
|
||||
void __Logger_logTopFormattedGranted(Logger* this, LogTopic logTopic, LogLevel level,
|
||||
const char* context, const char* msgFormat, va_list args)
|
||||
{
|
||||
if(__Logger_checkThreadMultiLock(this) )
|
||||
{
|
||||
// this thread is already trying to log a message. trying to lock outputMutex would deadlock.
|
||||
// => discard this message
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Mutex_lock(&this->outputMutex);
|
||||
|
||||
__Logger_setCurrentOutputPID(this, current->pid); // grab currentOutputPID
|
||||
|
||||
|
||||
// evaluate msgFormat
|
||||
vsnprintf(this->logFormattedBuf, LOGGER_LOGBUF_SIZE, msgFormat, args);
|
||||
|
||||
// extend context
|
||||
if(this->clientID)
|
||||
snprintf(this->logContextBuf, LOGGER_LOGBUF_SIZE, "%s: %s", this->clientID, context);
|
||||
else
|
||||
snprintf(this->logContextBuf, LOGGER_LOGBUF_SIZE, "%s", context);
|
||||
|
||||
printk_fhgfs(KERN_INFO, "%s: %s\n", this->logContextBuf, this->logFormattedBuf);
|
||||
|
||||
__Logger_setCurrentOutputPID(this, LOGGER_PID_NOCURRENTOUTPUT); // release currentOutputPID
|
||||
|
||||
Mutex_unlock(&this->outputMutex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Note: Call this before locking the outputMutex (because it exists to avoid dead-locking).
|
||||
*
|
||||
* @return true if the currentOutputPID is set to the current thread and logging cannot continue;
|
||||
*/
|
||||
bool __Logger_checkThreadMultiLock(Logger* this)
|
||||
{
|
||||
bool retVal = false;
|
||||
|
||||
Mutex_lock(&this->multiLockMutex);
|
||||
|
||||
if(this->currentOutputPID == current->pid)
|
||||
{ // we alread own the outputPID (=> we already own the outputMutex)
|
||||
retVal = true;
|
||||
}
|
||||
|
||||
Mutex_unlock(&this->multiLockMutex);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Call this only after the thread owns the outputMutex to avoid "stealing".
|
||||
*/
|
||||
void __Logger_setCurrentOutputPID(Logger* this, pid_t pid)
|
||||
{
|
||||
Mutex_lock(&this->multiLockMutex);
|
||||
|
||||
this->currentOutputPID = pid;
|
||||
|
||||
Mutex_unlock(&this->multiLockMutex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a pointer to the static string representation of a log topic (or "<unknown>" for unknown/
|
||||
* invalid log topic numbers.
|
||||
*/
|
||||
const char* Logger_getLogTopicStr(LogTopic logTopic)
|
||||
{
|
||||
switch(logTopic)
|
||||
{
|
||||
case LogTopic_GENERAL:
|
||||
return LOG_TOPIC_GENERAL_STR;
|
||||
|
||||
case LogTopic_CONN:
|
||||
return LOG_TOPIC_CONN_STR;
|
||||
|
||||
case LogTopic_COMMKIT:
|
||||
return LOG_TOPIC_COMMKIT_STR;
|
||||
|
||||
default:
|
||||
return LOG_TOPIC_UNKNOWN_STR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the log topic number from a string (not case-sensitive).
|
||||
*
|
||||
* @return false if string didn't match any known log topic.
|
||||
*/
|
||||
bool Logger_getLogTopicFromStr(const char* logTopicStr, LogTopic* outLogTopic)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i=0; i < LogTopic_LAST; i++)
|
||||
{
|
||||
const char* currentLogTopicStr = Logger_getLogTopicStr( (LogTopic)i);
|
||||
|
||||
if(!strcasecmp(logTopicStr, currentLogTopicStr))
|
||||
{
|
||||
*outLogTopic = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// (note: we carefully set outLogTopic to "general" to not risk leaving it undefined)
|
||||
*outLogTopic = LogTopic_GENERAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to retrieve the level of LogTopic_GENERAL in old code.
|
||||
* New code should use _getLogTopicLevel() instead.
|
||||
*/
|
||||
LogLevel Logger_getLogLevel(Logger* this)
|
||||
{
|
||||
return this->logLevels[LogTopic_GENERAL];
|
||||
}
|
||||
|
||||
LogLevel Logger_getLogTopicLevel(Logger* this, LogTopic logTopic)
|
||||
{
|
||||
return this->logLevels[logTopic];
|
||||
}
|
||||
|
||||
244
client_module/source/app/log/Logger.h
Normal file
244
client_module/source/app/log/Logger.h
Normal file
@@ -0,0 +1,244 @@
|
||||
#ifndef LOGGER_H_
|
||||
#define LOGGER_H_
|
||||
|
||||
#include <app/config/Config.h>
|
||||
#include <app/App.h>
|
||||
#include <common/Common.h>
|
||||
#include <common/toolkit/StringTk.h>
|
||||
#include <common/toolkit/Time.h>
|
||||
#include <common/threading/Mutex.h>
|
||||
#include <common/Common.h>
|
||||
#include <common/Common.h>
|
||||
|
||||
|
||||
#define LOGGER_LOGBUF_SIZE 1000 /* max log message length */
|
||||
#define LOGGER_PID_NOCURRENTOUTPUT 0 /* pid value if outputMutex not locked */
|
||||
|
||||
|
||||
#ifdef LOG_DEBUG_MESSAGES
|
||||
|
||||
#define LOG_DEBUG(logger, level, contextStr, msgStr) \
|
||||
do { Logger_log(logger, level, contextStr, msgStr); } while(0)
|
||||
|
||||
#define LOG_DEBUG_TOP(logger, logTopic, level, contextStr, msgStr) \
|
||||
do { Logger_logTop(logger, logTopic, level, contextStr, msgStr); } while(0)
|
||||
|
||||
#define LOG_DEBUG_FORMATTED(logger, level, contextStr, msgStr, ...) \
|
||||
do { Logger_logFormatted(logger, level, contextStr, msgStr, ## __VA_ARGS__); } while(0)
|
||||
|
||||
#define LOG_DEBUG_TOP_FORMATTED(logger, logTopic, level, contextStr, msgStr, ...) \
|
||||
do { Logger_logTopFormatted(logger, logTopic, level, contextStr, msgStr, ## __VA_ARGS__); } \
|
||||
while(0)
|
||||
|
||||
#else
|
||||
|
||||
#define LOG_DEBUG(logger, level, contextStr, msgStr)
|
||||
#define LOG_DEBUG_TOP(logger, logTopic, level, contextStr, msgStr)
|
||||
#define LOG_DEBUG_FORMATTED(logger, level, contextStr, msgStr, ...)
|
||||
#define LOG_DEBUG_TOP_FORMATTED(logger, logTopic, level, contextStr, msgStr, ...)
|
||||
|
||||
#endif // LOG_DEBUG_MESSAGES
|
||||
|
||||
#define Logger_logFormattedWithEntryID(inode, level, logContext, msgFormat, ...) \
|
||||
Logger_logTopFormattedWithEntryID(inode, LogTopic_GENERAL, level, logContext, msgFormat, \
|
||||
##__VA_ARGS__)
|
||||
|
||||
// forward declarations...
|
||||
|
||||
struct Logger;
|
||||
typedef struct Logger Logger;
|
||||
|
||||
struct Node;
|
||||
|
||||
enum LogLevel;
|
||||
typedef enum LogLevel LogLevel;
|
||||
enum LogTopic;
|
||||
typedef enum LogTopic LogTopic;
|
||||
|
||||
|
||||
|
||||
extern void Logger_init(Logger* this, App* app, Config* cfg);
|
||||
extern Logger* Logger_construct(App* app, Config* cfg);
|
||||
extern void Logger_uninit(Logger* this);
|
||||
extern void Logger_destruct(Logger* this);
|
||||
|
||||
__attribute__((format(printf, 4, 5)))
|
||||
extern void Logger_logFormatted(Logger* this, LogLevel level, const char* context,
|
||||
const char* msgFormat, ...);
|
||||
__attribute__((format(printf, 5, 6)))
|
||||
extern void Logger_logTopFormattedWithEntryID(struct inode* inode, LogTopic logTopic,
|
||||
LogLevel level, const char* logContext, const char* msgFormat, ...);
|
||||
__attribute__((format(printf, 5, 6)))
|
||||
extern void Logger_logTopFormatted(Logger* this, LogTopic logTopic, LogLevel level,
|
||||
const char* context, const char* msgFormat, ...);
|
||||
extern void Logger_logFormattedVA(Logger* this, LogLevel level, const char* context,
|
||||
const char* msgFormat, va_list ap);
|
||||
extern void Logger_logTopFormattedVA(Logger* this, LogTopic logTopic, LogLevel level,
|
||||
const char* context, const char* msgFormat, va_list ap);
|
||||
__attribute__((format(printf, 3, 4)))
|
||||
extern void Logger_logErrFormatted(Logger* this, const char* context, const char* msgFormat, ...);
|
||||
__attribute__((format(printf, 4, 5)))
|
||||
extern void Logger_logTopErrFormatted(Logger* this, LogTopic logTopic, const char* context,
|
||||
const char* msgFormat, ...);
|
||||
|
||||
extern LogLevel Logger_getLogLevel(Logger* this);
|
||||
extern LogLevel Logger_getLogTopicLevel(Logger* this, LogTopic logTopic);
|
||||
|
||||
extern void __Logger_logTopFormattedGranted(Logger* this, LogTopic logTopic, LogLevel level,
|
||||
const char* context, const char* msgFormat, va_list args);
|
||||
|
||||
extern bool __Logger_checkThreadMultiLock(Logger* this);
|
||||
extern void __Logger_setCurrentOutputPID(Logger* this, pid_t pid);
|
||||
|
||||
|
||||
// static
|
||||
extern const char* Logger_getLogTopicStr(LogTopic logTopic);
|
||||
extern bool Logger_getLogTopicFromStr(const char* logTopicStr, LogTopic* logTopic);
|
||||
|
||||
// getters & setters
|
||||
static inline void Logger_setClientID(Logger* this, const char* clientID);
|
||||
static inline void Logger_setAllLogLevels(Logger* this, LogLevel logLevel);
|
||||
static inline void Logger_setLogTopicLevel(Logger* this, LogTopic logTopic, LogLevel logLevel);
|
||||
|
||||
// inliners
|
||||
static inline void Logger_log(Logger* this, LogLevel level, const char* context, const char* msg);
|
||||
static inline void Logger_logTop(Logger* this, LogTopic logTopic, LogLevel level,
|
||||
const char* context, const char* msg);
|
||||
static inline void Logger_logErr(Logger* this, const char* context, const char* msg);
|
||||
static inline void Logger_logTopErr(Logger* this, LogTopic logTopic, const char* context,
|
||||
const char* msg);
|
||||
|
||||
|
||||
enum LogLevel
|
||||
{
|
||||
LOG_NOTHING=-1,
|
||||
Log_ERR=0, /* system error */
|
||||
Log_CRITICAL=1, /* something the users should definitely know about */
|
||||
Log_WARNING=2, /* things that indicate or are related to a problem */
|
||||
Log_NOTICE=3, /* things that could help finding problems */
|
||||
Log_DEBUG=4, /* things that are only useful during debugging, often logged with LOG_DEBUG() */
|
||||
Log_SPAM=5 /* things that are typically too detailed even during normal debugging,
|
||||
very often with LOG_DEBUG() */
|
||||
};
|
||||
|
||||
/**
|
||||
* Note: When you add a new log topic, you must also update these places:
|
||||
* 1) Logger_getLogTopicStr()
|
||||
* 2) ProcFsHelper_{read/write}_logLevels()
|
||||
*/
|
||||
enum LogTopic
|
||||
{
|
||||
LogTopic_GENERAL=0, /* everything that is not assigned to a more specific log topic */
|
||||
LogTopic_CONN, /* connects and disconnects */
|
||||
LogTopic_COMMKIT, /* CommKitVec */
|
||||
|
||||
LogTopic_LAST /* not valid, just exists to define the LogLevelsArray size */
|
||||
};
|
||||
|
||||
typedef signed char LogTopicLevels[LogTopic_LAST]; /* array for per-topic log levels, see LogTopic/
|
||||
LogLevel. Note: Type is actually type LogLevel, but we use char here because we also allow
|
||||
"-1" to disable a level. */
|
||||
|
||||
|
||||
/**
|
||||
* This is the general logger class.
|
||||
*/
|
||||
struct Logger
|
||||
{
|
||||
// configurables
|
||||
LogTopicLevels logLevels; // per-topic log levels
|
||||
|
||||
// internals
|
||||
App* app;
|
||||
|
||||
Mutex outputMutex;
|
||||
|
||||
Mutex multiLockMutex; // to avoid multiple locking of the outputMutex by the same thread
|
||||
pid_t currentOutputPID; // pid of outputMutex holder (see LOGGER_PID_NOCURRENTOUTPUT)
|
||||
|
||||
char* logFormattedBuf; // for logging functions with variable argument list
|
||||
char* logContextBuf; // for extended context logging
|
||||
|
||||
char* clientID; // only set if clientID logging is enabled
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Note: Copies the clientID.
|
||||
*/
|
||||
void Logger_setClientID(Logger* this, const char* clientID)
|
||||
{
|
||||
SAFE_KFREE(this->clientID); // free old clientID
|
||||
|
||||
this->clientID = StringTk_strDup(clientID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: This is intended to be used during app destruction to disable logging by setting the levels
|
||||
* to "-1".
|
||||
*
|
||||
* @param logLevel LogLevel_... or "-1" to disable.
|
||||
*/
|
||||
void Logger_setAllLogLevels(Logger* this, LogLevel logLevel)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i=0; i < LogTopic_LAST; i++)
|
||||
this->logLevels[i] = logLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param logLevel LogLevel_... or "-1" to disable.
|
||||
*/
|
||||
void Logger_setLogTopicLevel(Logger* this, LogTopic logTopic, LogLevel logLevel)
|
||||
{
|
||||
this->logLevels[logTopic] = logLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log msg for LogTopic_GENERAL.
|
||||
*
|
||||
* @param level LogLevel_... value
|
||||
* @param context the context from which this msg was printed (e.g. the calling function).
|
||||
* @param msg the log message
|
||||
*/
|
||||
void Logger_log(Logger* this, LogLevel level, const char* context, const char* msg)
|
||||
{
|
||||
Logger_logFormatted(this, level, context, "%s", msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log msg for a certain log topic.
|
||||
*
|
||||
* @param level LogLevel_... value
|
||||
* @param context the context from which this msg was printed (e.g. the calling function).
|
||||
* @param msg the log message
|
||||
*/
|
||||
void Logger_logTop(Logger* this, LogTopic logTopic, LogLevel level, const char* context,
|
||||
const char* msg)
|
||||
{
|
||||
Logger_logTopFormatted(this, logTopic, level, context, "%s", msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error msg for LogTopic_GENERAL.
|
||||
*/
|
||||
void Logger_logErr(Logger* this, const char* context, const char* msg)
|
||||
{
|
||||
Logger_logErrFormatted(this, context, "%s", msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error msg for a certain log topic.
|
||||
*/
|
||||
void Logger_logTopErr(Logger* this, LogTopic logTopic, const char* context, const char* msg)
|
||||
{
|
||||
Logger_logTopErrFormatted(this, logTopic, context, "%s", msg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif /*LOGGER_H_*/
|
||||
Reference in New Issue
Block a user