Logger in C++

 


Logger program in C++


Designing the logger system which logs the various system states during run time.
We can configure the logging level (warning, debug, error, etc ,.. ), also we can configure the output platform
(file or console).Default output path is console. If the user doesn't provide the file path when it selects
the output path as file, the default file path will be taken. Default file path is predefined.

Requirements:

1. Design Logger system in c++
2. This logger should provide the configurable output platform (file or console).
3. This logger should provide method to filter the logs during run time.
4. Provide default file incase client missed to provide the file for logs dumping.

Programming decisions:

1. Preferred singleton pattern since we need to create only one object , and all the client access the same.
2. Instance defined as the shared ptr. So if all the client objects dereferenced then reference count becomes
zero and destructor will get invoked for the shared ptr.



Note: Simple logger without mutithreading


//Header file

#ifndef LOGGER_H
#define LOGGER_H

//Header file declartions
#include <iostream>
#include <memory> //smart pointers
#include <string>
#include <cstring>
#include <fstream> //file operations
#include <time.h> //print time info

//namespace declarations
using namespace std;

namespace systemLogger
{
    enum logLevels
    {
        NONE = 0,
        INFO = 1,
        DEBUG = 2,
        WARNING = 4,
        ERROR = 8,
        ALLLOGS = 16
    };

    enum logOutputPath
    {
        INVALIDPATHTYPE  = 0,
        CONSOLETYPE,
        FILETYPE
    };

    const string DEFAULT_LOG_DUMP_FILE = "logsdump.txt";
    const uint16_t MAX_NUMBER_CHAR = 500;

    //Class declation
    class logger
    {
        private:
        //static instance , we need to have static instance so that only once object get created, and all the clients
        //access the same object.
        //singleton pattern
        static shared_ptr<logger> loggerInstance; //single object accessed by different users as different object

        //log path
        logOutputPath logPathType;
        ofstream outputFile; //file to dump the logs
        uint32_t logFilter;

        public:

        ~logger()
        {
            if(outputFile.is_open())
            {
                outputFile.close(); //closing file if its opened
            }
        }
       
        logger()
        {
            logPathType = INVALIDPATHTYPE;
            logFilter = 0;
        }

        //static function to get the static instance of the logger obj
        static shared_ptr<logger> getInstance();

        //set prefernce , if the preference doesn't set then logger will not dump the logs
        void setPreferences(logOutputPath outputPath, string filePath, uint32_t logFilters);

        //log function
        template<typename... Args>
        void Log(logLevels logLevel, string functionname, time_t time, string log, Args... args)
        {
            char buffer[MAX_NUMBER_CHAR];
            auto fomattedText = [=, &buffer]()
            {
                string logname = string("[ ")+strtok(ctime(&time), "\n")+ string(" ] [")+getLogLevelName(logLevel)+"] "+functionname+ ": "+log+" ";
                sprintf(buffer, logname.c_str(), args...);
                return buffer;
            };
           
            //print the log on desired location
            if((NONE != logLevel) && (isLogLevelEnabled(logLevel)))
            {
                if(FILETYPE == logPathType) //dump the logs into the file
                {
                    if(outputFile.good())
                    {
                        outputFile<<fomattedText()<<endl;
                    }
                }
                else if(CONSOLETYPE == logPathType) //dump the logs to console
                {
                    cout<<fomattedText()<<endl;
                }
                else
                {
                    cout<<"error in logpathtype. no logs printed !!!!!!!"<<endl;
                }
            }
            else
            {
                //cout<<"Log level not enabled. no logs printed !!!!!!!"<<endl;
            }
        }

        //utility functions
        inline logOutputPath getlogPathType()
        {
            return logPathType;
        }
        bool isLogLevelEnabled(uint8_t level);
        string getLogLevelName(logLevels level);
    };

}
#endif

//Source file

//Header file declarations
#include "logger.h"

//namespace declarations
using namespace systemLogger;

// static variables declarations
shared_ptr<logger> logger::loggerInstance = 0;

/************ Function definitions of logger class ***************/


//static function to get the object
shared_ptr<logger> logger::getInstance()
{
    //if the object hasn't created yet
    if(0 == loggerInstance)
    {
        //create the shared pointer object
        loggerInstance = shared_ptr<logger> (new logger);
    }
    return loggerInstance;
}

//set prefernce , if the preference doesn't set then logger will not dump the logs
void logger::setPreferences(logOutputPath outputPath, string filePath, uint32_t logFilters)        
{
    //set the log filter
    logFilter = logFilters;

    //Output path is file, then set the file path.
    if(FILETYPE == outputPath)
    {
        logPathType = outputPath;
        if(!filePath.empty())
        {
            outputFile.open(filePath);//create the file if the file is not present
            if(!outputFile.good())
            {
                cout<<"logger::setPreferences:: File open is not successfull -  "<<filePath.c_str()<<endl;
            }
        }
        else
        {
            //if path doesn't spectified , create the default path
            outputFile.open(DEFAULT_LOG_DUMP_FILE);
            if(!outputFile.good())
            {
                cout<<"logger::setPreferences:: File open is not successfull on default path (logsdump.txt)"<<endl;
            }
        }
       
    }
    else
    {
        logPathType = outputPath; // user request or by default
    }
}

//check whether log level enabled
bool logger::isLogLevelEnabled(uint8_t level)
{
    bool ret = false;
    if(0 == logFilter) //no log level enabled
    {
        ret = false;
    }
    else if(ALLLOGS == logFilter) // all levels enabled
    {
        ret = true;
    }
    else
    {
        uint32_t value = logFilter&level;
        //Info enabled
        if((value == (uint8_t)INFO) || (value == (uint8_t)DEBUG) || (value == (uint8_t)WARNING) || (value == (uint8_t)ERROR) )
        {
            ret = true;
        }
    }

    return ret;
}

//get the string name of the log level
string logger::getLogLevelName(logLevels level)
{
    string logLevelName = "";
    switch(level)
    {
        case NONE:  {
            logLevelName = "NONE";
            break;
        }
        case INFO:  {
            logLevelName = "INFO";
            break;
        }
        case WARNING: {
            logLevelName = "WARNING";
            break;
        }
        case DEBUG:  {
            logLevelName = "DEBUG";
            break;
        }
        case ERROR:  {
            logLevelName = "ERROR";
            break;
        }
        default:{
            //do nothing
            break;
        }
    }

    return logLevelName;
}


//unit testing
#include <iostream>
#include "logger.h"
#include <chrono>
#include <iostream>

using namespace systemLogger;
using std::chrono::system_clock;

int main()
{
    //Unit Testing
    shared_ptr<logger> obj = logger::getInstance();
    obj->setPreferences(CONSOLETYPE, "",ALLLOGS);
    obj->Log(DEBUG, __FUNCTION__, time(NULL),"Debug print ");
    obj->Log(ERROR, __FUNCTION__, time(NULL),"ERROR print %d",1);

    return 0;
}