设为首页 收藏本站
查看: 959|回复: 0

[经验分享] nginx的error_log和access_log分析(404)

[复制链接]

尚未签到

发表于 2018-11-13 12:56:07 | 显示全部楼层 |阅读模式
  nginx配置中有关日志的配置主要是围绕着下面两个指令:
  1、error_log
  2、access_log:记录访问日志
  首先要强调的一点是,如果access日志和error日志都是常量文件名(因为access支持变量文件名,后续会讲到),那么nginx进程会缓存文件描述符直到进程结束。
  什么时候日志的fd会改变呢?
  1)进程重启
  2)收到了NGX_REOPEN_SIGNAL信号,会产生新的日志文件
  其他情况下,日志的fd不变,所以当进程运行中,删除了日志文件的话,并不会生成新的日志文件,且日志都会丢失
  下面详细讲一下这两个指令的来龙去脉
  一:先说error_log:
  nginx有两个模块支持error_log指令:
  一个是 ngx_errlog_module ,这个模块只有一个指令,就是error_log ,配置类型为:NGX_MAIN_CONF,回调函数为:ngx_error_log;
  另一个是 ngx_http_core_module,这个模块中也有指令:error_log ,配置类型为:NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF,回调函数为:ngx_http_core_error_log。
[cpp]view plaincopy

  •   static ngx_command_t  ngx_errlog_commands[] = {
  •   {ngx_string("error_log"),
  •   NGX_MAIN_CONF|NGX_CONF_1MORE,
  •   ngx_error_log,
  •   0,
  •   0,
  •   NULL},
  •   ngx_null_command
  •   };
[cpp]view plaincopy

  •   static ngx_command_t  ngx_http_core_commands[] = {
  •   { ngx_string("error_log"),
  •   NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
  •   ngx_http_core_error_log,
  •   NGX_HTTP_LOC_CONF_OFFSET,
  •   0,
  •   NULL },
  •   }
  这样会产生几个疑问:
  1:为什么需要两个相同的指令实现相同的功能。
  2:两个指令的类型均支持NGX_HTTP_MAIN_CONF ,那么在main中配置的error_log到底使用的是哪一个
  3:两者的作用关系。
  下面来解释一下:
  nginx在进行模块注册时,会发现 ngx_errlog_module 模块是先于 ngx_http_core_module 模块注册的 。
  在nginx在解析配置文件的时候 ,见到 error_log,会按照注册模块的顺序查找指令,这样,会先找到ngx_errlog_module模块,如果此时,error_log是在main配置的,那么和ngx_errlog_module模块error_log的NGX_HTTP_MAIN_CONF match,执行ngx_error_log。
  如果error_log是在http{}配置的,也会按照注册模块的顺序查找指令,找到ngx_errlog_module模块的error_log,发现type是NGX_HTTP_MAIN_CONF,不match,继续往下找,找到ngx_http_core_module的error_log,type是NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF ,match后执行ngx_http_core_error_log。
  上面提到了ngx_error_log 和 ngx_http_core_error_log两个函数,这两个函数的功能基本一致,但是因为两个配置作用域不同,所以配置存储位置不同:ngx_errlog_module存储在cycle->new_log,ngx_http_core_module存储在http core模块数据结构ngx_http_core_loc_conf_s的error_log(在此简写成:clcf->error_log)。
  clcf->error_log是http模块中的,其主要记录和http请求相关的日志。
  cycle->new_log主要记录如进程启动,event等。
  但是主进程启动的时候,此时还没有读取配置文件,即没有指定日志打印在哪里。nginx这时候虽然可以将一些出错内容或者结果输到标准输出,但是如果要记录一些系统初始化情况,socket监听状况,还是需要写到日志文件中去的。在nginx的main函数中,首先会调用ngx_log_init 函数,默认日志文件为:安装路径/logs/error.log,如果这个文件没有权限访问的话,会直接报错退出。在mian函数结尾处,在ngx_master_process_cycle函数调用之前,会close掉这个日志文件。
  如果只在main配置了error_log,http{}中没有设置,那么clcf->error_log赋值为clcf->error_log,如下代码:
[cpp]view plaincopy

  •   staticchar *
  •   ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
  •   {
  •   ngx_http_core_loc_conf_t *prev = parent;
  •   ngx_http_core_loc_conf_t *conf = child;
  •   。。。。。。
  •   if (conf->error_log == NULL) {
  •   if (prev->error_log) {
  •   conf->error_log = prev->error_log;
  •   } else {
  •   conf->error_log = &cf->cycle->new_log;
  •   }
  •   }
  •   。。。。。。
  •   }
  那为什么不把两个指令合并到一起呢。
  首先看一下模块注册顺序:
[cpp]view plaincopy

  •   ngx_module_t *ngx_modules[] = {
  •   &ngx_core_module,
  •   &ngx_errlog_module,
  •   &ngx_conf_module,
  •   &ngx_events_module,
  •   &ngx_event_core_module,
  •   &ngx_rtsig_module,
  •   &ngx_epoll_module,
  •   &ngx_regex_module,
  •   &ngx_http_module,
  •   &ngx_http_core_module,
  •   &ngx_http_log_module,
  •   ......
  •   }
  可见ngx_errlog_module 和 ngx_http_core_module中间有n多模块。这些模块是独立于http的,而且需要日志的打印,这些日志肯定是需要记录的。
  则此模块不可省,那么考虑把ngx_http_core_module的error_log合并过来呢,想想也不行,为什么呢?
  想想ngx_errlog_module将error_log信息存在哪里,想想ngx_http_core_module的error_log信息存在哪里。
  在调用ngx_error_log时候,把针对不同server或者location的error_log信息存储在哪里。(模块之间不能深度耦合)
  为了两个模块互不影响,这是个好办法呀!
  二:access_log
  接下来看一下access_log,access_log指令是属于ngx_http_log_module模块。
  ngx_http_log_module有三个指令:
[cpp]view plaincopy

  •   static ngx_command_t  ngx_http_log_commands[] = {
  •   { ngx_string("log_format"),
  •   NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_2MORE,
  •   ngx_http_log_set_format,
  •   NGX_HTTP_MAIN_CONF_OFFSET,
  •   0,
  •   NULL },
  •   { ngx_string("access_log"),
  •   NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
  •   |NGX_HTTP_LMT_CONF|NGX_CONF_TAKE123,
  •   ngx_http_log_set_log,
  •   NGX_HTTP_LOC_CONF_OFFSET,
  •   0,
  •   NULL },
  •   { ngx_string("open_log_file_cache"),
  •   NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
  •   ngx_http_log_open_file_cache,
  •   NGX_HTTP_LOC_CONF_OFFSET,
  •   0,
  •   NULL },
  •   ngx_null_command
  •   };
  每个block都可以配置上面的指令 ,这些指令对应着各个block配置中的loc[ngx_http_log_module.index](要理解这个,需要知道配置文件的整个解析过程和block对应关系),即下面这个数据结构:
[cpp]view plaincopy

  •   typedefstruct {
  •   ngx_array_t                *logs;       /* array of ngx_http_log_t */
  •   ngx_open_file_cache_t      *open_file_cache;
  •   time_t                      open_file_cache_valid;
  •   ngx_uint_t                  open_file_cache_min_uses;
  •   ngx_uint_t                  off;        /* unsigned  off:1 */
  •   } ngx_http_log_loc_conf_t;
  关于access_log主要有以下几个点:
  1、ngx_http_log_module是属于nginx状态机最后一个阶段NGX_HTTP_LOG_PHASE的处理模块,即一个http请求结束时执行的,它的任务就是打印这次request的访问情况。
  2、access_log支持根据变量指令路径,如:
  access_log  logs/'$remote_addr'access.log  main;
  3、不管是变量路径还是常量路径,都将信息放入了 ngx_http_log_loc_conf_t的logs这个数组里进行管理,logs数组的元素是ngx_http_log_t。
[cpp]view plaincopy

  •   typedefstruct {
  •   ngx_open_file_t            *file;
  •   ngx_http_log_script_t      *script;
  •   time_t                      disk_full_time;
  •   time_t                      error_log_time;
  •   ngx_http_log_fmt_t         *format;
  •   } ngx_http_log_t;
  当是常量的时候使用file记录,这个文件记录会放入到cycle->open_files
[cpp]view plaincopy

  •   struct ngx_open_file_s {
  •   ngx_fd_t              fd;
  •   ngx_str_t             name;
  •   u_char               *buffer;
  •   u_char               *pos;
  •   u_char               *last;
  •   };
  当是变量的时候使用script记录
[cpp]view plaincopy

  •   typedefstruct {
  •   ngx_array_t                *lengths;
  •   ngx_array_t                *values;
  •   } ngx_http_log_script_t;
  4、不管是error_log还是access_log,nginx都是通过保存文件句柄来进行快速写日志文件的。但是因为access_log支持根据变量指令路径,如果按照request或者ip来分隔不同的access日志,那么可想而至,若还按照保存文件句柄的方式来写日志文件,会造成系统fd的大量占用。nginx在此进行了优化:
  1)如果用常量指定acess日志路径:
  access_log  logs/access.log  main;
  那么和error_log一样,将文件路径名称放到cycle->open_files中去,这是个list,在路径加入这个list的时候会进行除重操作的。在所有的模块初始化完毕,会依次打开这些文件路径,获取到fd,以备打印日志使用。
  打印日志的时候调用函数:ngx_write_fd
  2)如果用变量指定acess日志路径:
  使用script标记日志文件为变量文件名的。
  打印日志的时候调用函数:ngx_http_log_script_write
  在这个函数里,体现出了对缓存fd的管理。这些和指令open_file_log_cache的配置是息息相关的(后面会详细介绍)。
  打日志的函数:
[cpp]view plaincopy

  •   staticvoid
  •   ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf,
  •   size_t len)
  •   {
  •   u_char     *name;
  •   time_t      now;
  •   ssize_t     n;
  •   ngx_err_t   err;
  •   if (log->script == NULL) {
  •   name = log->file->name.data;
  •   n = ngx_write_fd(log->file->fd, buf, len);
  •   } else {
  •   name = NULL;
  •   n = ngx_http_log_script_write(r, log->script, &name, buf, len);
  •   }
  •   ......
  •   }
  5、说到缓存文件描述符,nginx有两个指令是管理缓存文件描述符的
  一个就是本文中说到的ngx_http_log_module模块的open_file_log_cache;
  一个是ngx_http_core_module模块的 open_file_cache;
  前者是只用来管理access变量日志文件。
  后者用来管理的就多了,包括:static,index,tryfiles,gzip,mp4,flv,看到了没,都是静态文件哦!
  这两个指令的handler都调用了函数 ngx_open_file_cache_init ,这就是用来管理缓存文件描述符的第一步:初始化
[cpp]view plaincopy

  •   ngx_open_file_cache_t *
  •   ngx_open_file_cache_init(ngx_pool_t *pool, ngx_uint_t max, time_t inactive)
  •   {
  •   ngx_pool_cleanup_t     *cln;
  •   ngx_open_file_cache_t  *cache;

  •   cache = ngx_palloc(pool,>
  •   if (cache == NULL) {
  •   return NULL;
  •   }
  •   ngx_rbtree_init(&cache->rbtree, &cache->sentinel,
  •   ngx_open_file_cache_rbtree_insert_value);
  •   ngx_queue_init(&cache->expire_queue);
  •   cache->current = 0;
  •   cache->max = max;
  •   cache->inactive = inactive;
  •   cln = ngx_pool_cleanup_add(pool, 0);
  •   if (cln == NULL) {
  •   return NULL;
  •   }
  •   cln->handler = ngx_open_file_cache_cleanup;
  •   cln->data = cache;
  •   return cache;
  •   }
  可以看到nginx管理缓存文件描述符,使用了红黑树和队列,这个后续还是作为一篇文章来叙述吧,涉及的内容有点多,本文还是以分析日志模块为主。
  6、说一下指令 open_file_log_cache
  1)nginx下默认这个指令的配置是:open_file_log_cache off;
  也就是说不对access变量日志文件的fd做缓存,每写一个文件就打开,然后写日志。那么这个文件fd什么时候关闭呢。
  这就涉及到nginx内存管理的cleanup了,cleanup可以注册,在内存池被销毁的时候,调用cleanup链表中各个cleanup的handler(详细可以去翻阅nginx内存池管理)
  而此时的文件fd就是在request完毕后,销毁内存池的时候,关闭fd。
  配置open_log_file_cache off; 时的运行代码如下:
  这是获取access变量文件fd的函数,返回值应该是access日志的fd。
[cpp]view plaincopy

  •   ngx_int_t
  •   ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name,
  •   ngx_open_file_info_t *of, ngx_pool_t *pool)
  •   {
  •   time_t                          now;
  •   uint32_t                        hash;
  •   ngx_int_t                       rc;
  •   ngx_file_info_t                 fi;
  •   ngx_pool_cleanup_t             *cln;
  •   ngx_cached_open_file_t         *file;
  •   ngx_pool_cleanup_file_t        *clnf;
  •   ngx_open_file_cache_cleanup_t  *ofcln;
  •   of->fd = NGX_INVALID_FILE;
  •   of->err = 0;
  •   //配置open_log_file_cache off的话,cache为空
  •   if (cache == NULL) {
  •   ......
  •   //添加一个cleanup

  •   cln = ngx_pool_cleanup_add(pool,>
  •   if (cln == NULL) {
  •   return NGX_ERROR;
  •   }
  •   //打开日志文件,获得fd rc = ngx_open_and_stat_file(name, of, pool->log);
  •   rc = ngx_open_and_stat_file(name, of, pool->log);
  •   //打开日志文件成功
  •   if (rc == NGX_OK && !of->is_dir) {
  •   //添加cleanup回调,在销毁内存池的时候调用ngx_pool_cleanup_file,函数内采用了close关闭fd
  •   cln->handler = ngx_pool_cleanup_file;
  •   clnf = cln->data;
  •   clnf->fd = of->fd;
  •   clnf->name = name->data;
  •   clnf->log = pool->log;
  •   }
  •   return rc;
  •   }
  •   ......
  •   }
  2)如果配置open_file_log_cache的话,支持四种参数:
  max = N [ inactive = time ] [ min_uses = N ] [ valid = time ]
  max  最大缓存文件描述符数量
  inactive 在多少时间内不活动,就会被删除
  min_uses 必须在 inactive时间内活动N次,才会被缓存
  valid 检查inactive的时间。
  具体的缓存文件fd的来龙去脉值得用一篇文章来详细描述,在这里就暂且不说了,希望最近有时间整理出来。
  转自:http://blog.csdn.net/fangru/article/details/9128699


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-634614-1-1.html 上篇帖子: 让 Vim 支持 nginx 语法(syntax) 格式化配置文件 下篇帖子: 配置nginx缓存存放在内存中
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表