A log Manager for C++

Logging is an important part of any language. Let us assume we are asked to implement the following functionality:

  1. Read a text file containing one integer per line.
  2. Sort the integers in increasing order.
  3. Output the sorted values to another text file.

Download the code and sample data as a zip file from this link. Compiling the code using Cmake and make should give you a screen as shown below.


When you type the following command: "./homework sortint ../sample_data/integers.txt /tmp/sorted_result.txt", The code should generate a result in the file "/tmp/sorted_result.txt". This file will contain the sorted integers. The console output is shown below:


The function declarations are in "Sorter.h". If you go through the "Sorter.h" file, you will notice that the code is well commented. The required functionality is implemented in the function definitions in the file "Sorter.cpp". Let us look at the error and status messages written by this code. For example, look at the following lines in "Sorter.cpp":


printf("Error, could not open file: %s\n", inFileName.c_str());

printf("Finished sorting, number of integers:%d\n", (int) integerVector.size());

These error and status messages are printed to the screen. This may be OK if you are writing a few lines of code to learn. But this will be troublesome if deployed in a production environment. Imagine someone using your code at several places in a production environment. In a production environment, the function Sorter::sortInAscending may be called multiple times. Each time this function is called, the printf functions would also be called and coressponding content would be written out to the console. To prevent these messages from being written to the console, you have two options:

  1. Comment out the printf lines in your code. This is easy if your code is all in Sorter.cpp. But what if a function in Sorter.cpp is calling functions in other files? Will you comment out the printf statements there as well?
  2. When the application is executed, you can direct the console output to a null stream. This idea will work only to a certain point. If there is a bug in your code that you want to analyze, you will have to enable the console output. When you do that, all printf and cout statements will write to the console. This is OK if your code is just using 2 or 3 functions. But this is not true in a production environment. If all the code outputs their error and status messages to console, you would have to go through several lines to identify the specific messages you are interested in. If the code is truly large, then outputting all the error messages could become a performance bottleneck.

Logging allows you to control which status or error messages should be printed and which ones should be curtailed. A functional C++ log manager is implemented in the files "util/LogManager.h" and "util/LogManager.cpp". The functions we will discuss in this blog are:

LogManager::writePrintfToLog(int logLevel, std::string className, const char *printfStart, ...)

LogManager::resetLogFile()

LogManager::isLogDisabled()

Let us replace the "printf(" statement in Sorter::sortInAscending with LogManager::writePrintfToLog(LogManager::Status, "Sorter::sortInAscending",

The function should now look like this:

void Sorter::sortInAscending(std::string& inFileName, std::string& outFileName){
	std::vector<int> integerVector;
	readIntegersFromFile(inFileName, integerVector);
	LogManager::writePrintfToLog(LogManager::Status, "Sorter::sortInAscending", "Finished reading file:%s, number of integers:%d\n", inFileName.c_str(), (int) integerVector.size());
	std::sort(integerVector.begin(), integerVector.end());
	LogManager::writePrintfToLog(LogManager::Status, "Sorter::sortInAscending", "Finished sorting, number of integers:%d\n", (int) integerVector.size());
	writeVectorToFile(outFileName, integerVector);
	LogManager::writePrintfToLog(LogManager::Status, "Sorter::sortInAscending", "Finished writing file:%s, number of integers:%d\n", inFileName.c_str(), (int) integerVector.size());
}

Compile the code and execute it. You should see the following lines on console:


You will notice that the statements in the function Sorter::sortInAscending have not printed to console. If you look at the function LogManager::getLogFileName(), you will notice that the log file is set to "/tmp/log_data.txt". However, the log file is not generated. The reason is that all log entries are disabled by default in the function LogManager::isLogDisabled. This function needs to return "false" for a log entry to be written to file. The current content of this function is:

bool LogManager::isLogDisabled(std::string className, int logLevel){
  if (logLevel == LogManager::Critical)
    return false;
    
  return true;
}

Only those log statements having logLevel set to LogManager::Critical are printed. All other log files are ignored. In the file LogManager.cpp, modify the function LogManager::isLogDisabled to look like this:

bool LogManager::isLogDisabled(std::string className, int logLevel){
  if (logLevel == LogManager::Critical)
    return false;
  
  if (className.find("Sorter::sortInAscending") == 0
      && logLevel >= LogManager::Status)
    return false;
  
  return true;
}

Save the file LogManager.cpp, recompile and execute the code using these commands:

make; ./homework sortint ../sample_data/integers.txt /tmp/sorted_result.txt

If you open /tmp/log_data.txt, you should see the following entries:

Sorter::sortInAscending:Finished reading file:../sample_data/integers.txt, number of integers:8

Sorter::sortInAscending:Finished sorting, number of integers:8

Sorter::sortInAscending:Finished writing file:../sample_data/integers.txt, number of integers:8

Now, change line nos 88 in LogManager.cpp to the following:

 if (logLevel == LogManager::Error)

The above change allows all log entries marked as LogManager::Error to be written to the log file. Change line no 8 in Sorter.cpp to the following:

LogManager::writePrintfToLog(LogManager::Error, "Sorter::readIntegersFromFile", "Error, could not open file: %s\n", inFileName.c_str());

Save all files, recompile the code and execute the following on command prompt (Note that we have changed the input file name):

make; ./homework sortint ../sample_data/abc.txt /tmp/sorted_result.txt

The log file /tmp/log_data.txt will contain following lines:

Sorter::sortInAscending:Finished reading file:../sample_data/integers.txt, number of integers:8

Sorter::sortInAscending:Finished sorting, number of integers:8

Sorter::sortInAscending:Finished writing file:../sample_data/integers.txt, number of integers:8

Sorter::readIntegersFromFile:Error, could not open file: ../sample_data/abc.txt

Sorter::sortInAscending:Finished reading file:../sample_data/abc.txt, number of integers:0

Sorter::sortInAscending:Finished sorting, number of integers:0

Sorter::sortInAscending:Finished writing file:../sample_data/abc.txt, number of integers:0

Note line no 4:

Sorter::readIntegersFromFile:Error, could not open file: ../sample_data/abc.txt

This has been generated due to the changes we made to line nos 88 in LogManager.cpp and line no 8 in Sorter.cpp. You will also notice that the log entries are being appended to the log file. Add the following code to the first line in Sorter::sortInAscending

LogManager::resetLogFile();

Save all files, compile and run the following command:

make; ./homework sortint ../sample_data/abc.txt /tmp/sorted_result.txt

You should see the following entry in the log file /tmp/log_data.txt

Log Started Mon Aug 17 15:46:29 2020
 
Sorter::readIntegersFromFile:Error, could not open file: ../sample_data/abc.txt

Sorter::sortInAscending:Finished reading file:../sample_data/abc.txt, number of integers:0

Sorter::sortInAscending:Finished sorting, number of integers:0

Sorter::sortInAscending:Finished writing file:../sample_data/abc.txt, number of integers:0

Making a call to LogManager::resetLogFile() deletes all previous entries from the log file and adds a time stamp to the beginning of the log file (Notice the first line in the log file). Now, change LogManager::isLogDisabled to the following (Changes have been made to line nos 88 and 91):

bool LogManager::isLogDisabled(std::string className, int logLevel){
  if (logLevel == LogManager::Critical)
    return false;
  
 if (className.find("Sorter::") != -1
      && logLevel >= LogManager::Status)
    return false;
  
  return true;
}

This will allow log entries from any function starting with Sorter:: to be written to the log file. Make the following changes to function Sorter::readIntegersFromFile. The two printf statements in this function have been changed to use LogManager

void Sorter::readIntegersFromFile(std::string& inFileName, std::vector<int>& outIntegerVector){
  FILE *filePointer;
  filePointer=fopen(inFileName.c_str(), "r");
  if(filePointer==NULL){
    LogManager::writePrintfToLog(LogManager::Error, "Sorter::readIntegersFromFile", "Error, could not open file: %s\n", inFileName.c_str());
    return;
  }
  int currInt;
  int fscanfStatus = fscanf(filePointer, "%d", &currInt);
  while(fscanfStatus>0){
	  LogManager::writePrintfToLog(LogManager::Error, "Sorter::readIntegersFromFile", "fscanfStatus:%d\n", fscanfStatus);
	  outIntegerVector.push_back(currInt);
  	  fscanfStatus = fscanf(filePointer, "%d", &currInt);
  }
  LogManager::writePrintfToLog(LogManager::Error, "Sorter::readIntegersFromFile", "Final fscanfStatus:%d\n", fscanfStatus);
  
  fclose(filePointer);
}

Save all files, compile the code and run the following command:

make; ./homework sortint ../sample_data/integers.txt /tmp/sorted_result.txt

You should see the following entries in the log file /tmp/log_data.txt:

Log Started Mon Aug 17 15:55:17 2020
 
Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:Final fscanfStatus:-1

Sorter::sortInAscending:Finished reading file:../sample_data/integers.txt, number of integers:8

Sorter::sortInAscending:Finished sorting, number of integers:8

Sorter::sortInAscending:Finished writing file:../sample_data/integers.txt, number of integers:8

Change line number 91 in LogManager.cpp to the following:

 if (className.find("Sorter::readIntegersFromFile") != -1

Save the file, compile the code and run the following command:

make; ./homework sortint ../sample_data/integers.txt /tmp/sorted_result.txt

The log file will now contain entries only from the function Sorter::readIntegersFromFile

 Log Started Mon Aug 17 15:56:50 2020
 
Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:fscanfStatus:1

Sorter::readIntegersFromFile:Final fscanfStatus:-1

The final version of the code can be downloaded as a zip file from here. The functions discussed so far should be sufficient to enable one to use the LogManager effectively. There are many other helper functions that you can use in LogManager. You may want to look at LogManager::setLogDirectory, LogManager::getLogFileOStream and LogManager::flushLogFileOStream. Together, these will allow you to replace std::cout in your code. We will leave it to the readers to go through the source code and learn other functions. Happy coding!!!

Comments

Popular posts from this blog

Discrete Wavelet Transform

Geometric image transforms

Testing image capture with high speed cameras