Skip to content

Instantly share code, notes, and snippets.

@fsmv
Created June 29, 2015 23:13
Show Gist options
  • Save fsmv/e9c507db06c3f06f01f9 to your computer and use it in GitHub Desktop.
Save fsmv/e9c507db06c3f06f01f9 to your computer and use it in GitHub Desktop.
C++ Logger
/**
* The MIT License (MIT)
*
* Copyright (c) 2015 Andrew Kallmeyer
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <type_traits>
#include <ctime>
#include <cstring>
#include "logger.h"
const char *Logger::printFormat = "%s %s [%s] - %s\n";
const char *Logger::timeFormat = "%Y-%m-%d %H:%M:%S";
static const int MAX_TIME_STR_LEN = 21;
//must be in the same order as the enum
const char *Logger::levelStr[] = { "ERROR", "WARNING", "INFO", "DEBUG" };
Logger::LogLevel Logger::globalLevel = LVL_INFO;
//The lambda here is the deleter for the unique_ptr
//it is a destructor for the FILE pointer basically
decltype(Logger::logFile) Logger::logFile(nullptr, [](FILE *ptr) {
fprintf(ptr,
"\n========================================"
"========================================\n\n");
fclose(ptr);
});
static Logger logger("Logger");
void Logger::log(LogLevel level, const char *message) {
if (willLog(level)) {
//get time string
time_t currentTime;
time(&currentTime);
char timeStr[MAX_TIME_STR_LEN];
strftime(timeStr, MAX_TIME_STR_LEN - 1,
Logger::timeFormat, localtime(&currentTime));
//print to console
printf(Logger::printFormat, timeStr, Logger::getLevelName(level), name,
message);
if (Logger::logFile.get()) {
//print to the file
fprintf(Logger::logFile.get(), Logger::printFormat, timeStr,
Logger::getLevelName(level), name, message);
//flush if we're at either warn or error
if (forceFlush || level <= LVL_WARN) {
fflush(Logger::logFile.get());
}
}
}
}
void Logger::setLogFile(const char *file) {
if (strcmp(file, "NULL") != 0) {
Logger::logFile.reset(fopen(file, "a"));
if (!Logger::logFile.get()) {
logger.warn("Could not open log file"); //goes to the console
}
}else{ //if the file name was null just don't have a file
Logger::logFile.reset(nullptr);
}
}
bool Logger::getForceFlush() const {
return forceFlush;
}
void Logger::setForceFlush(bool forceFlush) {
this->forceFlush = forceFlush;
}
bool Logger::willLog(LogLevel level) const {
return level <= globalLevel || level <= localLevel;
}
Logger::LogLevel Logger::getGlobalLogLevel() {
return Logger::globalLevel;
}
Logger::LogLevel Logger::getLocalLogLevel() const {
return this->localLevel;
}
void Logger::setGlobalLogLevel(LogLevel level) {
Logger::globalLevel = level;
}
void Logger::setLocalLogLevel(LogLevel level) {
this->localLevel = level;
}
const char *Logger::getLevelName(LogLevel level) {
if (level >= 0 && level < (int)std::extent<decltype(levelStr)>()) {
return levelStr[level];
}else{
return "INVALID";
}
}
Logger::LogLevel Logger::parseLogLevel(const char *level) {
for (unsigned int i = 0; i < std::extent<decltype(levelStr)>(); ++i) {
if (strcmp(level, levelStr[i]) == 0) {
return (LogLevel) i;
}
}
return (LogLevel) -1;
}
/**
* The MIT License (MIT)
*
* Copyright (c) 2015 Andrew Kallmeyer
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef _LOGGER_H_
#define _LOGGER_H_
#include <cstdio>
#include <string>
#include <memory>
#include <functional>
/**
* Logger class with shared global file
*
* There are different logging levels and they're ordered as follows:
* ERROR < WARN < INFO < DEBUG. If when you log the level you're logging at is
* less than or equal to either the local log level or the global log level it
* will get printed to the console and to the file if there is one. Otherwise
* the statement will be skipped.
*
* Because of the nature of this logger it is not recommended to permanently
* print high frequency updates, such as position updates from the PPT, even in
* the debug level. If however you are debugging it would be fine to
* temporarily print like that if needed. However the HADDIDON would probably
* suit your needs.
*
* Usage: include this header, then create a file-level global instance
* with a name for your class. For example: "static Logger logger("my class")"
* Then in any method of that class call the appropriate logging method with
* your message.
*/
class Logger {
public:
enum LogLevel { LVL_ERROR, LVL_WARN, LVL_INFO, LVL_DEBUG };
/**
* Construct a new instance with a "class name" to use
*
* @param name the name to identify this instance
* @param localLoglevel the local maximum logging level
* @param forceFlush force the log file to flush every time this logger
* instance prints.
*/
Logger(const char *name, LogLevel localLogLevel = LVL_ERROR,
bool forceFlush = false)
: name(name), localLevel(localLogLevel), forceFlush(forceFlush) {}
~Logger() {}
/**
* Log to stdout and the log file if set at the specified log level
*
* prints in the format: YYYY-MM-DD HH:MM:SS LEVEL [name] - Message
* @param level the level to log at
* @param message the message to log
*/
void log(LogLevel level, const char *message);
/**
* Equivalent to log(LVL_ERROR, message)
* @param message the message to log
*/
inline void error(const std::string &message) {
log(LVL_ERROR, message.c_str());
}
/**
* Equivalent to log(LVL_WARN, message)
* @param message the message to log
*/
inline void warn(const std::string &message) {
log(LVL_WARN, message.c_str());
}
/**
* Equivalent to log(LVL_INFO, message)
* @param message the message to log
*/
inline void info(const std::string &message) {
log(LVL_INFO, message.c_str());
}
/**
* Equivalent to log(LVL_DEBUG, message)
* @param message the message to log
*/
inline void debug(const std::string &message) {
log(LVL_DEBUG, message.c_str());
}
/**
* Determines whether or not the logger will log at a given level or not
*
* @param level the level to check
* @return whether or not the logger will log
*/
bool willLog(LogLevel level) const;
/**
* Returns the local log level
* @return the local log level
*/
LogLevel getLocalLogLevel() const;
/**
* Sets maximum local log level to output
* ERROR < WARN < INFO < DEBUG
*
* @param level the cutoff level
*/
void setLocalLogLevel(LogLevel level);
/**
* Returns whether or not this instance forces flushing the log file
* @returns whether we force flushing or not
*/
bool getForceFlush() const;
/**
* Sets whether or not this instance forces flushing the log file
*
* This is a slight performance hit, but if your class is infrequently
* logging important information this may be what you need.
*
* @param forceFlush whether or not we should force flushing
*/
void setForceFlush(bool forceFlush);
/**
* Sets the file to log to
*
* if nullptr do not log to a file
* @param file file to log to
*/
static void setLogFile(const char *file);
/**
* Returns the global log level
* @returns the global log level
*/
static LogLevel getGlobalLogLevel();
/**
* Sets maximum global log level to output
* ERROR < WARN < INFO < DEBUG
*
* @param level the cutoff level
*/
static void setGlobalLogLevel(LogLevel level);
/**
* Takes a string and converts it to a log level enum value.
*
* @param levelstr the string to parse
* @return the parsed value, or -1 on error;
*/
static LogLevel parseLogLevel(const char *levelstr);
/**
* Takes a LogLevel and converts it to a string
*
* @param level level to convert
* @return a constant string representing the log level
*/
static const char *getLevelName(LogLevel level);
private:
//!The format sent to printf for each log message.
//!When it is sent there are 4 strings in the following order:
//!time, level, class name, message
static const char *printFormat;
//!The format for the time string in the log messages.
//!It is sent to strftime, so the string should be what strftime expects
//!If you change this you'll also have to change the MAX_TIME_STR_LEN
//!define in Logger.cpp
static const char *timeFormat;
//!A list of the names for each log level in order.
//!Used for converting enums to strings
static const char *levelStr[4];
//!The global log level shared by all instances
static LogLevel globalLevel;
//!The global log file which all instances log to
static std::unique_ptr<FILE, std::function<void(FILE*)>> logFile;
//!The name of this logger instance which gets printed in each message
const char *name;
//!The log level for this instance which may override the global level
LogLevel localLevel;
//!If true this instance flushes the file every time it prints
bool forceFlush;
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment