当前位置:   article > 正文

Linux_实现简易日志系统

Linux_实现简易日志系统

       

目录

1、认识可变参数

2、解析可变参数

3、打印可变参数

3.1 va_list

3.2 va_start

3.3 va_arg

3.4 va_end 

3.5 小结 

4、实现日志 

4.1 日志左半部分 

4.2 日志右半部分 

4.3 日志的存档归类 

结语 


前言:

        在Linux下实现一个日志系统,该日志系统主要用于打印和记录程序的格式化信息,还可以对信息做分析处理,比如把信息分为五种类型:正常运行信息、测试信息,警告信息,错误信息、致命信息,当然还可以显示信息产生的具体时间,并且能够对这些信息进行存档归类。

1、认识可变参数

        因为日志系统记录的是程序往显示器上打印的信息,所以肯定离不开printf系列函数,可以得知他的底层肯定是调用了printf系列函数来实现的,而这个过程会涉及到可变参数的传递,所以实现日志系统前要认识可变参数。

        可变参数的形式如下:

  1. int printf(const char *format, ...);
  2. //第二个参数是三个点,...表示可变参数,即可以传多个实参给到printf

        所以可变参数表示可以接收任意数量的参数。

2、解析可变参数

        当我们拿到了一个可变参数,如何对其进行分析拿到具体的每一个值呢?要解析可变参数必须从函数栈帧的角度来理解,示意图如下: 

        从上图可得,只要拿到了format处的地址,后续就可以通过下一步的操作拿到format+1处的内容了,然后让指针继续“向上走”就可以遍历整个可变参数了。以上的逻辑也规定了一点:若想使用可变参数则必须得有一个具体参数,这个规则体现在下面代码处。

3、打印可变参数

        有了上述的解析规则,则可以用代码打印可变参数的每个值,代码如下:

  1. #include <iostream>
  2. #include <stdarg.h>
  3. using namespace std;
  4. void print(int n,.../*6 8 9 2 3*/)
  5. {
  6. va_list s;
  7. va_start(s,n);
  8. while (n--)
  9. {
  10. printf("%d\n",va_arg(s,int));
  11. }
  12. va_end(s);
  13. }
  14. int main()
  15. {
  16. print(5,6,8,9,2,3);
  17. return 0;
  18. }

         运行结果:

        从结果可以看到,打印的顺序和上述分析的逻辑一模一样。下面就来解释上述代码中出现的4个字段:va_list、va_start、va_arg、va_end。

3.1 va_list

        va_list可以看成是一个类型,用他定义的变量就像是创建一个char*的指针。

3.2 va_start

        va_start是一个宏函数,他的作用是对va_list定义的变量进行初始化,他的参数介绍如下:

  1. void va_start(va_list ap, last);
  2. // ap表示用va_list定义的变量
  3. // last表示可变参数的前一个固定参数

        比如上述代码中可变参数的前一个参数是n,所以va_start的第二个参数是n,这一动作就是上述逻辑中让指针定位到format处(这也解释了为什么可变参数前面必须得有固定参数,因为要让指针定位)。

3.3 va_arg

        va_arg也是一个宏函数,他的功能是拿到下一个参数的值(注意是拿下一个,而不是拿当前位置的值),其参数介绍如下:

  1. type va_arg(va_list ap, type)
  2. //第一个参数是va_list定义的变量
  3. //第二个参数表示ap指向下一个参数的类型
  4. //返回下一个参数的值

         从当前地址开始,通过计算拿到下一个参数的地址,然后在根据type类型,对该地址解引用拿到该参数的值并返回,此时会更新ap的位置,以便继续遍历后续的列表。

3.4 va_end 

         va_end是对va_list创建的变量进行清理工作,他的参数介绍如下:

  1. void va_end(va_list ap);
  2. //ap表示清理的目标

3.5 小结 

        按照va_list(创建变量)、va_start(初始化变量)、va_arg(遍历列表)、va_end(清理工作)这四个步骤,就可以拿到可变列表中的每一个值了。

4、实现日志 

        日志信息实际上就是一串字符,日志的目的就是打印到屏幕上给用户观看(或者存档归类方便查看),只不过日志信息更加规范且完善,因为他还记录了信息的时间和类型,所以可以把日志信息分成两个部分:左半部分和右半部分,其中右半部分就是程序的输出信息,而左半部分是记录信息的类型和时间。

        信息类型:信息分为五种类型:info,debug、warning、error、fatal,让用户手动对信息进行分类。

        记录时间:可以用time函数得到一个时间戳,然后再把该时间戳传给localtime函数,localtime会返回一个结构体,里面包含了年月日时分秒等精确时间。

4.1 日志左半部分 

        日志左半部分由信息类型和时间构成,代码如下:

  1. #include <time.h>
  2. #include <iostream>
  3. #include <stdarg.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <unistd.h>
  7. #include <fcntl.h>
  8. #include <cstring>
  9. using namespace std;
  10. #define SIZE 1024
  11. //对信息类型进行define
  12. #define Info 0
  13. #define Debug 1
  14. #define Warning 2
  15. #define Error 3
  16. #define Fatal 4
  17. class log
  18. {
  19. public:
  20. log()
  21. {}
  22. const char* option(int point)//手动选择信息类型
  23. {
  24. switch (point)
  25. {
  26. case Info:
  27. return "Info";
  28. case Debug:
  29. return "Debug";
  30. case Warning:
  31. return "Warning";
  32. case Error:
  33. return "Error";
  34. case Fatal:
  35. return "Fatal";
  36. default:
  37. return "None";
  38. }
  39. }
  40. void logprint(int point, const char *format, ...)
  41. {
  42. //日志左边字符串
  43. char leffbuff[SIZE];
  44. time_t t = time(nullptr);
  45. struct tm *pt = localtime(&t);
  46. snprintf(leffbuff, SIZE, "[%s][%d-%d-%d-%d-%d-%d]",option(point),
  47. pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour,
  48. pt->tm_min, pt->tm_sec);
  49. printf("%s\n",leffbuff);
  50. }
  51. ~log()
  52. {}
  53. private:
  54. };
  55. int main()
  56. {
  57. log l;
  58. l.logprint(Debug,nullptr);
  59. return 0;
  60. }

        运行结果:

        上述中将功能都写进类log内,目的是更好的对日志系统进行封装。

4.2 日志右半部分 

        日志右半部分就是程序格式化的信息,程序的格式化信息一般由printf系列的函数完成,所以要想在logprint内完成printf操作,则必须传递可变参数,因此可以用一个功能极其强大的接口vsnprintf函数,该函数介绍如下:

  1. int vsnprintf(char *str, size_t size, const char *format, va_list ap);
  2. //str表示数组首元素地址,将printf的结果写进str数组中
  3. //size表示要写入数组内容的大小,并且在末尾处自动添加\0,即最多写size-1个字符,
  4. //他会自动留个位置给到\0
  5. //format表示格式化的字符串
  6. //ap表示va_list创建的变量

        所以可以用va_list定义的变量指向可变参数列表,然后把该变量传给vsnprintf,这样就等同于将可变参数传给vsnprintf,则vsnprintf可以将可变参数的值打印出来,只不过不是打印到屏幕上而是写进str内,而这个str就是日志右半部分的字符串,最后在合并左右部分就得到最终的日志信息了。

        代码如下:

  1. #include <time.h>
  2. #include <iostream>
  3. #include <stdarg.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <unistd.h>
  7. #include <fcntl.h>
  8. #include <cstring>
  9. using namespace std;
  10. #define SIZE 1024
  11. //对信息类型进行define
  12. #define Info 0
  13. #define Debug 1
  14. #define Warning 2
  15. #define Error 3
  16. #define Fatal 4
  17. class log
  18. {
  19. public:
  20. log()
  21. {}
  22. const char* option(int point)//手动选择信息类型
  23. {
  24. switch (point)
  25. {
  26. case Info:
  27. return "Info";
  28. case Debug:
  29. return "Debug";
  30. case Warning:
  31. return "Warning";
  32. case Error:
  33. return "Error";
  34. case Fatal:
  35. return "Fatal";
  36. default:
  37. return "None";
  38. }
  39. }
  40. void logprint(int point, const char *format, ...)
  41. {
  42. //日志左边字符串
  43. char leffbuff[SIZE];
  44. time_t t = time(nullptr);
  45. struct tm *pt = localtime(&t);
  46. snprintf(leffbuff, SIZE, "[%s][%d-%d-%d-%d-%d-%d]",option(point),
  47. pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour,
  48. pt->tm_min, pt->tm_sec);
  49. //日志右边字符串
  50. char rightbuff[SIZE];
  51. va_list s;
  52. va_start(s,format);
  53. vsnprintf(rightbuff,SIZE,format,s);
  54. //合并成为最终日志信息
  55. char logmessege[SIZE*2];
  56. snprintf(logmessege,SIZE*2,"%s%s\n",leffbuff,rightbuff);
  57. printf("%s",logmessege);
  58. }
  59. ~log()
  60. {}
  61. private:
  62. };
  63. int main()
  64. {
  65. log l;
  66. int a = 10;
  67. int b = 12;
  68. l.logprint(Debug,"a=%d,b=%d",a,b);
  69. return 0;
  70. }

         运行结果:

4.3 日志的存档归类 

        上面实现的日志只能打印在屏幕上,而日志的主要功能是可以存档归类,方便后续查看,所以上面代码在打印日志这一步时可以做一个判断:1、打印到屏幕,2、打开文件将日志信息写入文件中,3、写入文件时根据文件类型进行文件归类,因此可以在log类中加上两个成员变量,一个是指定日志存放的路径,一个是输出方式,具体成员如下:

  1. private:
  2. string path;//即路径目录
  3. int printmethod;//输出方式:1、显示器 2、文件 3、文件归类

         完整日志代码如下:

  1. #include <time.h>
  2. #include <iostream>
  3. #include <stdarg.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <unistd.h>
  7. #include <fcntl.h>
  8. #include <cstring>
  9. using namespace std;
  10. #define SIZE 1024
  11. //对信息类型进行define
  12. #define Info 0
  13. #define Debug 1
  14. #define Warning 2
  15. #define Error 3
  16. #define Fatal 4
  17. //三种方式
  18. #define Screen 1
  19. #define Onefile 2
  20. #define Classfile 3
  21. #define logfile "log.txt"
  22. class log
  23. {
  24. public:
  25. log()//初始化成员变量
  26. {
  27. path = "./log/";
  28. printmethod = 2;//选取方式2
  29. }
  30. const char* option(int point)//手动选择信息类型
  31. {
  32. switch (point)
  33. {
  34. case Info:
  35. return "Info";
  36. case Debug:
  37. return "Debug";
  38. case Warning:
  39. return "Warning";
  40. case Error:
  41. return "Error";
  42. case Fatal:
  43. return "Fatal";
  44. default:
  45. return "None";
  46. }
  47. }
  48. //打印日志信息的三种方式具体实现
  49. void printscreen(const char* logmessege)
  50. {
  51. printf("%s",logmessege);
  52. }
  53. void printonefile(const string& filename,const char* logmessege)
  54. {
  55. string logpath = path+filename;
  56. int fd = open(logpath.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
  57. if (fd<0)
  58. {
  59. perror("open");
  60. exit(-1);
  61. }
  62. //写
  63. write(fd,logmessege,strlen(logmessege));
  64. close(fd);
  65. }
  66. void printclassfile(int point, const char* logmessege)
  67. {
  68. string logpath = logfile;
  69. (logpath+='.')+=option(point);
  70. printonefile(logpath,logmessege);
  71. }
  72. //选取打印日志的方式
  73. void printflow(int point, const char* logmessege)
  74. {
  75. switch (printmethod)
  76. {
  77. case Screen:
  78. printscreen(logmessege);//打印屏幕
  79. break;
  80. case Onefile:
  81. printonefile(logfile,logmessege);//写入文件
  82. break;
  83. case Classfile:
  84. //实际上就是改了文件名然后复用printonefile
  85. printclassfile(point,logmessege);//文件归类
  86. break;
  87. default:
  88. break;
  89. }
  90. }
  91. void logprint(int point, const char *format, ...)
  92. {
  93. //日志左边字符串
  94. char leffbuff[SIZE];
  95. time_t t = time(nullptr);
  96. struct tm *pt = localtime(&t);
  97. snprintf(leffbuff, SIZE, "[%s][%d-%d-%d-%d-%d-%d]",option(point),
  98. pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour,
  99. pt->tm_min, pt->tm_sec);
  100. //日志右边字符串
  101. char rightbuff[SIZE];
  102. va_list s;
  103. va_start(s,format);
  104. vsnprintf(rightbuff,SIZE,format,s);
  105. //合并成为最终日志信息
  106. char logmessege[SIZE*2];
  107. snprintf(logmessege,SIZE*2,"%s%s\n",leffbuff,rightbuff);
  108. //printf("%s\n",logmessege);
  109. printflow(point, logmessege);//调用具体输出方法
  110. }
  111. ~log()
  112. {
  113. }
  114. private:
  115. string path;//即路径目录
  116. int printmethod;//输出方式:1、显示器 2、文件 3、文件归类
  117. };
  118. int main()
  119. {
  120. log l;
  121. int a = 10;
  122. int b = 12;
  123. l.logprint(Debug,"a=%d,b=%d",a,b);
  124. return 0;
  125. }

        运行结果:

        从结果可以看到,方式2是将格式化的内容写进文件里,但是在此之前要先在当前目录下创建一个log目录,因为成员变量path初始化的内容是"./log/",表示日志文件都存放在这个路径下。 

结语 

         以上就是关于Linux下实现日志系统的讲解,实现日志系统的核心点在于了解可变参数的使用,以及对字符串的操作运用。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/810866
推荐阅读
相关标签
  

闽ICP备14008679号