How to make my C code have multi-level logs

How to make my C code have multi-level logs


Sarah, an embedded engineer in the software IP development team, writes tons of C code daily. All the company employees including her colleagues and boss are the users of her code. She has to make sure that her code is flexible for all the users. For example, her co-workers need all the details at all the levels of the program execution while her boss is only interested in the results. For this Sarah uses multi-level logs in her code.

What are multi-level logs?

Categorizing the logs thus forming a level for each and printing the logs on screen only when that level is required
is called multi-level logging. Sarah came across this when she started reading the Kernel Code. The Kernel has various logging levels as follows:
1.    KERN_EMRG                     
2.    KERN_ALERT  
3.    KERN_CRIT
4.    KERN_ERR
5.    KERN_WARNING
6.    KERN_INFO
7.    KERN_DEBUG

Why does your code need it?

Multi-level logging makes your code flexible. Sometimes everything just works perfect and you just need to know the end results while at other times you need to debug an issue which may require more than just results. Multi-level logs come to your rescue here.

Ways to write code with multi-level logging:

Passing the debug level value from Makefile – Compile time

In this case Sarah passed the value of the level of logging she want through command line while compiling.
Sarah's makefile is as follows: (The same command one can fire from the command line)
all: multi_level_debugging.c
        gcc -DDEBUG -DDEBUG_LEVEL='0' -I. multi_level_debugging.c -o multi_level_debugging
One needs to put "-D" in front of any MACRO one wants to be defined at compile time. "-D" logically equals to "#define".
The DEBUG_LEVEL Macro here is passed and used in the below header file.

Sarah's header file contains:
#define debug_print(level, fmt, ...) \
   do {if(level <= DEBUG_LEVEL) fprintf(stderr, fmt, __VA_ARGS__);} while (0);
Sarah's source file contains:
#include <stdio.h>
#include "multi_level_debugging.h"

int main ()
{
   char string[6] = "Hello";

   debug_print (3, "%s.\n", string);
   return 0;
}

In this code when someone forgets to write the switch in Makefile or command line it gives compile time error.

Passing the debug level value from a configuration file - Run time

Sarah also came up with multi-level logging through a configuration file, if one finds the Makefile difficult to edit. This is a slightly better method as one doesn't need to recomplie the code after changing the debug level.
In this method Sarah makes a configuration file which will contain the logging level. This configuration file will then be parsed run time from the code and the all the prints with their level having below the debug level will shown.
The configuration file can have your own rules and hence you own parsing methods need to applied.
Sarah's configuration file looks as follows:
DBG_LVL=3
#SAN=1
Here "#" starts a comment and it has 'name=value' pairs. The parsing code snippet is as follows:
int parser (FILE *fPtr)
{
        char line[MAX_LINE_LENGTH] = {0};
        char *linePtr, *category, *test_case;
        char *token = "=";
        int line_number = 1;

        while (!feof(fPtr)) {
                /* Read a line */
                linePtr = fgets (line, MAX_LINE_LENGTH, fPtr);
                /* If error reading */
                if (linePtr != line) {
                        break;
                }
                /* If the line is empty */
                if (0 == linePtr[0]) {
                        continue;
                }
                /* Ignore a line starting with '#' */
                if ('#' == *linePtr) {
                        continue;
                }
                /* Ignore the leading white spaces */
                while (isspace(*linePtr))
                        linePtr++;

                /* Find out the Category */
                category = strtok (linePtr, token);
               
                char *token2 = ",";
                int index = 0;

                if (line_number == 1 & (strcmp (category, "DBG_LVL") == 0)) {
                        char *token3 = "\n";
                        test_case = strtok (NULL, token3);
                        if (NULL == test_case)
                                break;
                        debug_level = atoi(test_case);
                        printf ("Debug Level is %d.\n", debug_level);
                }
                else {
                        printf ("The first line in Configuration File needs to mention the debug level.\n");
                        return FAILURE;
                }

... /* Further configuration file parsing related to Sarah's project */
}

Here's Sarah's header file (relevant code snippet):
/* Debug Level Variable.
 * Value will be obtained from configuration file.*/

int debug_level;


/* Debug Prints */
#define debug_print(level, fmt, ...) \
        do {if (level <= debug_level) fprintf (stderr, fmt, __VA_ARGS__);} while(0);
Sarah uses the multi-level logs pretty much as she did in the previous case:
#include <stdio.h>
#include "multi_level_debugging.h"

int main ()
{
   char string[6] = "Hello";

   debug_print (3, "%s.\n", string);
   return 0;
}

If you are already having a configuration file in your project you can easily integrate this multi-level logging just like Sarah did.
Now you got Sarah's secret ingredient to flexible prints.

Happy Embedding !!

No comments:

Post a Comment