Windows XP Windows 7 Windows 2003 Windows Vista Windows教程綜合 Linux 系統教程
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟件教程

Nginx的error

日期:2017/2/8 10:10:24      編輯:關於服務器

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。
 
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
 };
 

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,如下:

static char *
 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;
         }
     }
 
    。。。。。。
 }
 

那為什麼不把兩個指令合並到一起呢。

首先看一下模塊注冊順序:

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有三個指令:

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對應關系),即下面這個數據結構:
 
typedef struct {
     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。
 
typedef struct {
     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

struct ngx_open_file_s {
     ngx_fd_t              fd;
     ngx_str_t             name;
 
    u_char               *buffer;
     u_char               *pos;
     u_char               *last;
 
};
 

當是變量的時候使用script記錄

typedef struct {
     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的配置是息息相關的(後面會詳細介紹)。

打日志的函數: 

static void
 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 ,這就是用來管理緩存文件描述符的第一步:初始化

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, sizeof(ngx_open_file_cache_t));
     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。

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, sizeof(ngx_pool_cleanup_file_t));
         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的來龍去脈值得用一篇文章來詳細描述,在這裡就暫且不說了,希望最近有時間整理出來。

The End

Copyright © Windows教程網 All Rights Reserved