从零构建通讯器--4.3日志打印实战,捋下main函数的调用顺序
发布日期:2021-05-04 18:23:25 浏览次数:14 分类:技术文章

本文共 27950 字,大约阅读时间需要 93 分钟。

一:基础设施之日志打印实战代码一

(1)新文件:ngx_printf.cxx以及ngx_log.cxx
ngx_printf.cxx:放和打印格式相关的函数;
ngx_log.cxx:放和日志相关的函数;
函数:
(2)(//ngx_log_stderr() :三个特殊文件描述符【三章七节】,谈到了标准错误 STDERR_FILENO,代表屏幕)服务器一般都是守护进程,守护进程输入都是定位到黑洞区,不往屏幕上输出信息,防止干扰到用户,日志直接输入到文件
ngx_log_stderr():往屏幕上打印一条错误信息;功能类似于printf,都是可变参数
(3)printf的例子说明
printf(“mystring=%s,myint=%d,%d”,“mytest”,15,20);
①根据可变的参数,组合出一个字符串:mystring=mytest,myint=15,20
②往屏幕上显示出这个组合出来的字符串;
(4)ngx_log_stderr()说明和使用的理由:
①提高大家编码能力和理解能力;
②ngx_log_stderr():可以支持任意我想支持的格式化字符 %d, %f,对于扩展原有功能非常有帮助
实现效果如下:
在这里插入图片描述
在这里插入图片描述
③p = ngx_vslprintf(p,last,fmt,args); //实现了自我可定制的printf类似的功能
真正干正事的是ngx_vslprintf(最重要
函数定义:
u_char *ngx_vslprintf(u_char *buf, u_char *last,const char *fmt,va_list args)
ngx_log_stderr(0, “invalid option: “%s”,%d”, “testinfo”,123);
fmt = “invalid option: “%s”,%d”
args = “testinfo”,123
④buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
效果:
在这里插入图片描述在这里插入图片描述
⑤错误代码和错误信息也要显示出来
p = ngx_log_errno(p, last, err);

二:设置时区

我们要设置成CST时区,以保证日期,时间显示的都正确
a)PST【PST美国太平洋标准时间】 = GMT - 8;
b)GMT【格林尼治平均时间Greenwich Mean Time】等同于英国伦敦本地时间
c)UTC【通用协调时Universal Time Coordinated】 = GMT
d)CST【北京时间:北京时区是东八区,领先UTC八个小时】
查看时区代码: date
在这里插入图片描述
修改代码:

tzselect //短暂修改sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime     //永久修改

三:基础设施之日志打印实战代码二

(3.1)日志等级划分
划分日志等级,一共分8级,分级的目的是方便管理,显示,过滤等等;
日志级别从高到低,数字最小的级别最高,数字最大的级别最低;
在这里插入图片描述
①让日志只显示严重级别及以上的信息
②提前创建logs目录,日志存放在logs/error.log
③配置文件中的 #只打印日志等级<= 数字 的日志到日志文件中 ,日志等级0-8,0级别最高,8级别最低。
LogLevel = 3 #只打印3和3以上的
(3.2)配置文件中和日志有关的选项
继续介绍void ngx_log_init();打开/创建日志文件
介绍ngx_log_error_core()函数:写日志文件的核心函数,把字符串写入到日志中去
①gettimeofday获得当前秒数
②localtime_r获得当前时间,_r表示线程安全
tm.tm_mon++; //月份要调整下正常
tm.tm_year += 1900; //年份要调整下才正常
③ngx_slprintf这个函数接受的是args
ngx_vslprintf这个函数接受的是可变参
还有定义级别和打印日志的pid

四:捋顺main函数中代码执行顺序

(1)无伤大雅的也不需要释放的放最上面
如获取进程pid
保存参数指针等
(2)初始化失败就直接退出的
(3)一些初始化函数
如初始化日志
(4)一些不好归类的其他类别的代码
如把环境变量搬家
(5)一些系统资源的释放,写成单独一个函数
如void freeresource()
(6)退出函数可以写成goto lblexiit;
lblexit:
freeresouce();
printf(老子要退出了)
return exitcode;


代码部分

1.nginx.cxx
2.ngx_log.cxx
3.ngx_printf.cxx
4.ngx_func.h
5.ngx_global.h
6.ngx_macro.h

1.nginx.cxx

//整个程序入口函数放这里#include 
#include
#include
#include
#include "ngx_c_conf.h" //和配置文件处理相关的类,名字带c_表示和类有关#include "ngx_signal.h"#include "ngx_func.h" //各种函数声明//本文件用的函数声明static void freeresource();//和设置标题有关的全局量char **g_os_argv; //原始命令行参数数组,在main中会被赋值char *gp_envmem = NULL; //指向自己分配的env环境变量的内存,在ngx_init_setproctitle()函数中会被分配内存int g_environlen = 0; //环境变量所占内存大小//和进程本身有关的全局量pid_t ngx_pid; //当前进程的pidint main(int argc, char *const *argv){ int exitcode = 0; //退出代码,先给0表示正常退出 //(1)无伤大雅也不需要释放的放最上边 ngx_pid = getpid(); //取得进程pid g_os_argv = (char **) argv; //保存参数指针 //(2)初始化失败,就要直接退出的 //配置文件必须最先要,后边初始化啥的都用,所以先把配置读出来,供后续使用 CConfig *p_config = CConfig::GetInstance(); //单例类 if(p_config->Load("nginx.conf") == false) //把配置文件内容载入到内存,若载入失败,载入配置文件 { ngx_log_stderr(0,"配置文件[%s]载入失败,退出!","nginx.conf"); //exit(1);终止进程,在main中出现和return效果一样 ,exit(0)表示程序正常, exit(1)/exit(-1)表示程序异常退出,exit(2)表示表示系统找不到指定的文件 exitcode = 2; //标记找不到文件 goto lblexit; } //(3)一些初始化函数,准备放这里 ngx_log_init(); //日志初始化(创建/打开日志文件) //(4)一些不好归类的其他类别的代码,准备放这里 ngx_init_setproctitle(); //把环境变量搬家 //-------------------------------------------------------------- for(;;) //for(int i = 0; i < 10;++i) { sleep(1); //休息1秒 printf("休息1秒\n"); } //--------------------------------------lblexit: //(5)该释放的资源要释放掉 freeresource(); //一系列的main返回前的释放动作函数 printf("程序退出,再见!\n"); return exitcode;}//专门在程序执行末尾释放资源的函数【一系列的main返回前的释放动作函数】void freeresource(){ //(1)对于因为设置可执行程序标题导致的环境变量分配的内存,我们应该释放 if(gp_envmem) { delete []gp_envmem; gp_envmem = NULL; } //(2)关闭日志文件 if(ngx_log.fd != STDERR_FILENO && ngx_log.fd != -1) { close(ngx_log.fd); //不用判断结果了 ngx_log.fd = -1; //标记下,防止被再次close吧 }}

2.ngx_log.cxx

#include 
#include
#include
#include
//uintptr_t#include
//va_start....#include
//STDERR_FILENO等#include
//gettimeofday#include
//localtime_r#include
//open#include
//errno#include "ngx_global.h"#include "ngx_macro.h"#include "ngx_func.h"#include "ngx_c_conf.h"//全局量---------------------//错误等级,和ngx_macro.h里定义的日志等级宏是一一对应关系static u_char err_levels[][20] = { { "stderr"}, //0:控制台错误 { "emerg"}, //1:紧急 { "alert"}, //2:警戒 { "crit"}, //3:严重 { "error"}, //4:错误 { "warn"}, //5:警告 { "notice"}, //6:注意 { "info"}, //7:信息 { "debug"} //8:调试};ngx_log_t ngx_log;//----------------------------------------------------------------------------------------------------------------------//描述:通过可变参数组合出字符串【支持...省略号形参】,自动往字符串最末尾增加换行符【所以调用者不用加\n】, 往标准错误上输出这个字符串;// 如果err不为0,表示有错误,会将该错误编号以及对应的错误信息一并放到组合出的字符串中一起显示;//《c++从入门到精通》里老师讲解过,比较典型的C语言中的写法,就是这种va_start,va_end//fmt:通过这第一个普通参数来寻址后续的所有可变参数的类型及其值//调用格式比如:ngx_log_stderr(0, "invalid option: \"%s\",%d", "testinfo",123); /* ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]); //nginx: invalid option: "./nginx" ngx_log_stderr(0, "invalid option: %10d", 21); //nginx: invalid option: 21 ---21前面有8个空格 ngx_log_stderr(0, "invalid option: %.6f", 21.378); //nginx: invalid option: 21.378000 ---%.这种只跟f配合有效,往末尾填充0 ngx_log_stderr(0, "invalid option: %.6f", 12.999); //nginx: invalid option: 12.999000 ngx_log_stderr(0, "invalid option: %.2f", 12.999); //nginx: invalid option: 13.00 ngx_log_stderr(0, "invalid option: %xd", 1678); //nginx: invalid option: 68E ngx_log_stderr(0, "invalid option: %Xd", 1678); //nginx: invalid option: 68E ngx_log_stderr(15, "invalid option: %s , %d", "testInfo",326); //nginx: invalid option: testInfo , 326 ngx_log_stderr(0, "invalid option: %d", 1678); */void ngx_log_stderr(int err, const char *fmt, ...){ va_list args; //创建一个va_list类型变量 u_char errstr[NGX_MAX_ERROR_STR+1]; //2048 -- ************ +1是我自己填的,感谢官方写法有点小瑕疵,所以动手调整一下 u_char *p,*last;//last指向errstr最后的一个元素 memset(errstr,0,sizeof(errstr)); //我个人加的,这块有必要加,至少在va_end处理之前有必要,否则字符串没有结束标记不行的;*************************** last = errstr + NGX_MAX_ERROR_STR; //last指向整个buffer最后去了【指向最后一个有效位置的后面也就是非有效位】,作为一个标记,防止输出内容超过这么长, //其实我认为这有问题,所以我才在上边errstr[NGX_MAX_ERROR_STR+1]; 给加了1 //比如你定义 char tmp[2]; 你如果last = tmp+2,那么last实际指向了tmp[2],而tmp[2]在使用中是无效的 //p指向当前可以写的位置,刚开始,p指向数组头 p = ngx_cpymem(errstr, "nginx: ", 7); //p指向"nginx: "之后,打印就从nginx: 后开始 //通过fmt和...定位,使args指向起始的参数 va_start(args, fmt); //使args指向起始的参数 p = ngx_vslprintf(p,last,fmt,args); //组合出这个字符串保存在errstr里 va_end(args); //释放args if (err) //如果错误代码不是0,表示有错误发生 { //错误代码和错误信息也要显示出来 p = ngx_log_errno(p, last, err); } //若位置不够,那换行也要硬插入到末尾,哪怕覆盖到其他内容 if (p >= (last - 1)) { p = (last - 1) - 1; //把尾部空格留出来,这里感觉nginx处理的似乎就不对 //我觉得,last-1,才是最后 一个而有效的内存,而这个位置要保存\0,所以我认为再减1,这个位置,才适合保存\n } *p++ = '\n'; //增加个换行符 //往标准错误【一般是屏幕】输出信息 write(STDERR_FILENO,errstr,p - errstr); //三章七节讲过,这个叫标准错误,一般指屏幕 //测试代码: //printf("ngx_log_stderr()处理结果=%s\n",errstr); //printf("ngx_log_stderr()处理结果=%s",errstr); return;}//----------------------------------------------------------------------------------------------------------------------//描述:给一段内存,一个错误编号,我要组合出一个字符串,形如: (错误编号: 错误原因),放到给的这段内存中去// 这个函数我改造的比较多,和原始的nginx代码多有不同//buf:是个内存,要往这里保存数据//last:放的数据不要超过这里//err:错误编号,我们是要取得这个错误编号对应的错误字符串,保存到buffer中u_char *ngx_log_errno(u_char *buf, u_char *last, int err){ //以下代码是我自己改造,感觉作者的代码有些瑕疵 char *perrorinfo = strerror(err); //根据资料不会返回NULL; size_t len = strlen(perrorinfo); //然后我还要插入一些字符串: (%d:) char leftstr[10] = { 0}; sprintf(leftstr," (%d: ",err); size_t leftlen = strlen(leftstr); char rightstr[] = ") "; size_t rightlen = strlen(rightstr); size_t extralen = leftlen + rightlen; //左右的额外宽度 if ((buf + len + extralen) < last) { //保证整个我装得下,我就装,否则我全部抛弃 ,nginx的做法是 如果位置不够,就硬留出50个位置【哪怕覆盖掉以往的有效内容】,也要硬往后边塞,这样当然也可以; buf = ngx_cpymem(buf, leftstr, leftlen); buf = ngx_cpymem(buf, perrorinfo, len); buf = ngx_cpymem(buf, rightstr, rightlen); } return buf;}//----------------------------------------------------------------------------------------------------------------------//往日志文件中写日志,代码中有自动加换行符,所以调用时字符串不用刻意加\n;// 日过定向为标准错误,则直接往屏幕上写日志【比如日志文件打不开,则会直接定位到标准错误,此时日志就打印到屏幕上,参考ngx_log_init()】//level:一个等级数字,我们把日志分成一些等级,以方便管理、显示、过滤等等,如果这个等级数字比配置文件中的等级数字"LogLevel"大,那么该条信息不被写到日志文件中//err:是个错误代码,如果不是0,就应该转换成显示对应的错误信息,一起写到日志文件中,//ngx_log_error_core(5,8,"这个XXX工作的有问题,显示的结果是=%s","YYYY");void ngx_log_error_core(int level, int err, const char *fmt, ...){ u_char *last; u_char errstr[NGX_MAX_ERROR_STR+1]; //这个+1也是我放入进来的,本函数可以参考ngx_log_stderr()函数的写法; memset(errstr,0,sizeof(errstr)); last = errstr + NGX_MAX_ERROR_STR; struct timeval tv; struct tm tm; time_t sec; //秒 u_char *p; //指向当前要拷贝数据到其中的内存位置 va_list args; memset(&tv,0,sizeof(struct timeval)); memset(&tm,0,sizeof(struct tm)); gettimeofday(&tv, NULL); //获取当前时间,返回自1970-01-01 00:00:00到现在经历的秒数【第二个参数是时区,一般不关心】 sec = tv.tv_sec; //秒 localtime_r(&sec, &tm); //把参数1的time_t转换为本地时间,保存到参数2中去,带_r的是线程安全的版本,尽量使用 tm.tm_mon++; //月份要调整下正常 tm.tm_year += 1900; //年份要调整下才正常 u_char strcurrtime[40]={ 0}; //先组合出一个当前时间字符串,格式形如:2019/01/08 19:57:11 ngx_slprintf(strcurrtime, (u_char *)-1, //若用一个u_char *接一个 (u_char *)-1,则 得到的结果是 0xffffffff....,这个值足够大 "%4d/%02d/%02d %02d:%02d:%02d", //格式是 年/月/日 时:分:秒 tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); p = ngx_cpymem(errstr,strcurrtime,strlen((const char *)strcurrtime)); //日期增加进来,得到形如: 2019/01/08 20:26:07 p = ngx_slprintf(p, last, " [%s] ", err_levels[level]); //日志级别增加进来,得到形如: 2019/01/08 20:26:07 [crit] p = ngx_slprintf(p, last, "%P: ",ngx_pid); //支持%P格式,进程id增加进来,得到形如: 2019/01/08 20:50:15 [crit] 2037: va_start(args, fmt); //使args指向起始的参数 p = ngx_vslprintf(p, last, fmt, args); //把fmt和args参数弄进去,组合出来这个字符串 va_end(args); //释放args if (err) //如果错误代码不是0,表示有错误发生 { //错误代码和错误信息也要显示出来 p = ngx_log_errno(p, last, err); } //若位置不够,那换行也要硬插入到末尾,哪怕覆盖到其他内容 if (p >= (last - 1)) { p = (last - 1) - 1; //把尾部空格留出来,这里感觉nginx处理的似乎就不对 //我觉得,last-1,才是最后 一个而有效的内存,而这个位置要保存\0,所以我认为再减1,这个位置,才适合保存\n } *p++ = '\n'; //增加个换行符 //这么写代码是图方便:随时可以把流程弄到while后边去;大家可以借鉴一下这种写法 ssize_t n; while(1) { if (level > ngx_log.log_level) { //要打印的这个日志的等级太落后(等级数字太大,比配置文件中的数字大) //这种日志就不打印了 break; } //磁盘是否满了的判断,先算了吧,还是由管理员保证这个事情吧; //写日志文件 n = write(ngx_log.fd,errstr,p - errstr); //文件写入成功后,如果中途 if (n == -1) { //写失败有问题 if(errno == ENOSPC) //写失败,且原因是磁盘没空间了 { //磁盘没空间了 //没空间还写个毛线啊 //先do nothing吧; } else { //这是有其他错误,那么我考虑把这个错误显示到标准错误设备吧; if(ngx_log.fd != STDERR_FILENO) //当前是定位到文件的,则条件成立 { n = write(STDERR_FILENO,errstr,p - errstr); } } } break; } //end while return;}//----------------------------------------------------------------------------------------------------------------------//描述:日志初始化,就是把日志文件打开 ,注意这里边涉及到释放的问题,如何解决?void ngx_log_init(){ u_char *plogname = NULL; size_t nlen; //从配置文件中读取和日志相关的配置信息 CConfig *p_config = CConfig::GetInstance(); plogname = (u_char *)p_config->GetString("Log"); if(plogname == NULL)//没读到 { //没读到,就要给个缺省的路径文件名了 plogname = (u_char *) NGX_ERROR_LOG_PATH; //"logs/error.log" ,logs目录需要提前建立出来 } ngx_log.log_level = p_config->GetIntDefault("LogLevel",NGX_LOG_NOTICE);//缺省日志等级为6【注意】 ,如果读失败,就给缺省日志等级 //nlen = strlen((const char *)plogname);//宽度 //只写打开|追加到末尾|文件不存在则创建【这个需要跟第三参数指定文件访问权限】 //mode = 0644:文件访问权限, 6: 110 , 4: 100: 【用户:读写, 用户所在组:读,其他:读】 老师在第三章第一节介绍过 ngx_log.fd = open((const char *)plogname,O_WRONLY|O_APPEND|O_CREAT,0644);//打开日志文件,只写,追加,同时创建 if (ngx_log.fd == -1) //如果有错误,则直接定位到 标准错误上去,也就是直接打印到屏幕 { ngx_log_stderr(errno,"[alert] could not open error log file: open() \"%s\" failed", plogname); ngx_log.fd = STDERR_FILENO; //直接定位到标准错误去了,标准错误输入到fd } return;}

3.ngx_printf.cxx

//和打印格式相关的函数放这里#include 
#include
#include
#include
#include
//类型相关头文件#include "ngx_global.h"#include "ngx_macro.h"#include "ngx_func.h"//只用于本文件的一些函数声明就放在本文件中static u_char *ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64,u_char zero, uintptr_t hexadecimal, uintptr_t width);//----------------------------------------------------------------------------------------------------------------------//对于 nginx 自定义的数据结构进行标准格式化输出,就像 printf,vprintf 一样,我们顺道学习写这类函数到底内部是怎么实现的//该函数只不过相当于针对ngx_vslprintf()函数包装了一下,所以,直接研究ngx_vslprintf()即可u_char *ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...) { va_list args; u_char *p; va_start(args, fmt); //使args指向起始的参数 p = ngx_vslprintf(buf, last, fmt, args); va_end(args); //释放args return p;}//----------------------------------------------------------------------------------------------------------------------//对于 nginx 自定义的数据结构进行标准格式化输出,就像 printf,vprintf 一样,我们顺道学习写这类函数到底内部是怎么实现的//例如,给进来一个 "abc = %d",13 ,最终buf里得到的应该是 abc=13 这种结果//buf:往这里放数据//last:放的数据不要超过这里//fmt:以这个为首的一系列可变参数//支持的格式: %d【%Xd/%xd】:数字, %s:字符串 %f:浮点, %P:pid_t //对于:ngx_log_stderr(0, "invalid option: \"%s\",%d", "testinfo",123); //fmt = "invalid option: \"%s\",%d" //args = "testinfo",123u_char *ngx_vslprintf(u_char *buf, u_char *last,const char *fmt,va_list args){ //比如说你要调用ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);,那么这里的fmt就应该是: invalid option: "%s" //printf("fmt = %s\n",fmt); u_char zero; /* #ifdef _WIN64 typedef unsigned __int64 uintptr_t; #else typedef unsigned int uintptr_t; #endif */ uintptr_t width,sign,hex,frac_width,scale,n; //临时用到的一些变量 int64_t i64; //保存%d对应的可变参 uint64_t ui64; //保存%ud对应的可变参,临时作为%f可变参的整数部分也是可以的 u_char *p; //保存%s对应的可变参 double f; //保存%f对应的可变参 uint64_t frac; //%f可变参数,根据%.2f等,取得小数部分的2位后的内容; //只要写入不溢出,就不断循环往里面写 while (*fmt && buf < last) //每次处理一个字符,处理的是 "invalid option: \"%s\",%d" 中的字符 { if (*fmt == '%') //%开头的一般都是需要被可变参数 取代的 { //-----------------变量初始化工作开始----------------- //++fmt是先加后用,也就是fmt先往后走一个字节位置,然后再判断该位置的内容 zero = (u_char) ((*++fmt == '0') ? '0' : ' '); //判断%后边接的是否是个'0',如果是zero = '0',否则zero = ' ',一般比如你想显示10位,而实际数字7位,前头填充三个字符,就是这里的zero用于填充 //ngx_log_stderr(0, "数字是%010d", 12); width = 0; //格式字符% 后边如果是个数字,这个数字最终会弄到width里边来 ,这东西目前只对数字格式有效,比如%d,%f这种 sign = 1; //显示的是否是有符号数,这里给1,表示是有符号数,除非你 用%u,这个u表示无符号数 hex = 0; //是否以16进制形式显示(比如显示一些地址),0:不是,1:是,并以小写字母显示a-f,2:是,并以大写字母显示A-F frac_width = 0; //小数点后位数字,一般需要和%.10f配合使用,这里10就是frac_width; i64 = 0; //一般用%d对应的可变参中的实际数字,会保存在这里 ui64 = 0; //一般用%ud对应的可变参中的实际数字,会保存在这里 //-----------------变量初始化工作结束----------------- //这个while就是判断%后边是否是个数字,如果是个数字,就把这个数字取出来,比如%16,最终这个循环就能够把16取出来弄到width里边去 //%16d 这里最终width = 16; while (*fmt >= '0' && *fmt <= '9') //如果%后边接的字符是 '0' --'9'之间的内容 ,比如 %16这种; { //第一次 :width = 1; 第二次 width = 16,所以整个width = 16; width = width * 10 + (*fmt++ - '0'); } //这个for循环啥也没干 for ( ;; ) //一些特殊的格式,我们做一些特殊的标记【给一些变量特殊值等等】 { switch (*fmt) //处理一些%之后的特殊字符 { case 'u': //%u,这个u表示无符号 sign = 0; //标记这是个无符号数 fmt++; //往后走一个字符 continue; //回到for继续判断 case 'X': //%X,大写X表示十六进制,并且十六进制中的A-F以大写字母显示,不要单独使用,一般是%Xd hex = 2; //标记以大写字母显示十六进制中的A-F sign = 0; fmt++; continue; case 'x': //%x,小写x表示十六进制,并且十六进制中的a-f以小写字母显示,不要单独使用,一般是%xd hex = 1; //标记以小写字母显示十六进制中的a-f sign = 0; fmt++; continue; case '.': //其后边必须跟个数字,必须与%f配合使用,形如 %.10f:表示转换浮点数时小数部分的位数,比如%.10f表示转换浮点数时,小数点后必须保证10位数字,不足10位则用0来填补; fmt++; //往后走一个字符,后边这个字符肯定是0-9之间,因为%.要求接个数字先 while(*fmt >= '0' && *fmt <= '9') //如果是数字,一直循环,这个循环最终就能把诸如%.10f中的10提取出来 { frac_width = frac_width * 10 + (*fmt++ - '0'); } //end while(*fmt >= '0' && *fmt <= '9') break; default: break; } //end switch (*fmt) break; } //end for ( ;; ) switch (*fmt) { case '%': //只有%%时才会遇到这个情形,本意是打印一个%,所以 *buf++ = '%';//把%塞到buf后面 fmt++; continue; case 'd': //显示整型数据,如果和u配合使用,也就是%ud,则是显示无符号整型数据 if (sign) //如果是有符号数 { i64 = (int64_t) va_arg(args, int); //va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变的参数的类型 } else //如何是和 %ud配合使用,则本条件就成立 { ui64 = (uint64_t) va_arg(args, u_int); } break; //这break掉,直接跳道switch后边的代码去执行,这种凡是break的,都不做fmt++; *********************【switch后仍旧需要进一步处理】 case 's': //一般用于显示字符串 p = va_arg(args, u_char *); //va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变的参数的类型 while (*p && buf < last) //没遇到字符串结束标记,并且buf值够装得下这个参数 { *buf++ = *p++; //那就装,比如 "%s" , "abcdefg",那abcdefg都被装进来 } fmt++; continue; //重新从while开始执行 case 'P': //转换一个pid_t类型 i64 = (int64_t) va_arg(args, pid_t); sign = 1; break; case 'f': //一般 用于显示double类型数据,如果要显示小数部分,则要形如 %.5f f = va_arg(args, double); //va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变的参数的类型 if (f < 0) //负数的处理 { *buf++ = '-'; //单独搞个负号出来 f = -f; //那这里f应该是正数了! } //走到这里保证f肯定 >= 0【不为负数】 ui64 = (int64_t) f; //正整数部分给到ui64里 frac = 0; //如果要求小数点后显示多少位小数 if (frac_width) //如果是%d.2f,那么frac_width就会是这里的2 { scale = 1; //缩放从1开始 for (n = frac_width; n; n--) { scale *= 10; //这可能溢出哦 } //把小数部分取出来 ,比如如果是格式 %.2f ,对应的参数是12.537 // (uint64_t) ((12.537 - (double) 12) * 100 + 0.5); //= (uint64_t) (0.537 * 100 + 0.5) = (uint64_t) (53.7 + 0.5) = (uint64_t) (54.2) = 54 frac = (uint64_t) ((f - (double) ui64) * scale + 0.5); //取得保留的那些小数位数,【比如 %.2f ,对应的参数是12.537,取得的就是小数点后的2位四舍五入,也就是54】 //如果是"%.6f", 21.378,那么这里frac = 378000 if (frac == scale) //进位,比如 %.2f ,对应的参数是12.999,那么 = (uint64_t) (0.999 * 100 + 0.5) = (uint64_t) (99.9 + 0.5) = (uint64_t) (100.4) = 100 //而此时scale == 100,两者正好相等 { ui64++; //正整数部分进位 frac = 0; //小数部分归0 } } //end if (frac_width) //正整数部分,先显示出来 buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width); //把一个数字 比如“1234567”弄到buffer中显示 if (frac_width) //指定了显示多少位小数 { if (buf < last) { *buf++ = '.'; //因为指定显示多少位小数,先把小数点增加进来 } buf = ngx_sprintf_num(buf, last, frac, '0', 0, frac_width); //frac这里是小数部分,显示出来,不够的,前边填充'0'字符 } fmt++; continue; //重新从while开始执行 //.................................. //................其他格式符,逐步完善 //.................................. default: *buf++ = *fmt++; //往下移动一个字符 continue; //注意这里不break,而是continue;而这个continue其实是continue到外层的while去了,也就是流程重新从while开头开始执行; } //end switch (*fmt) //显示%d的,会走下来,其他走下来的格式日后逐步完善...... //统一把显示的数字都保存到 ui64 里去; if (sign) //显示的是有符号数 { if (i64 < 0) //这可能是和%d格式对应的要显示的数字 { *buf++ = '-'; //小于0,自然要把负号先显示出来 ui64 = (uint64_t) -i64; //变成无符号数(正数) } else //显示正数 { ui64 = (uint64_t) i64; } } //end if (sign) //把一个数字 比如“1234567”弄到buffer中显示,如果是要求10位,则前边会填充3个空格比如“ 1234567” //注意第5个参数hex,是否以16进制显示,比如如果你是想以16进制显示一个数字则可以%Xd或者%xd,此时hex = 2或者1 buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width); fmt++; } else //当成正常字符,源【fmt】拷贝到目标【buf】里 { //用fmt当前指向的字符赋给buf当前指向的位置,然后buf往前走一个字符位置,fmt当前走一个字符位置 *buf++ = *fmt++; //*和++优先级相同,结合性从右到左,所以先求的是buf++以及fmt++,但++是先用后加; } //end if (*fmt == '%') } //end while (*fmt && buf < last) return buf;}//----------------------------------------------------------------------------------------------------------------------//以一个指定的宽度把一个数字显示在buf对应的内存中, 如果实际显示的数字位数 比指定的宽度要小 ,比如指定显示10位,而你实际要显示的只有“1234567”,那结果可能是会显示“ 1234567” //当然如果你不指定宽度【参数width=0】,则按实际宽度显示 //你给进来一个%Xd之类的,还能以十六进制数字格式显示出来//buf:往这里放数据//last:放的数据不要超过这里//ui64:显示的数字 //zero:显示内容时,格式字符%后边接的是否是个'0',如果是zero = '0',否则zero = ' ' 【一般显示的数字位数不足要求的,则用这个字符填充】,比如要显示10位,而实际只有7位,则后边填充3个这个字符;//hexadecimal:是否显示成十六进制数字 0:不//width:显示内容时,格式化字符%后接的如果是个数字比如%16,那么width=16,所以这个是希望显示的宽度值【如果实际显示的内容不够,则后头用0填充】static u_char * ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64, u_char zero, uintptr_t hexadecimal, uintptr_t width){ //temp[21] u_char *p, temp[NGX_INT64_LEN + 1]; //#define NGX_INT64_LEN (sizeof("-9223372036854775808") - 1) = 20 ,注意这里是sizeof是包括末尾的\0,不是strlen; size_t len; uint32_t ui32; static u_char hex[] = "0123456789abcdef"; //跟把一个10进制数显示成16进制有关,换句话说和 %xd格式符有关,显示的16进制数中a-f小写 static u_char HEX[] = "0123456789ABCDEF"; //跟把一个10进制数显示成16进制有关,换句话说和 %Xd格式符有关,显示的16进制数中A-F大写 p = temp + NGX_INT64_LEN; //NGX_INT64_LEN = 20,所以 p指向的是temp[20]那个位置,也就是数组最后一个元素位置 if (hexadecimal == 0) { if (ui64 <= (uint64_t) NGX_MAX_UINT32_VALUE) //NGX_MAX_UINT32_VALUE :最大的32位无符号数:十进制是‭4294967295‬ { ui32 = (uint32_t) ui64; //能保存下 do //这个循环能够把诸如 7654321这个数字保存成:temp[13]=7,temp[14]=6,temp[15]=5,temp[16]=4,temp[17]=3,temp[18]=2,temp[19]=1 //而且的包括temp[0..12]以及temp[20]都是不确定的值 { *--p = (u_char) (ui32 % 10 + '0'); //把屁股后边这个数字拿出来往数组里装,并且是倒着装:屁股后的也往数组下标大的位置装; } while (ui32 /= 10); //每次缩小10倍等于去掉屁股后边这个数字 } else { do { *--p = (u_char) (ui64 % 10 + '0'); } while (ui64 /= 10); //每次缩小10倍等于去掉屁股后边这个数字 } } else if (hexadecimal == 1) //如果显示一个十六进制数字,格式符为:%xd,则这个条件成立,要以16进制数字形式显示出来这个十进制数,a-f小写 { //比如我显示一个1,234,567【十进制数】,他对应的二进制数实际是 12 D687 ,那怎么显示出这个12D687来呢? do { //0xf就是二进制的1111,大家都学习过位运算,ui64 & 0xf,就等于把 一个数的最末尾的4个二进制位拿出来; //ui64 & 0xf 其实就能分别得到 这个16进制数也就是 7,8,6,D,2,1这个数字,转成 (uint32_t) ,然后以这个为hex的下标,找到这几个数字的对应的能够显示的字符; *--p = hex[(uint32_t) (ui64 & 0xf)]; } while (ui64 >>= 4); //ui64 >>= 4 ---> ui64 = ui64 >> 4 ,而ui64 >> 4是啥,实际上就是右移4位,就是除以16,因为右移4位就等于移动了1111; //相当于把该16进制数的最末尾一位干掉,原来是 12 D687, >> 4后是 12 D68,如此反复,最终肯定有=0时导致while不成立退出循环 //比如 1234567 / 16 = 77160(0x12D68) // 77160 / 16 = 4822(0x12D6) } else // hexadecimal == 2 //如果显示一个十六进制数字,格式符为:%Xd,则这个条件成立,要以16进制数字形式显示出来这个十进制数,A-F大写 { //参考else if (hexadecimal == 1),非常类似 do { *--p = HEX[(uint32_t) (ui64 & 0xf)]; } while (ui64 >>= 4); } len = (temp + NGX_INT64_LEN) - p; //得到这个数字的宽度,比如 “7654321”这个数字 ,len = 7 while (len++ < width && buf < last) //如果你希望显示的宽度是10个宽度【%12f】,而实际想显示的是7654321,只有7个宽度,那么这里要填充5个0进去到末尾,凑够要求的宽度 { *buf++ = zero; //填充0进去到buffer中(往末尾增加),比如你用格式 //ngx_log_stderr(0, "invalid option: %10d\n", 21); //显示的结果是:nginx: invalid option: 21 ---21前面有8个空格,这8个弄个,就是在这里添加进去的; } len = (temp + NGX_INT64_LEN) - p; //还原这个len,也就是要显示的数字的实际宽度【因为上边这个while循环改变了len的值】 //现在还没把实际的数字比如“7654321”往buf里拷贝呢,要准备拷贝 //如下这个等号是我加的【我认为应该加等号】,nginx源码里并没有加;*********************************************** if((buf + len) >= last) //发现如果往buf里拷贝“7654321”后,会导致buf不够长【剩余的空间不够拷贝整个数字】 { len = last - buf; //剩余的buf有多少我就拷贝多少 } return ngx_cpymem(buf, p, len); //把最新buf返回去;}

4.ngx_func.h

//函数声明放在这个头文件里-------------------------------------------#ifndef __NGX_FUNC_H__#define __NGX_FUNC_H__//字符串相关函数void   Rtrim(char *string);void   Ltrim(char *string);//设置可执行程序标题相关函数void   ngx_init_setproctitle();void   ngx_setproctitle(const char *title);//和日志,打印输出有关void   ngx_log_init();void   ngx_log_stderr(int err, const char *fmt, ...);void   ngx_log_error_core(int level,  int err, const char *fmt, ...);u_char *ngx_log_errno(u_char *buf, u_char *last, int err);u_char *ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...);u_char *ngx_vslprintf(u_char *buf, u_char *last,const char *fmt,va_list args);#endif

5.ngx_global.h

#ifndef __NGX_GBLDEF_H__#define __NGX_GBLDEF_H__//一些比较通用的定义放在这里,比如typedef定义//一些全局变量的外部声明也放在这里//类型定义----------------//结构定义typedef struct{   	char ItemName[50];	char ItemContent[500];}CConfItem,*LPCConfItem;//和运行日志相关 typedef struct{   	int    log_level;   //日志级别 或者日志类型,ngx_macro.h里分0-8共9个级别	int    fd;          //日志文件描述符}ngx_log_t;//外部全局量声明extern char  **g_os_argv;extern char  *gp_envmem; extern int   g_environlen; extern pid_t       ngx_pid;extern ngx_log_t   ngx_log;#endif

6.ngx_macro.h

#ifndef __NGX_MACRO_H__#define __NGX_MACRO_H__//各种#define宏定义相关的定义放这里#define NGX_MAX_ERROR_STR   2048   //显示的错误信息最大数组长度//简单功能函数--------------------//类似memcpy,但常规memcpy返回的是指向目标dst的指针,而这个ngx_cpymem返回的是目标【拷贝数据后】的终点位置,连续复制多段数据时方便//+(n)表示p始终指向可写的字符串的末尾,也是在nginx: 之后#define ngx_cpymem(dst, src, n)   (((u_char *) memcpy(dst, src, n)) + (n))  //注意#define写法,n这里用()包着,防止出现什么错误#define ngx_min(val1, val2)  ((val1 > val2) ? (val2) : (val1))              //比较大小,返回小值,注意,参数都用()包着//数字相关--------------------#define NGX_MAX_UINT32_VALUE   (uint32_t) 0xffffffff              //最大的32位无符号数:十进制是‭4294967295‬#define NGX_INT64_LEN          (sizeof("-9223372036854775808") - 1)     //日志相关--------------------//我们把日志一共分成八个等级【级别从高到低,数字最小的级别最高,数字大的级别最低】,以方便管理、显示、过滤等等#define NGX_LOG_STDERR            0    //控制台错误【stderr】:最高级别日志,日志的内容不再写入log参数指定的文件,而是会直接将日志输出到标准错误设备比如控制台屏幕#define NGX_LOG_EMERG             1    //紧急 【emerg】#define NGX_LOG_ALERT             2    //警戒 【alert】#define NGX_LOG_CRIT              3    //严重 【crit】#define NGX_LOG_ERR               4    //错误 【error】:属于常用级别#define NGX_LOG_WARN              5    //警告 【warn】:属于常用级别#define NGX_LOG_NOTICE            6    //注意 【notice】#define NGX_LOG_INFO              7    //信息 【info】#define NGX_LOG_DEBUG             8    //调试 【debug】:最低级别#define NGX_ERROR_LOG_PATH       "logs/error1.log"   //定义日志存放的路径和文件名 #endif

7.nginx.conf

#是注释行,#每个有效配置项用 等号 处理,等号前不超过40个字符,等号后不超过400个字符; #[开头的表示组信息,也等价于注释行[Socket]ListenPort = 5678    DBInfo = 127.0.0.1;1234;myr;123456;mxdb_g#日志相关[Log]#日志文件输出目录和文件名#Log=logs/error.log Log=error.log#只打印日志等级<= 数字 的日志到日志文件中 ,日志等级0-8,0级别最高,8级别最低。LogLevel = 8
上一篇:从零构建通讯器--4.4-4.5信号在创建线程的实战作用、write函数写入日志设置成不混乱、文件IO详解
下一篇:从零构建通讯器--4.2读取配置文件和检查内存泄漏、设置可执行程序的标题(名称)

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2025年04月02日 16时15分54秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

2021年电工(初级)考试及电工(初级)报名考试 2019-03-03
2021年R2移动式压力容器充装考试题及R2移动式压力容器充装找答案 2019-03-03
2021年高处安装、维护、拆除考试资料及高处安装、维护、拆除证考试 2019-03-03
2021年电工(初级)考试及电工(初级)证考试 2019-03-03
2021年安全员-B证-项目负责人(广东省)新版试题及安全员-B证-项目负责人(广东省)考试试卷 2019-03-03
2021年R2移动式压力容器充装考试总结及R2移动式压力容器充装模拟考试 2019-03-03
2021年安全员-B证(山东省)考试APP及安全员-B证(山东省)考试技巧 2019-03-03
2021年安全员-A证-主要负责人(广东省)复审考试及安全员-A证-主要负责人(广东省)操作证考试 2019-03-03
2021年安全员-A证(山东省)考试题及安全员-A证(山东省)报名考试 2019-03-03
2021年G1工业锅炉司炉考试报名及G1工业锅炉司炉模拟考试题库 2019-03-03
2021年安全员-B证(山东省)考试内容及安全员-B证(山东省)模拟考试题 2019-03-03
从xx离职随笔 2019-03-03
大数据学习之Spark——00Spark项目的pom.xml文件 2019-03-03
大数据学习之Spark——01Spark概述 2019-03-03
LeetCode0234. 回文链表 2019-03-03
比特币史话·78 | 有容乃大(2): 零食售卖机 2019-03-03
比特币史话·96 | 隐私(3): 熔币重铸 2019-03-03
Fire prejudice: 巴菲特搭档芒格首度认可比特币 2019-03-03
从 MFC 移植程序到 wxWidgets 界面库 ——《定时执行专家 5.0》的界面实现 2019-03-03
GLUT和wxWidgets在OpenGL开发中的比较 2019-03-03