日志打印相关代码

1. printf函数的实现原理

1. int printf(const char *format, ...);
         format是固定参数,是参数1        
… 是可变参数
2. 相关宏

1)va_list 就是一个char*指针, 即:typedef char * va_list; (2)va_start宏 ,#define va_start(list,param1) (list = (va_list)&param1+ sizeof(param1)) 获取可变参数列表的第一个参数的地址(list是类型为va_list的指针,param1是固定参数)
3)va_arg宏,#define va_arg(list,mode) ((mode *) (list += sizeof(mode)))[-1] 获取可变参数的当前参数的值,返回指定类型并将指针指向下一参数(mode参数描述了当前参数的类型)
(
4)va_end宏 va_end(list) ( list = (va_list)0 )

清空va_list可变参数列表,防止出现野指针

     

             说白了 就是通过固定参数的地址,根据可变参数的大小,来寻找可变参数的地址

printf实现原理基本如下:(ngx_log_stderr、ngx_log_error_core函数就是这样实现的
  1. 定义一个数组str(最后用于write输出)
  2. 遍历固定参数所指向的字符串 (vsprintf函数中来实现的)
      遇到普通文本就直接拷贝到数组str中;
      遇到%d这些,就利用那几个宏去可变参数中找出来然后转换成文本形式拷贝到str中。
  3. 利用write将str数组输出即可。(如果想添加额外的信息,就自己在str数组中添加即可)
printf源码(参考Linux1.0内核源码)
 3 static int printf(const char *fmt, ...)
 4 {
 5   va_list args;
 6   int i;
 7   va_start(args, fmt);
 8   write(1,printbuf,i=vsprintf(printbuf, fmt, args));   //vsprintf源码此处就省略,感兴趣的自己去网上搜搜
9 va_end(args);
10 return i;
11 }

2. ngx_log_stderr函数

           往屏幕中进行错误输出,使用方式如下:void ngx_log_stderr(int err, const char *fmt, ...)

    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);

3. ngx_log_error_core函数

       类似这样的输出,前面增加了很多提示信息,

               2019/03/12 22:54:18 [stderr] 4112: nginx: 当前收消息队列/发消息队列大小分别为(0/0),丢弃的待发送数据包数量为0。

4. 源码实现

ngx_log.h

 1 #ifndef NGX_LOG_H
 2 #define NGX_LOG_H
 3 #include<stddef.h>
 4 #include <stdarg.h>
 5 
 6 #define NGX_MAX_ERROR_STR   2048   //显示的错误信息最大数组长度
 7 //简单功能函数--------------------
 8 //类似memcpy,但常规memcpy返回的是指向目标dst的指针,而这个ngx_cpymem返回的是目标【拷贝数据后】的终点位置,连续复制多段数据时方便
 9 #define ngx_cpymem(dst, src, n)   (((u_char *) memcpy(dst, src, n)) + (n))  //注意#define写法,n这里用()包着,防止出现什么错误
10 #define ngx_min(val1, val2)  ((val1 > val2) ? (val2) : (val1))              //比较大小,返回小值,注意,参数都用()包着
11 
12 //数字相关--------------------
13 #define NGX_MAX_UINT32_VALUE   (uint32_t) 0xffffffff              //最大的32位无符号数:十进制是‭4294967295‬
14 #define NGX_INT64_LEN          (sizeof("-9223372036854775808") - 1)
15 
16 //日志相关--------------------
17 //我们把日志一共分成八个等级【级别从高到低,数字最小的级别最高,数字大的级别最低】,以方便管理、显示、过滤等等
18 #define NGX_LOG_STDERR            0    //控制台错误【stderr】:最高级别日志,日志的内容写入log参数指定的文件,同时也尝试直接将日志输出到标准错误设备比如控制台屏幕
19 #define NGX_LOG_EMERG             1    //紧急 【emerg】
20 #define NGX_LOG_ALERT             2    //警戒 【alert】
21 #define NGX_LOG_CRIT              3    //严重 【crit】
22 #define NGX_LOG_ERR               4    //错误 【error】:属于常用级别
23 #define NGX_LOG_WARN              5    //警告 【warn】:属于常用级别
24 #define NGX_LOG_NOTICE            6    //注意 【notice】
25 #define NGX_LOG_INFO              7    //信息 【info】
26 #define NGX_LOG_DEBUG             8    //调试 【debug】:最低级别
27 
28 //#define NGX_ERROR_LOG_PATH       "logs/error1.log"   //定义日志存放的路径和文件名
29 #define NGX_ERROR_LOG_PATH       "error.log"   //定义日志存放的路径和文件名
30 
31 
32 typedef unsigned char u_char;
33 typedef unsigned int u_int;
34 
35 //和日志,打印输出有关
36 void   ngx_log_init();
37 void   ngx_log_stderr(int err, const char *fmt, ...);
38 void   ngx_log_error_core(int level,  int err, const char *fmt, ...);
39 u_char *ngx_log_errno(u_char *buf, u_char *last, int err);
40 u_char *ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...);
41 u_char *ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...);
42 u_char *ngx_vslprintf(u_char *buf, u_char *last,const char *fmt,va_list args);
43 
44 #endif // NGX_LOG_H

ngx_log.cpp

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <stdint.h>    //uintptr_t
  5 #include <stdarg.h>    //va_start....
  6 #include <unistd.h>    //STDERR_FILENO等
  7 #include <sys/time.h>  //gettimeofday
  8 #include <time.h>      //localtime_r
  9 #include <fcntl.h>     //open
 10 #include <errno.h>     //errno
 11 #include <ngx_log.h>
 12 
 13 //#include "ngx_global.h"
 14 //#include "ngx_macro.h"
 15 //#include "ngx_func.h"
 16 #include "ngx_c_conf.h"
 17 
 18 short ngx_pid;
 19 
 20 //和运行日志相关
 21 typedef struct
 22 {
 23     int    log_level;   //日志级别 或者日志类型,ngx_macro.h里分0-8共9个级别
 24     int    fd;          //日志文件描述符
 25 
 26 }ngx_log_t;
 27 
 28 
 29 //全局量---------------------
 30 //错误等级,和ngx_macro.h里定义的日志等级宏是一一对应关系
 31 static char err_levels[][20]  =
 32 {
 33     {"stderr"},    //0:控制台错误
 34     {"emerg"},     //1:紧急
 35     {"alert"},     //2:警戒
 36     {"crit"},      //3:严重
 37     {"error"},     //4:错误
 38     {"warn"},      //5:警告
 39     {"notice"},    //6:注意
 40     {"info"},      //7:信息
 41     {"debug"}      //8:调试
 42 };
 43 
 44 ngx_log_t   ngx_log;
 45 
 46 
 47 //----------------------------------------------------------------------------------------------------------------------
 48 //描述:通过可变参数组合出字符串【支持...省略号形参】,自动往字符串最末尾增加换行符【所以调用者不用加\n】, 往标准错误上输出这个字符串;
 49 //     如果err不为0,表示有错误,会将该错误编号以及对应的错误信息一并放到组合出的字符串中一起显示;
 50 
 51 //《c++从入门到精通》里老师讲解过,比较典型的C语言中的写法,就是这种va_start,va_end
 52 //fmt:通过这第一个普通参数来寻址后续的所有可变参数的类型及其值
 53 //调用格式比如:ngx_log_stderr(0, "invalid option: \"%s\",%d", "testinfo",123);
 54  /*
 55     ngx_log_stderr(0, "invalid option: \"%s\"", argv[0]);  //nginx: invalid option: "./nginx"
 56     ngx_log_stderr(0, "invalid option: %10d", 21);         //nginx: invalid option:         21  ---21前面有8个空格
 57     ngx_log_stderr(0, "invalid option: %.6f", 21.378);     //nginx: invalid option: 21.378000   ---%.这种只跟f配合有效,往末尾填充0
 58     ngx_log_stderr(0, "invalid option: %.6f", 12.999);     //nginx: invalid option: 12.999000
 59     ngx_log_stderr(0, "invalid option: %.2f", 12.999);     //nginx: invalid option: 13.00
 60     ngx_log_stderr(0, "invalid option: %xd", 1678);        //nginx: invalid option: 68E
 61     ngx_log_stderr(0, "invalid option: %Xd", 1678);        //nginx: invalid option: 68E
 62     ngx_log_stderr(15, "invalid option: %s , %d", "testInfo",326);        //nginx: invalid option: testInfo , 326
 63     ngx_log_stderr(0, "invalid option: %d", 1678);
 64     */
 65 void ngx_log_stderr(int err, const char *fmt, ...)
 66 {
 67     //typedef char * va_list;  args就是一个char*类型的指针
 68     va_list args;                        //创建一个va_list类型变量
 69     u_char  errstr[NGX_MAX_ERROR_STR+1]; //2048  -- ************  +1是我自己填的,感谢官方写法有点小瑕疵,所以动手调整一下
 70     u_char  *p, *last;
 71 
 72     memset(errstr,0,sizeof(errstr));     //我个人加的,这块有必要加,至少在va_end处理之前有必要,否则字符串没有结束标记不行的;***************************
 73 
 74     last = errstr + NGX_MAX_ERROR_STR;        //last指向整个buffer最后去了【指向最后一个有效位置的后面也就是非有效位】,作为一个标记,防止输出内容超过这么长,
 75                                                     //其实我认为这有问题,所以我才在上边errstr[NGX_MAX_ERROR_STR+1]; 给加了1
 76                                               //比如你定义 char tmp[2]; 你如果last = tmp+2,那么last实际指向了tmp[2],而tmp[2]在使用中是无效的
 77 
 78     p = ngx_cpymem(errstr, "nginx: ", 7);     //p指向"nginx: "之后
 79 
 80     //让args指向可变参数的第一项
 81     va_start(args, fmt);  //使args指向起始的参数 args = &param1+sizeof(param1)
 82 
 83     p = ngx_vslprintf(p,last,fmt,args); //组合出这个字符串保存在errstr里
 84     va_end(args);        //释放args
 85 
 86     if (err)  //如果错误代码不是0,表示有错误发生
 87     {
 88         //错误代码和错误信息也要显示出来
 89         p = ngx_log_errno(p, last, err);
 90     }
 91 
 92     //若位置不够,那换行也要硬插入到末尾,哪怕覆盖到其他内容
 93     if (p >= (last - 1))
 94     {
 95         p = (last - 1) - 1; //把尾部空格留出来,这里感觉nginx处理的似乎就不对
 96                              //我觉得,last-1,才是最后 一个而有效的内存,而这个位置要保存\0,所以我认为再减1,这个位置,才适合保存\n
 97     }
 98     *p++ = '\n'; //增加个换行符
 99 
100     //往标准错误【一般是屏幕】输出信息
101     write(STDERR_FILENO,errstr,p - errstr); //三章七节讲过,这个叫标准错误,一般指屏幕
102 
103     if(ngx_log.fd > STDERR_FILENO) //如果这是个有效的日志文件,本条件肯定成立,此时也才有意义将这个信息写到日志文件
104     {
105         //因为上边已经把err信息显示出来了,所以这里就不要显示了,否则显示重复了
106         err = 0;    //不要再次把错误信息弄到字符串里,否则字符串里重复了
107         p--;*p = 0; //把原来末尾的\n干掉,因为到ngx_log_err_core中还会加这个\n
108         ngx_log_error_core(NGX_LOG_STDERR,err,(const char *)errstr);
109     }
110     return;
111 }
112 
113 //----------------------------------------------------------------------------------------------------------------------
114 //描述:给一段内存,一个错误编号,我要组合出一个字符串,形如:   (错误编号: 错误原因),放到给的这段内存中去
115 //     这个函数我改造的比较多,和原始的nginx代码多有不同
116 //buf:是个内存,要往这里保存数据
117 //last:放的数据不要超过这里
118 //err:错误编号,我们是要取得这个错误编号对应的错误字符串,保存到buffer中
119 u_char *ngx_log_errno(u_char *buf, u_char *last, int err)
120 {
121     //以下代码是我自己改造,感觉作者的代码有些瑕疵
122     char *perrorinfo = strerror(err); //根据资料不会返回NULL;
123     size_t len = strlen(perrorinfo);
124 
125     //然后我还要插入一些字符串: (%d:)
126     char leftstr[10] = {0};
127     sprintf(leftstr," (%d: ",err);
128     size_t leftlen = strlen(leftstr);
129 
130     char rightstr[] = ") ";
131     size_t rightlen = strlen(rightstr);
132 
133     size_t extralen = leftlen + rightlen; //左右的额外宽度
134     if ((buf + len + extralen) < last)
135     {
136         //保证整个我装得下,我就装,否则我全部抛弃 ,nginx的做法是 如果位置不够,就硬留出50个位置【哪怕覆盖掉以往的有效内容】,也要硬往后边塞,这样当然也可以;
137         buf = ngx_cpymem(buf, leftstr, leftlen);
138         buf = ngx_cpymem(buf, perrorinfo, len);
139         buf = ngx_cpymem(buf, rightstr, rightlen);
140     }
141     return buf;
142 }
143 
144 //----------------------------------------------------------------------------------------------------------------------
145 //往日志文件中写日志,代码中有自动加换行符,所以调用时字符串不用刻意加\n;
146 //    日过定向为标准错误,则直接往屏幕上写日志【比如日志文件打不开,则会直接定位到标准错误,此时日志就打印到屏幕上,参考ngx_log_init()】
147 //level:一个等级数字,我们把日志分成一些等级,以方便管理、显示、过滤等等,如果这个等级数字比配置文件中的等级数字"LogLevel"大,那么该条信息不被写到日志文件中
148 //err:是个错误代码,如果不是0,就应该转换成显示对应的错误信息,一起写到日志文件中,
149 //ngx_log_error_core(5,8,"这个XXX工作的有问题,显示的结果是=%s","YYYY");
150 void ngx_log_error_core(int level,  int err, const char *fmt, ...)
151 {
152     u_char  *last;
153     u_char  errstr[NGX_MAX_ERROR_STR+1];   //这个+1也是我放入进来的,本函数可以参考ngx_log_stderr()函数的写法;
154 
155     memset(errstr,0,sizeof(errstr));
156     last = errstr + NGX_MAX_ERROR_STR;
157 
158     struct timeval   tv;
159     struct tm        tm;
160     time_t           sec;   //
161     u_char           *p;    //指向当前要拷贝数据到其中的内存位置
162     va_list          args;
163 
164     memset(&tv,0,sizeof(struct timeval));
165     memset(&tm,0,sizeof(struct tm));
166 
167     gettimeofday(&tv, NULL);     //获取当前时间,返回自1970-01-01 00:00:00到现在经历的秒数【第二个参数是时区,一般不关心】
168 
169     sec = tv.tv_sec;             //
170     localtime_r(&sec, &tm);      //把参数1的time_t转换为本地时间,保存到参数2中去,带_r的是线程安全的版本,尽量使用
171     tm.tm_mon++;                 //月份要调整下正常
172     tm.tm_year += 1900;          //年份要调整下才正常
173 
174     u_char strcurrtime[40]={0};  //先组合出一个当前时间字符串,格式形如:2019/01/08 19:57:11
175     ngx_slprintf(strcurrtime,
176                     (u_char *)-1,                       //若用一个u_char *接一个 (u_char *)-1,则 得到的结果是 0xffffffff....,这个值足够大
177                     "%4d/%02d/%02d %02d:%02d:%02d",     //格式是 年/月/日 时:分:秒
178                     tm.tm_year, tm.tm_mon,
179                     tm.tm_mday, tm.tm_hour,
180                     tm.tm_min, tm.tm_sec);
181     p = ngx_cpymem(errstr,strcurrtime,strlen((const char *)strcurrtime));  //日期增加进来,得到形如:     2019/01/08 20:26:07
182     p = ngx_slprintf(p, last, " [%s] ", err_levels[level]);                //日志级别增加进来,得到形如:  2019/01/08 20:26:07 [crit]
183     p = ngx_slprintf(p, last, "%P: ",ngx_pid);                             //支持%P格式,进程id增加进来,得到形如:   2019/01/08 20:50:15 [crit] 2037:
184 
185     va_start(args, fmt);                     //使args指向起始的参数
186     p = ngx_vslprintf(p, last, fmt, args);   //把fmt和args参数弄进去,组合出来这个字符串
187     va_end(args);                            //释放args
188 
189     if (err)  //如果错误代码不是0,表示有错误发生
190     {
191         //错误代码和错误信息也要显示出来
192         p = ngx_log_errno(p, last, err);
193     }
194     //若位置不够,那换行也要硬插入到末尾,哪怕覆盖到其他内容
195     if (p >= (last - 1))
196     {
197         p = (last - 1) - 1; //把尾部空格留出来,这里感觉nginx处理的似乎就不对
198                              //我觉得,last-1,才是最后 一个而有效的内存,而这个位置要保存\0,所以我认为再减1,这个位置,才适合保存\n
199     }
200     *p++ = '\n'; //增加个换行符
201 
202     //这么写代码是图方便:随时可以把流程弄到while后边去;大家可以借鉴一下这种写法
203     ssize_t   n;
204     while(1)
205     {
206         if (level > ngx_log.log_level)
207         {
208             //要打印的这个日志的等级太落后(等级数字太大,比配置文件中的数字大)
209             //这种日志就不打印了
210             break;
211         }
212         //磁盘是否满了的判断,先算了吧,还是由管理员保证这个事情吧;
213 
214         //写日志文件
215         n = write(ngx_log.fd,errstr,p - errstr);  //文件写入成功后,如果中途
216         if (n == -1)
217         {
218             //写失败有问题
219             if(errno == ENOSPC) //写失败,且原因是磁盘没空间了
220             {
221                 //磁盘没空间了
222                 //没空间还写个毛线啊
223                 //先do nothing吧;
224             }
225             else
226             {
227                 //这是有其他错误,那么我考虑把这个错误显示到标准错误设备吧;
228                 if(ngx_log.fd != STDERR_FILENO) //当前是定位到文件的,则条件成立
229                 {
230                     n = write(STDERR_FILENO,errstr,p - errstr);
231                 }
232             }
233         }
234         break;
235     } //end while
236     return;
237 }
238 
239 //----------------------------------------------------------------------------------------------------------------------
240 //描述:日志初始化,就是把日志文件打开 ,注意这里边涉及到释放的问题,如何解决?
241 void ngx_log_init()
242 {
243     u_char *plogname = NULL;
244     size_t nlen;
245 
246     //从配置文件中读取和日志相关的配置信息
247     CConfig *p_config = CConfig::GetInstance();
248     plogname = (u_char *)p_config->GetString("Log");
249     if(plogname == NULL)
250     {
251         //没读到,就要给个缺省的路径文件名了
252         plogname = (u_char *) NGX_ERROR_LOG_PATH; //"logs/error.log" ,logs目录需要提前建立出来
253     }
254     ngx_log.log_level = p_config->GetIntDefault("LogLevel",NGX_LOG_NOTICE);//缺省日志等级为6【注意】 ,如果读失败,就给缺省日志等级
255     //nlen = strlen((const char *)plogname);
256 
257     //只写打开|追加到末尾|文件不存在则创建【这个需要跟第三参数指定文件访问权限】
258     //mode = 0644:文件访问权限, 6: 110    , 4: 100:     【用户:读写, 用户所在组:读,其他:读】 老师在第三章第一节介绍过
259     //ngx_log.fd = open((const char *)plogname,O_WRONLY|O_APPEND|O_CREAT|O_DIRECT,0644);   //绕过内和缓冲区,write()成功则写磁盘必然成功,但效率可能会比较低;
260     ngx_log.fd = open((const char *)plogname,O_WRONLY|O_APPEND|O_CREAT,0644);
261     if (ngx_log.fd == -1)  //如果有错误,则直接定位到 标准错误上去
262     {
263         ngx_log_stderr(errno,"[alert] could not open error log file: open() \"%s\" failed", plogname);
264         ngx_log.fd = STDERR_FILENO; //直接定位到标准错误去了
265     }
266     return;
267 }

ngx_printf.cpp

  1 //和打印格式相关的函数放这里
  7 #include <stdio.h>
  8 #include <stdlib.h>
  9 #include <string.h>
 10 #include <stdarg.h>
 11 #include <stdint.h>   //类型相关头文件
 12 
 13 #include <ngx_log.h>
 14 
 15 typedef long long pid_t;
 16 //只用于本文件的一些函数声明就放在本文件中
 17 static u_char *ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64,u_char zero, uintptr_t hexadecimal, uintptr_t width);
 18 
 19 //----------------------------------------------------------------------------------------------------------------------
 20 //对于 nginx 自定义的数据结构进行标准格式化输出,就像 printf,vprintf 一样,我们顺道学习写这类函数到底内部是怎么实现的
 21 //该函数只不过相当于针对ngx_vslprintf()函数包装了一下,所以,直接研究ngx_vslprintf()即可
 22 u_char *ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...)
 23 {
 24     va_list   args;
 25     u_char   *p;
 26 
 27     va_start(args, fmt); //使args指向起始的参数
 28     p = ngx_vslprintf(buf, last, fmt, args);
 29     va_end(args);        //释放args
 30     return p;
 31 }
 32 
 33 //----------------------------------------------------------------------------------------------------------------------
 34 //和上边的ngx_snprintf非常类似
 35 u_char * ngx_snprintf(u_char *buf, size_t max, const char *fmt, ...)   //类printf()格式化函数,比较安全,max指明了缓冲区结束位置
 36 {
 37     u_char   *p;
 38     va_list   args;
 39 
 40     va_start(args, fmt);
 41     p = ngx_vslprintf(buf, buf + max, fmt, args);
 42     va_end(args);
 43     return p;
 44 }
 45 
 46 //----------------------------------------------------------------------------------------------------------------------
 47 //对于 nginx 自定义的数据结构进行标准格式化输出,就像 printf,vprintf 一样,我们顺道学习写这类函数到底内部是怎么实现的
 48 //例如,给进来一个 "abc = %d",13   ,最终buf里得到的应该是   abc=13 这种结果
 49 //buf:往这里放数据
 50 //last:放的数据不要超过这里
 51 //fmt:以这个为首的一系列可变参数
 52 //支持的格式: %d【%Xd/%xd】:数字,    %s:字符串      %f:浮点,  %P:pid_t
 53     //对于:ngx_log_stderr(0, "invalid option: \"%s\",%d", "testinfo",123);
 54        //fmt = "invalid option: \"%s\",%d"
 55        //args = "testinfo",123
 56 u_char *ngx_vslprintf(u_char *buf, u_char *last,const char *fmt,va_list args)
 57 {
 58     //比如说你要调用ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);,那么这里的fmt就应该是:   invalid option: "%s"
 59     //printf("fmt = %s\n",fmt);
 60 
 61     u_char     zero;
 62 
 63     uintptr_t  width,sign,hex,frac_width,scale,n;  //临时用到的一些变量
 64 
 65     int64_t    i64;   //保存%d对应的可变参
 66     uint64_t   ui64;  //保存%ud对应的可变参,临时作为%f可变参的整数部分也是可以的
 67     u_char     *p;    //保存%s对应的可变参
 68     double     f;     //保存%f对应的可变参
 69     uint64_t   frac;  //%f可变参数,根据%.2f等,取得小数部分的2位后的内容;
 70 
 71 
 72     while (*fmt && buf < last) //每次处理一个字符,处理的是  "invalid option: \"%s\",%d" 中的字符
 73     {
 74         if (*fmt == '%')  //%开头的一般都是需要被可变参数 取代的
 75         {
 76             //-----------------变量初始化工作开始-----------------
 77             //++fmt是先加后用,也就是fmt先往后走一个字节位置,然后再判断该位置的内容
 78             zero  = (u_char) ((*++fmt == '0') ? '0' : ' ');  //判断%后边接的是否是个'0',如果是zero = '0',否则zero = ' ',一般比如你想显示10位,而实际数字7位,前头填充三个字符,就是这里的zero用于填充
 79                                                                 //ngx_log_stderr(0, "数字是%010d", 12);
 80 
 81             width = 0;                                       //格式字符% 后边如果是个数字,这个数字最终会弄到width里边来 ,这东西目前只对数字格式有效,比如%d,%f这种
 82             sign  = 1;                                       //显示的是否是有符号数,这里给1,表示是有符号数,除非你 用%u,这个u表示无符号数
 83             hex   = 0;                                       //是否以16进制形式显示(比如显示一些地址),0:不是,1:是,并以小写字母显示a-f,2:是,并以大写字母显示A-F
 84             frac_width = 0;                                  //小数点后位数字,一般需要和%.10f配合使用,这里10就是frac_width;
 85             i64 = 0;                                         //一般用%d对应的可变参中的实际数字,会保存在这里
 86             ui64 = 0;                                        //一般用%ud对应的可变参中的实际数字,会保存在这里
 87 
 88             //-----------------变量初始化工作结束-----------------
 89 
 90             //这个while就是判断%后边是否是个数字,如果是个数字,就把这个数字取出来,比如%16,最终这个循环就能够把16取出来弄到width里边去
 91             //%16d 这里最终width = 16;
 92             while (*fmt >= '0' && *fmt <= '9')  //如果%后边接的字符是 '0' --'9'之间的内容   ,比如  %16这种;
 93             {
 94                 //第一次 :width = 1;  第二次 width = 16,所以整个width = 16;
 95                 width = width * 10 + (*fmt++ - '0');
 96             }
 97 
 98             for ( ;; ) //一些特殊的格式,我们做一些特殊的标记【给一些变量特殊值等等】
 99             {
100                 switch (*fmt)  //处理一些%之后的特殊字符
101                 {
102                 case 'u':       //%u,这个u表示无符号
103                     sign = 0;   //标记这是个无符号数
104                     fmt++;      //往后走一个字符
105                     continue;   //回到for继续判断
106 
107                 case 'X':       //%X,X表示十六进制,并且十六进制中的A-F以大写字母显示,不要单独使用,一般是%Xd
108                     hex = 2;    //标记以大写字母显示十六进制中的A-F
109                     sign = 0;
110                     fmt++;
111                     continue;
112                 case 'x':       //%x,x表示十六进制,并且十六进制中的a-f以小写字母显示,不要单独使用,一般是%xd
113                     hex = 1;    //标记以小写字母显示十六进制中的a-f
114                     sign = 0;
115                     fmt++;
116                     continue;
117 
118                 case '.':       //其后边必须跟个数字,必须与%f配合使用,形如 %.10f:表示转换浮点数时小数部分的位数,比如%.10f表示转换浮点数时,小数点后必须保证10位数字,不足10位则用0来填补;
119                     fmt++;      //往后走一个字符,后边这个字符肯定是0-9之间,因为%.要求接个数字先
120                     while(*fmt >= '0' && *fmt <= '9')  //如果是数字,一直循环,这个循环最终就能把诸如%.10f中的10提取出来
121                     {
122                         frac_width = frac_width * 10 + (*fmt++ - '0');
123                     } //end while(*fmt >= '0' && *fmt <= '9')
124                     break;
125 
126                 default:
127                     break;
128                 } //end switch (*fmt)
129                 break;
130             } //end for ( ;; )
131 
132             switch (*fmt)
133             {
134             case '%': //只有%%时才会遇到这个情形,本意是打印一个%,所以
135                 *buf++ = '%';
136                 fmt++;
137                 continue;
138 
139             case 'd': //显示整型数据,如果和u配合使用,也就是%ud,则是显示无符号整型数据
140                 if (sign)  //如果是有符号数
141                 {
142                     i64 = (int64_t) va_arg(args, int);  //va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变的参数的类型
143                 }
144                 else //如何是和 %ud配合使用,则本条件就成立
145                 {
146                     ui64 = (uint64_t) va_arg(args, u_int);
147                 }
148                 break;  //这break掉,直接跳道switch后边的代码去执行,这种凡是break的,都不做fmt++;  *********************【switch后仍旧需要进一步处理】
149 
150              case 'i': //转换ngx_int_t型数据,如果用%ui,则转换的数据类型是ngx_uint_t
151                 if (sign)
152                 {
153                     i64 = (int64_t) va_arg(args, intptr_t);
154                 }
155                 else
156                 {
157                     ui64 = (uint64_t) va_arg(args, uintptr_t);
158                 }
159 
160                 //if (max_width)
161                 //{
162                 //    width = NGX_INT_T_LEN;
163                 //}
164 
165                 break;
166 
167             case 'L':  //转换int64j型数据,如果用%uL,则转换的数据类型是uint64 t
168                 if (sign)
169                 {
170                     i64 = va_arg(args, int64_t);
171                 }
172                 else
173                 {
174                     ui64 = va_arg(args, uint64_t);
175                 }
176                 break;
177 
178             case 'p':
179                 ui64 = (uintptr_t) va_arg(args, void *);
180                 hex = 2;    //标记以大写字母显示十六进制中的A-F
181                 sign = 0;   //标记这是个无符号数
182                 zero = '0'; //前边0填充
183                 width = 2 * sizeof(void *);
184                 break;
185 
186             case 's': //一般用于显示字符串
187                 p = va_arg(args, u_char *); //va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变的参数的类型
188 
189                 while (*p && buf < last)  //没遇到字符串结束标记,并且buf值够装得下这个参数
190                 {
191                     *buf++ = *p++;  //那就装,比如  "%s"    ,   "abcdefg",那abcdefg都被装进来
192                 }
193 
194                 fmt++;
195                 continue; //重新从while开始执行
196 
197             case 'P':  //转换一个pid_t类型
198                 i64 = (int64_t) va_arg(args, pid_t);
199                 sign = 1;
200                 break;
201 
202             case 'f': //一般 用于显示double类型数据,如果要显示小数部分,则要形如 %.5f
203                 f = va_arg(args, double);  //va_arg():遍历可变参数,var_arg的第二个参数表示遍历的这个可变的参数的类型
204                 if (f < 0)  //负数的处理
205                 {
206                     *buf++ = '-'; //单独搞个负号出来
207                     f = -f; //那这里f应该是正数了!
208                 }
209                 //走到这里保证f肯定 >= 0【不为负数】
210                 ui64 = (int64_t) f; //正整数部分给到ui64里
211                 frac = 0;
212 
213                 //如果要求小数点后显示多少位小数
214                 if (frac_width) //如果是%d.2f,那么frac_width就会是这里的2
215                 {
216                     scale = 1;  //缩放从1开始
217                     for (n = frac_width; n; n--)
218                     {
219                         scale *= 10; //这可能溢出哦
220                     }
221 
222                     //把小数部分取出来 ,比如如果是格式    %.2f   ,对应的参数是12.537
223                     // (uint64_t) ((12.537 - (double) 12) * 100 + 0.5);
224                                 //= (uint64_t) (0.537 * 100 + 0.5)  = (uint64_t) (53.7 + 0.5) = (uint64_t) (54.2) = 54
225                     frac = (uint64_t) ((f - (double) ui64) * scale + 0.5);   //取得保留的那些小数位数,【比如  %.2f   ,对应的参数是12.537,取得的就是小数点后的2位四舍五入,也就是54】
226                                                                              //如果是"%.6f", 21.378,那么这里frac = 378000
227 
228                     if (frac == scale)   //进位,比如    %.2f ,对应的参数是12.999,那么  = (uint64_t) (0.999 * 100 + 0.5)  = (uint64_t) (99.9 + 0.5) = (uint64_t) (100.4) = 100
229                                           //而此时scale == 100,两者正好相等
230                     {
231                         ui64++;    //正整数部分进位
232                         frac = 0;  //小数部分归0
233                     }
234                 } //end if (frac_width)
235 
236                 //正整数部分,先显示出来
237                 buf = ngx_sprintf_num(buf, last, ui64, zero, 0, width); //把一个数字 比如“1234567”弄到buffer中显示
238 
239                 if (frac_width) //指定了显示多少位小数
240                 {
241                     if (buf < last)
242                     {
243                         *buf++ = '.'; //因为指定显示多少位小数,先把小数点增加进来
244                     }
245                     buf = ngx_sprintf_num(buf, last, frac, '0', 0, frac_width); //frac这里是小数部分,显示出来,不够的,前边填充'0'字符
246                 }
247                 fmt++;
248                 continue;  //重新从while开始执行
249 
250             //..................................
251             //................其他格式符,逐步完善
252             //..................................
253 
254             default:
255                 *buf++ = *fmt++; //往下移动一个字符
256                 continue; //注意这里不break,而是continue;而这个continue其实是continue到外层的while去了,也就是流程重新从while开头开始执行;
257             } //end switch (*fmt)
258 
259             //显示%d的,会走下来,其他走下来的格式日后逐步完善......
260 
261             //统一把显示的数字都保存到 ui64 里去;
262             if (sign) //显示的是有符号数
263             {
264                 if (i64 < 0)  //这可能是和%d格式对应的要显示的数字
265                 {
266                     *buf++ = '-';  //小于0,自然要把负号先显示出来
267                     ui64 = (uint64_t) -i64; //变成无符号数(正数)
268                 }
269                 else //显示正数
270                 {
271                     ui64 = (uint64_t) i64;
272                 }
273             } //end if (sign)
274 
275             //把一个数字 比如“1234567”弄到buffer中显示,如果是要求10位,则前边会填充3个空格比如“   1234567”
276             //注意第5个参数hex,是否以16进制显示,比如如果你是想以16进制显示一个数字则可以%Xd或者%xd,此时hex = 2或者1
277             buf = ngx_sprintf_num(buf, last, ui64, zero, hex, width);
278             fmt++;
279         }
280         else  //当成正常字符,源【fmt】拷贝到目标【buf】里
281         {
282             //用fmt当前指向的字符赋给buf当前指向的位置,然后buf往前走一个字符位置,fmt当前走一个字符位置
283             *buf++ = *fmt++;   //*和++优先级相同,结合性从右到左,所以先求的是buf++以及fmt++,但++是先用后加;
284         } //end if (*fmt == '%')
285     }  //end while (*fmt && buf < last)
286 
287     return buf;
288 }
289 
290 //----------------------------------------------------------------------------------------------------------------------
291 //以一个指定的宽度把一个数字显示在buf对应的内存中, 如果实际显示的数字位数 比指定的宽度要小 ,比如指定显示10位,而你实际要显示的只有“1234567”,那结果可能是会显示“   1234567”
292      //当然如果你不指定宽度【参数width=0】,则按实际宽度显示
293      //你给进来一个%Xd之类的,还能以十六进制数字格式显示出来
294 //buf:往这里放数据
295 //last:放的数据不要超过这里
296 //ui64:显示的数字
297 //zero:显示内容时,格式字符%后边接的是否是个'0',如果是zero = '0',否则zero = ' ' 【一般显示的数字位数不足要求的,则用这个字符填充】,比如要显示10位,而实际只有7位,则后边填充3个这个字符;
298 //hexadecimal:是否显示成十六进制数字 0:不
299 //width:显示内容时,格式化字符%后接的如果是个数字比如%16,那么width=16,所以这个是希望显示的宽度值【如果实际显示的内容不够,则后头用0填充】
300 static u_char * ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64, u_char zero, uintptr_t hexadecimal, uintptr_t width)
301 {
302     //temp[21]
303     u_char      *p, temp[NGX_INT64_LEN + 1];   //#define NGX_INT64_LEN   (sizeof("-9223372036854775808") - 1)     = 20   ,注意这里是sizeof是包括末尾的\0,不是strlen;
304     size_t      len;
305     uint32_t    ui32;
306 
307     static u_char   hex[] = "0123456789abcdef";  //跟把一个10进制数显示成16进制有关,换句话说和  %xd格式符有关,显示的16进制数中a-f小写
308     static u_char   HEX[] = "0123456789ABCDEF";  //跟把一个10进制数显示成16进制有关,换句话说和  %Xd格式符有关,显示的16进制数中A-F大写
309 
310     p = temp + NGX_INT64_LEN; //NGX_INT64_LEN = 20,所以 p指向的是temp[20]那个位置,也就是数组最后一个元素位置
311 
312     if (hexadecimal == 0)
313     {
314         if (ui64 <= (uint64_t) NGX_MAX_UINT32_VALUE)   //NGX_MAX_UINT32_VALUE :最大的32位无符号数:十进制是‭4294967295‬
315         {
316             ui32 = (uint32_t) ui64; //能保存下
317             do  //这个循环能够把诸如 7654321这个数字保存成:temp[13]=7,temp[14]=6,temp[15]=5,temp[16]=4,temp[17]=3,temp[18]=2,temp[19]=1
318                   //而且的包括temp[0..12]以及temp[20]都是不确定的值
319             {
320                 *--p = (u_char) (ui32 % 10 + '0');  //把屁股后边这个数字拿出来往数组里装,并且是倒着装:屁股后的也往数组下标大的位置装;
321             }
322             while (ui32 /= 10); //每次缩小10倍等于去掉屁股后边这个数字
323         }
324         else
325         {
326             do
327             {
328                 *--p = (u_char) (ui64 % 10 + '0');
329             } while (ui64 /= 10); //每次缩小10倍等于去掉屁股后边这个数字
330         }
331     }
332     else if (hexadecimal == 1)  //如果显示一个十六进制数字,格式符为:%xd,则这个条件成立,要以16进制数字形式显示出来这个十进制数,a-f小写
333     {
334         //比如我显示一个1,234,567【十进制数】,他对应的二进制数实际是 12 D687 ,那怎么显示出这个12D687来呢?
335         do
336         {
337             //0xf就是二进制的1111,大家都学习过位运算,ui64 & 0xf,就等于把 一个数的最末尾的4个二进制位拿出来;
338             //ui64 & 0xf  其实就能分别得到 这个16进制数也就是 7,8,6,D,2,1这个数字,转成 (uint32_t) ,然后以这个为hex的下标,找到这几个数字的对应的能够显示的字符;
339             *--p = hex[(uint32_t) (ui64 & 0xf)];
340         } while (ui64 >>= 4);    //ui64 >>= 4     --->   ui64 = ui64 >> 4 ,而ui64 >> 4是啥,实际上就是右移4位,就是除以16,因为右移4位就等于移动了1111;
341                                  //相当于把该16进制数的最末尾一位干掉,原来是 12 D687, >> 4后是 12 D68,如此反复,最终肯定有=0时导致while不成立退出循环
342                                   //比如 1234567 / 16 = 77160(0x12D68)
343                                   // 77160 / 16 = 4822(0x12D6)
344     }
345     else // hexadecimal == 2    //如果显示一个十六进制数字,格式符为:%Xd,则这个条件成立,要以16进制数字形式显示出来这个十进制数,A-F大写
346     {
347         //参考else if (hexadecimal == 1),非常类似
348         do
349         {
350             *--p = HEX[(uint32_t) (ui64 & 0xf)];
351         } while (ui64 >>= 4);
352     }
353 
354     len = (temp + NGX_INT64_LEN) - p;  //得到这个数字的宽度,比如 “7654321”这个数字 ,len = 7
355 
356     while (len++ < width && buf < last)  //如果你希望显示的宽度是10个宽度【%12f】,而实际想显示的是7654321,只有7个宽度,那么这里要填充5个0进去到末尾,凑够要求的宽度
357     {
358         *buf++ = zero;  //填充0进去到buffer中(往末尾增加),比如你用格式
359                                           //ngx_log_stderr(0, "invalid option: %10d\n", 21);
360                                           //显示的结果是:nginx: invalid option:         21  ---21前面有8个空格,这8个弄个,就是在这里添加进去的;
361     }
362 
363     len = (temp + NGX_INT64_LEN) - p; //还原这个len,也就是要显示的数字的实际宽度【因为上边这个while循环改变了len的值】
364     //现在还没把实际的数字比如“7654321”往buf里拷贝呢,要准备拷贝
365 
366     //如下这个等号是我加的【我认为应该加等号】,nginx源码里并没有加;***********************************************
367     if((buf + len) >= last)   //发现如果往buf里拷贝“7654321”后,会导致buf不够长【剩余的空间不够拷贝整个数字】
368     {
369         len = last - buf; //剩余的buf有多少我就拷贝多少
370     }
371 
372     return ngx_cpymem(buf, p, len); //把最新buf返回去;
373 }

 

posted @ 2022-12-11 21:46  我就不告诉你我是谁  阅读(135)  评论(0编辑  收藏  举报