c++ Log file - colorful, daily rotating

You need to keep track of the events or activities that occur during the execution of the program you are developing now. You create a log file for this purpose. Log files are also necessary for debugging during development. Log files that serve this purpose must contain the following entry:

  • Logging time : The time when the log is written. It can be expressed in mili second units. However, if the log frequency is low, the log in seconds may be OK.
  • Log Level : It represents the importance of the log and is generally divided into debug, info, notice, .. error, and so on.
  • Log contents : What you want to record. It is good to be compact and to express the contents clearly.

Time recording

For server programs, tens to hundreds of logs can be generated per second. Therefore, whenever possible, log time should be logged in milliseconds rather than seconds. Logging in microseconds is possible, but on some systems these values ​​are meaningless. In this article, I'll cover creating a microsecond log.

Second unit logging

If you want to log in seconds, the following code is sufficient


time_t tm_t;
struct tm *ntime;

time(&tm_t);
ntime = localtime(&tm_t);
printf(stdout, "%d%02d%02d-%02d:%02d:%02d", ntime->tm_year + 1900, ntime->tm_mon + 1, ntime->tm_mday, ntime->tm_hour, ntime->tm_min, ntime->tm_sec);


And time_t is define as long int.

typedef long int __time_t;


Get the current count time (seconds passed from 00:00:00 UTC 1 January 1970) with the time function. So the tm_t contains only second information.


Next call the localtime function. The declaration of the localtime function is as follows:


struct tm *localtime(const time_t *timep);

The localtime function takes the current time(long int type seconds) as a parameter and returns it in tm structure format of locale time.

So the return tm structure contains only second information.
As you can see, tm_sec is the minimum unit value.


struct tm {
  int tm_sec;
  int tm_min;
  int tm_hour;
  int tm_mday;
  int tm_mon;
  int tm_year;
  int tm_wday;
  int tm_yday;
  int tm_isdst;
};

Microsecond(milisecond) Unit logging

If you want to log in milliseconds, you have to use a different function gettimeofday.


timeval curTime;

gettimeofday(&curTime, NULL);
ntime = localtime(&curTime.tv_sec);


The gettimeofday function takes timeval parameter. The timeval structure is as follows:


struct timeval {
  long tv_sec;
  long tv_usec;
};

The tv_sec is equal to the value of tm_t. And there's additional value tv_usec. This value is the microsecond value in seconds. So time goes, when this value reaches 1000000, the tv_sec value increases by 1 and tv_usec becomes 0 again.

So the tv_sec value can be used as a parameter of the localtime function, and the tv_usec value can be used to measure microsecond values.

Therefore, the following code is sufficient to represent time in milliseconds.

timeval curTime;
struct tm *ntime;
long milisecond;
 
gettimeofday(&curTime, NULL);
ntime = localtime(&curTime.tv_sec);
milisecond = curTime.tv_usec /1000;
printf(stdout, "%d%02d%02d-%02d:%02d:%02d.%03d", ntime->tm_year + 1900, ntime->tm_mon + 1, ntime->tm_mday, ntime->tm_hour, ntime->tm_min, ntime->tm_sec, milisecond);

Log Level

I prefer the following log format: The log level, log time, and log contents are displayed in order .


[ notice ] [2019/11/17-20:34:16.241]:This is notice log  x= 2
[warning ] [2019/11/17-20:34:16.241]:This is warning log  x= 3
[  err   ] [2019/11/17-20:34:16.241]:This is err log  x= 4
[critical] [2019/11/17-20:34:16.241]:This is critical log  x= 5
[  file  ] [2019/11/17-20:34:16.241]:This is file_ony log  x= 6

Screen color output

Log output generally proceeds to both screen and file output at the same time. Of course, when running in daemon mode, the screen output is skipped. Coloring by log level in screen output is much easier to read.

Let's compile and run this code.


#include <stdio.h>
int main(int argc, char *argv[])
{
    fprintf(stdout, "\033[1;32;40m Bright Green  \n");
    return 0;
}

You can see that the color of the output string has changed.



The above ANSI escape code will set the text colour to bright green. The format is;
\033[  Escape code, this is always the same
1 = Style, 1 for normal.
32 = Text colour, 32 for bright green.
40m = Background colour, 40 is for black.

This table shows some of the available formats;


Text color
Code
Text style
Code
Background color
Code
Black
30
No effect
0
Black
40
Red
31
Bold
1
Red
41
Green
32
Underline
2
Green
42
Yellow
33
Negative1
3
Yellow
43
Blue
34
Negative2
5
Blue
44
Purple
35


Purple
45
Cyan
36


Cyan
46
White
37


White
47



I used the following string to control the text color output.


const char *g_level_color[] = {
        "\033[1;32;40m",     //Bright Green
        "\033[1;36;40m",     //Bright Cyan
        "\033[1;35;40m",     //Bright Magenta
        "\033[1;34;40m",     //Bright Blue
        "\033[1;31;40m",     //Bright Red
        "\033[1;31;40m",     //Bright Red
        "",                  //useless
        ""
};

You can use these values ​​to make your readability much better like this:


Full Source Codes



#include "log.h"
#include <stdarg.h>
#include <sys/time.h> // for clock_gettime()

log_level g_log_level = warning;
pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER;
const char *g_level_msg[] = {
 "[ debug  ]",
 "[  info  ]",
 "[ notice ]",
 "[warning ]",
 "[  err   ]",
 "[critical]",
 "[  file  ]",
 ""
};

const char *g_level_color[] = {
        "\033[1;32;40m",     //Bright Green
        "\033[1;36;40m",     //Bright Cyan 
        "\033[1;35;40m",     //Bright Magenta 
        "\033[1;34;40m",     //Bright Blue
        "\033[1;31;40m",     //Bright Red 
        "\033[1;31;40m",     //Bright Red
        "",                  //useless
        ""
};

const char *DEFAULT_COLOR = "\033[1;37;40m";
void set_log_level(log_level val);

void LogToFile(log_level level, const char * szFormat, ...)
{

 if(level < g_log_level) return;
 if(level > file_only) return;

 va_list args;
 struct tm *ntime;
 char szPath[256], p[2048], szTime[128];
 timeval curTime;
 FILE *fhLog = NULL;

 gettimeofday(&curTime, NULL);
 ntime = localtime(&curTime.tv_sec);
 sprintf(szTime, "%s [%d/%02d/%02d-%02d:%02d:%02d.%03d]:", g_level_msg[level], ntime->tm_year + 1900, ntime->tm_mon + 1, ntime->tm_mday, ntime->tm_hour, ntime->tm_min, ntime->tm_sec, (int)(curTime.tv_usec/ 1000));
 sprintf(szPath, "%s/rtp_relay_%d%02d%02d.log",  LOG_PATH , ntime->tm_year + 1900, ntime->tm_mon + 1, ntime->tm_mday);

 va_start(args, szFormat);
 vsnprintf (p, sizeof(p), szFormat, args);
 va_end(args) ;

 if(level != file_only) fprintf(stderr, "%s%s%s%s\n", g_level_color[level], g_level_msg[level],DEFAULT_COLOR,  p);

 fhLog = fopen(szPath, "a+");
 if(NULL == fhLog){
  //error  
  fprintf(stderr,  "Log File Path[%s] Not Found\n" , szPath); 
  return;
 }
 //pthread_mutex_lock(&g_log_mutex);
 string str = szTime + (string)p + "\n";
 fwrite(str.c_str(), 1, str.length() + 1, fhLog);
 //pthread_mutex_unlock(&g_log_mutex);
 fclose(fhLog);
 fhLog = NULL;
}

void set_log_level(log_level val)
{
 g_log_level = val;
}
<log.cpp>



#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>
#include <string.h>
#include <iostream>
#include <string>
#include <time.h>
#include <fcntl.h>

using namespace std;

#define LOG_PATH  "/var/log"

enum log_level {
    debug = 0,
    info,
    notice,
    warning,
    err,
    critical,
    file_only
};

extern log_level g_log_level;

void LogToFile(log_level level, const char * szFormat, ...);
void set_log_level(log_level val);
<log.h>



#include "log.h"
#include <sys/time.h> // for clock_gettime()

int main(int argc, char *argv[])
{
    int x = 0;
    set_log_level(notice); 
    LogToFile(debug, "This is file_ony log  x= %d", x++);
    LogToFile(info, "This is info log  x= %d", x++);
    LogToFile(notice, "This is notice log  x= %d", x++);
    LogToFile(warning, "This is warning log  x= %d", x++);
    LogToFile(err, "This is err log  x= %d", x++);
    LogToFile(critical, "This is critical log  x= %d", x++);
    LogToFile(file_only, "This is file_ony log  x= %d", x++);
  

    sleep(1); 
    return 0;
}
<main.cpp>

Wrapping up


The above codes can be built with the following command:


g++ main.cpp log.cpp -lrt -o output

You can modify the log file name and path name to suit your purpose. If you want to use the log.cpp in a multithreaded environment, uncomment pthread_mutex_lock, pthread_mutex_unlock functions.
Happy logging.


댓글

이 블로그의 인기 게시물

MQTT - C/C++ Client

RabbitMQ - C++ Client #1 : Installing C/C++ Libraries

C/C++ - Everything about time, date