add good-old Logger class.

JSON-90
Roker 2018-06-18 13:59:19 +02:00
parent d750c2ffaa
commit 3b0f931384
3 changed files with 517 additions and 0 deletions

346
server/logger.cc Normal file
View File

@ -0,0 +1,346 @@
#include "logger.hh"
#include "logger_config.hh"
#include <cstdarg>
#include <ctime>
#include <cstdlib>
#include <mutex>
#include <thread>
#include <sys/time.h>
#ifdef LOGGER_ENABLE_SYSLOG
extern "C" {
#include <syslog.h>
}
#endif // LOGGER_ENABLE_SYSLOG
#ifdef DEBUG_ENABLED
#include <sstream>
#endif // DEBUG_ENABLED
using Lock = std::lock_guard<std::recursive_mutex>;
enum { LogLineMax = LOGGER_MAX_LOG_LINE_LENGTH };
namespace LoggerS // namespace containing all data for the Logger singleton. HACK!
{
std::FILE* logfile = 0;
// Logger* GL = 0; // global log object
std::recursive_mutex mut;
Logger::Severity loglevel;
Logger::Target target = Logger::Target(-1);
std::string filename;
std::string ident;
bool omit_timestamp = false;
void openfile();
void opensyslog();
void log(Logger::Severity s, const std::string& msg);
#ifdef LOGGER_USE_ASCII_TAGS
const char* Levelname[] =
{
"<<EMERGENCY>>",
"<<ALERT>>",
"<<CRITICAL>>",
"<ERROR>",
"<Warning>",
"<Notice>",
"<info>",
"<>", // Debug
"! " // DebugInternal
};
#else
const char* Levelname[] =
{
"»»EMERGENCY",
"»»ALERT",
"»»CRITICAL",
"»ERROR",
"»Warning",
"»Notice",
"»Info",
"°", // Debug
"·" // DebugInternal
};
#endif
} // end of namespace LoggerS
std::string Logger::gmtime(time_t t)
{
char buf[24]; // long enough to hold YYYYY-MM-DD.hh:mm:ss" (y10k-safe!)
std::tm T;
gmtime_r(&t, &T); // TODO: GNU extension also in std:: ?
std::snprintf(buf, sizeof(buf)-1, "%04d-%02d-%02d.%02d:%02d:%02d",
T.tm_year+1900, T.tm_mon+1, T.tm_mday, T.tm_hour, T.tm_min, T.tm_sec );
return buf;
}
std::string Logger::gmtime(timeval t)
{
char buf[31]; // long enough to hold YYYYY-MM-DD.hh:mm:ss.uuuuuu
std::tm T;
gmtime_r(&t.tv_sec, &T); // TODO: GNU extension also in std:: ?
std::snprintf(buf, sizeof(buf)-1, "%04d-%02d-%02d.%02d:%02d:%02d.%06lu",
T.tm_year+1900, T.tm_mon+1, T.tm_mday, T.tm_hour, T.tm_min, T.tm_sec, (long unsigned)t.tv_usec);
return buf;
}
Logger::Severity Logger::getLevel() const
{
return loglevel;
}
void Logger::setLevel(Severity s)
{
// TODO: use C++17 std::clamp()
loglevel = s<Emergency ? Emergency : (s>DebugInternal ? DebugInternal : s);
}
const std::string& Logger::getPrefix() const
{
return prefix;
}
Logger::Target Logger::getTarget()
{
return LoggerS::target;
}
void Logger::setTarget(Target t)
{
LoggerS::target = t;
}
Logger::Logger(const std::string& my_prefix, Severity my_loglevel)
: prefix(my_prefix + ":")
{
setLevel(my_loglevel);
}
Logger::Logger(Logger& parent, const std::string& my_prefix, Severity my_loglevel)
: prefix(parent.getPrefix() + my_prefix + ':')
, loglevel( my_loglevel == Severity::Inherited ? parent.getLevel() : my_loglevel )
{
setLevel(loglevel); // clamp the value
}
void Logger::log(Severity s, const char* format, ...)
{
if(s<=loglevel && s<=LoggerS::loglevel)
{
va_list va;
va_start(va, format);
char buf[ LogLineMax + 1];
std::vsnprintf(buf, LogLineMax, format, va);
va_end(va);
LoggerS::log(s, prefix + buf);
}
}
void LogP(Logger::Severity s, Logger::Severity my_loglevel, const std::string& prefix, const char* format, va_list va)
{
if(s<=my_loglevel && s<=LoggerS::loglevel)
{
char buf[ LogLineMax + 1];
std::vsnprintf(buf, LogLineMax, format, va);
LoggerS::log(s, prefix + buf );
}
}
void Logger::log(Severity s, const std::string& logline)
{
if(s<=loglevel && s<=LoggerS::loglevel)
{
LoggerS::log(s, prefix + logline);
}
}
#define LOGGER_LAZY( fnname, severity ) \
void Logger::fnname ( const std::string& line) { \
log( Logger:: severity, line ); \
} \
void Logger:: fnname (const char* format, ...) { \
va_list va; va_start(va, format); \
LogP( severity, loglevel, prefix, format, va ) ;\
va_end(va); \
} \
LOGGER_LAZY( emergency, Emergency)
LOGGER_LAZY( alert , Alert )
LOGGER_LAZY( critical , Crit )
LOGGER_LAZY( error , Error )
LOGGER_LAZY( warning , Warn )
LOGGER_LAZY( notice , Notice )
LOGGER_LAZY( info , Info )
#ifdef DEBUG_ENABLED
LOGGER_LAZY( debug , Debug )
LOGGER_LAZY( debugInternal , DebugInternal )
#else
// intentionally left blank.
// the methods are already defined as do-nothing inline functions in Logger.hh
#endif
#undef LOGGER_LAZY
Logger& getLogger()
{
Lock LCK(LoggerS::mut);
static Logger log("#", Logger::Debug);
return log;
}
// ---------- LoggerS ----------
void LoggerS::openfile()
{
logfile = std::fopen(filename.c_str(), "a");
if(logfile==0)
{
perror("Could not open log file! ");
}
}
#ifdef LOGGER_ENABLE_SYSLOG
void LoggerS::opensyslog()
{
openlog(ident.c_str(), 0, LOG_DAEMON);
}
#else
void LoggerS::opensyslog() {}
#endif
static const std::string thread_alphabet = "0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const unsigned thread_alph_len = thread_alphabet.size(); // shall be a prime number
static std::hash<std::thread::id> hash_tid;
// create a three-digit base37 string of the current thread ID for easy grepping and filtering:
std::string thread_id()
{
char buf[8] = { ' ', '\302', '\266', '?', '?', '?', ' ' };
unsigned long long id = hash_tid(std::this_thread::get_id());
buf[3] = thread_alphabet.at( id % thread_alph_len); id /= thread_alph_len;
buf[4] = thread_alphabet.at( id % thread_alph_len); id /= thread_alph_len;
buf[5] = thread_alphabet.at( id % thread_alph_len); id /= thread_alph_len;
return std::string(buf, buf+7);
}
void LoggerS::log(Logger::Severity s, const std::string& logline)
{
Lock LCK(mut);
if(s<Logger::Emergency ) s = Logger::Emergency;
if(s>Logger::DebugInternal ) s = Logger::DebugInternal;
if(target & (Logger::File | Logger::Console))
{
std::string timestamp;
timestamp.reserve(45);
if(omit_timestamp == false)
{
timestamp = Logger::gmtime(time(0));
}
timestamp.append( thread_id() );
timestamp.append(Levelname[s]);
timestamp.append(" :");
if(target & Logger::Console)
{
std::fputs(timestamp.c_str(), stderr);
std::fputs(logline.c_str(), stderr);
std::fputc('\n', stderr);
}
if(target & Logger::File)
{
std::fputs(timestamp.c_str(), logfile);
std::fputs(logline.c_str(), logfile);
std::fputc('\n', logfile);
std::fflush(logfile);
}
}
#ifdef LOGGER_ENABLE_SYSLOG
if(target & Logger::Syslog)
{
syslog(s, "%s", logline.c_str());
}
#endif
}
#ifdef DEBUG_ENABLED
Logger::Stream::Stream(Logger* l) : L(l) , parent(0)
{}
Logger::Stream::Stream(Stream& S, const std::string& str)
: L(0) , parent(&S), s(str)
{}
Logger::Stream::~Stream()
{
if(parent)
{
parent->s.append(s);
}
if(L)
{
L->debugInternal(s);
}
}
template<class T>
Logger::Stream operator<<(Logger::Stream s, const T& t)
{
std::stringstream ss;
ss << t;
return Logger::Stream(s, ss.str());
}
Logger::Stream operator<<(Logger::Stream s, const char*const t)
{
return Logger::Stream(s, t);
}
template Logger::Stream operator<<(Logger::Stream, const int&);
template Logger::Stream operator<<(Logger::Stream, const unsigned&);
template Logger::Stream operator<<(Logger::Stream, const std::string&);
template Logger::Stream operator<<(Logger::Stream, const double&);
Logger::operator Logger::Stream()
{
return Stream(this);
}
#endif // DEBUG_ENABLED
// End of file

157
server/logger.hh Normal file
View File

@ -0,0 +1,157 @@
#ifndef LOGGER_HH
#define LOGGER_HH
#include <cstdio>
#include <string>
#include <cerrno>
#include <sys/time.h>
#ifdef DEBUG_ENABLED
#define DEBUG_OUT( LL , ... ) LL.debug( __VA_ARGS__ )
#define DEBUGI_OUT( LL , ... ) LL.debugInternal( __VA_ARGS__ )
#else
#define DEBUG_OUT(...) do{}while(0)
#define DEBUGI_OUT(...) do{}while(0)
#endif
#ifdef __GNUC__
#define PRINTF __attribute__((format (printf, 2,3) ))
#define PRINTF3 __attribute__((format (printf, 3,4) ))
#else
#define PRINTF
#define PRINTF3
#endif
class Logger;
// get global Logger instance
Logger& getLogger();
class Logger
{
public:
enum Severity
{
Emergency,// The message says the system is unusable.
Alert, // Action on the message must be taken immediately.
Crit, // The message states a critical condition.
Error, // The message describes an error.
Warn, // The message is a warning.
Notice, // The message describes a normal but important event.
Info, // The message is purely informational.
Debug, // The message is only for interface debugging purposes.
DebugInternal, // The message is for internal debugging purposes.
Inherited=-1 // Use the same loglevel as the parent Logger (if any)
};
enum Target
{
Console=1, Syslog=2, File=4 // may be ORed together
};
// returns a string in YYYY-MM-DD.hh:mm:ss format of the given time_t t
static std::string gmtime(time_t t);
// returns a string in YYYY-MM-DD.hh:mm:ss.uuuuuu format (u=microseconds) of the given timval
static std::string gmtime(timeval t);
void emergency(const char* format, ...) PRINTF;
void emergency(const std::string& line);
void alert(const char* format, ...) PRINTF;
void alert(const std::string& line);
void critical(const char* format, ...) PRINTF;
void critical(const std::string& line);
void error(const char* format, ...) PRINTF;
void error(const std::string& line);
void warning(const char* format, ...) PRINTF;
void warning(const std::string& line);
void notice(const char* format, ...) PRINTF;
void notice(const std::string& line);
void info(const char* format, ...) PRINTF;
void info(const std::string& line);
#ifdef DEBUG_ENABLED
void debug(const char* format, ...) PRINTF;
void debug(const std::string& line);
void debugInternal(const char* format, ...) PRINTF;
void debugInternal(const std::string& line);
#else
void debug(const char*, ...) PRINTF { /* do nothing */ }
void debug(const std::string&) { /* do nothing */ }
void debugInternal(const char*, ...) PRINTF { /* do nothing */ }
void debugInternal(const std::string&) { /* do nothing */ }
#endif
void log(Severity s, const char* format, ...) PRINTF3; // C style
void log(Severity s, const std::string& line); // C++ style :-)
// log messages with a lower severity are ignored
void setLevel(Severity s);
Severity getLevel() const;
static void setTarget(Target t);
static Target getTarget();
const std::string& getPrefix() const;
explicit Logger(const std::string& my_prefix, Severity my_severity);
explicit Logger(Logger& parent, const std::string& my_prefix, Severity my_severity = Severity::Inherited);
// non-copyable:
Logger(const Logger&) = delete;
void operator=(const Logger&) = delete;
#ifdef DEBUG_ENABLED
class Stream
{
Stream(Logger*);
Stream(Stream& parent, const std::string& msg);
Logger* L;
Stream* parent;
std::string s;
public:
~Stream();
friend class Logger;
template<class T>
friend Logger::Stream operator<<(Logger::Stream, const T&);
friend Logger::Stream operator<<(Logger::Stream, const char*const);
};
operator Stream();
#else
template<class T>
Logger& operator<<(const T&) { return *this; }
#endif
private:
friend Logger& getLogger();
const std::string prefix;
Severity loglevel;
};
#ifdef DEBUG_ENABLED
template<class T>
Logger::Stream operator<<(Logger::Stream, const T&);
Logger::Stream operator<<(Logger::Stream, const char*const);
#endif
// clean up defines which may collide with other headers...
#undef PRINTF
#undef PRINTF3
#endif // LOGGER_HH

14
server/logger_config.hh Normal file
View File

@ -0,0 +1,14 @@
#ifndef LOGGER_CONFIG_HH
#define LOGGER_CONFIG_HH
// maximum length of a log line. longer log messages will be folded
#define LOGGER_MAX_LOG_LINE_LENGTH (4096)
// enable logging to syslog
#define LOGGER_ENABLE_SYSLOG (1)
// use ASCII-only characters to tag log lines
// #define LOGGER_USE_ASCII_TAGS (1)
#endif // LOGGER_CONFIG_HH