nginx日志是在src/core/ngx_log.h|c中定义的, 是nginx日志相关的结构及操作方法的定义。下面看看日志类是什么情况:
1. 日志级别定义:
#define NGX_LOG_STDERR 0 #define NGX_LOG_EMERG 1 #define NGX_LOG_ALERT 2 #define NGX_LOG_CRIT 3 #define NGX_LOG_ERR 4 #define NGX_LOG_WARN 5 #define NGX_LOG_NOTICE 6 #define NGX_LOG_INFO 7 #define NGX_LOG_DEBUG 8
这里定义了9种日志级别, 分别看下宏的名字就知道具体的意思了。
后面有对应的日志级别定义:
static ngx_str_t err_levels[] = { ngx_null_string, ngx_string("emerg"), ngx_string("alert"), ngx_string("crit"), ngx_string("error"), ngx_string("warn"), ngx_string("notice"), ngx_string("info"), ngx_string("debug") };
2. 调试日志类型的定义:
#define NGX_LOG_DEBUG_CORE 0x010 #define NGX_LOG_DEBUG_ALLOC 0x020 #define NGX_LOG_DEBUG_MUTEX 0x040 #define NGX_LOG_DEBUG_EVENT 0x080 #define NGX_LOG_DEBUG_HTTP 0x100 #define NGX_LOG_DEBUG_MAIL 0x200 #define NGX_LOG_DEBUG_MYSQL 0x400
分别是核心模块调试日志, 分配日志调试, 互斥锁相关的日志调试, 事件日志调试, HTTP日志调试, MAIL日志调试, MYSQL日志调试。
后面有相应的日志调试级别的定义:
static const char *debug_levels[] = { "debug_core", "debug_alloc", "debug_mutex", "debug_event", "debug_http", "debug_mail", "debug_mysql" };
3. 下面看看ngx_log_t结构体:
typedef ngx_log_s ngx_log_t; struct ngx_log_s { ngx_uint_t log_level; //日志级别 ngx_open_file_t *file; //日志文件 ngx_atomic_uint_t connection; //原子单元链接 ngx_log_handler_pt handler; //日志处理器 void *data; //日志数据 /* * we declare "action" as "char *" because the actions are usually * the static strings and in the "u_char *" case we have to override * their types all the time */ char *action; // 操作 };
4. ngx_errlog_commands nginx错误日志命令集:
static char *ngx_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); // 错误日志处理器指针 static ngx_command_t ngx_errlog_commands[] = { {ngx_string("error_log"), // 对应于配置中的error_log指令, 日志的文件位置 NGX_MAIN_CONF|NGX_CONF_1MORE, // 属于顶级配置指令 ngx_error_log, // 即上面的错误日志处理器 0, 0, NULL}, ngx_null_command };5. ngx_errlog_module_ctx 错误日志模块的上下文
前面也提到过nginx每个模块都有自己的上下文, 该对象就是nginx日志的上下文, 日志模块也属于核心模块。
static ngx_core_module_t ngx_errlog_module_ctx = { ngx_string("errlog"), // 核心模块的名称 NULL, // 创建配置的方法指针, 这里为NULL NULL // 初始化配置的方法指针, 这里也为NULL };
ngx_module_t ngx_errlog_module = { NGX_MODULE_V1, // 这里和一般的模块都一样, 使用这个宏一次性初始化前面6个域的内容。 &ngx_errlog_module_ctx, // 错误日志的上下文 ngx_errlog_commands, // 错误日志相关的命令集 NGX_CORE_MODULE, // 模块类型, 错误日志为核心模块 NULL, // 初始化master进程的钩子 NULL, // 初始化模块的钩子 NULL, // 初始化进程的钩子 NULL, // 初始化线程的钩子 NULL, // 退出线程的钩子 NULL, // 退出进程的钩子 NULL, // 退出master进程的钩子 NGX_MODULE_V1_PADDING // 使用这个宏一次性初始化最后的8个域的内容 };7. 错误日志中定义的几个变量
static ngx_log_t ngx_log; // 错误日志对象
static ngx_open_file_t ngx_log_file; // 错误日志文件
ngx_uint_t ngx_use_stderr = 1; //是否启用标准错误
其中ngx_log_t上面介绍过, 包含了日志的级别, 日志文件, 原子粒度, 日志处理器, 日志数据, 以及行为等信息的一个对象。
下面介绍下ngx_open_file_t结构体:
typedef ngx_open_file_s ngx_open_file_t; struct ngx_open_file_s { ngx_fd_t fd; //标准IO文件描述符 ngx_str_t name; //文件名称 void (*flush)(ngx_open_file_t *file, ngx_log_t *log); // 刷新的句柄 void *data; // 数据 };
前面在main函数的介绍中, 我们看到日志的初始化是从这个函数调用开始的, 下面就分析下这个函数的作用和实现。
ngx_log_t * ngx_log_init(u_char *prefix) { u_char *p, *name; size_t nlen, plen; ngx_log.file = &ngx_log_file; ngx_log.log_level = NGX_LOG_NOTICE; name = (u_char *) NGX_ERROR_LOG_PATH; // 这个貌似是安装后才有的宏定义?? 对应于日志文件的位置 /* * we use ngx_strlen() here since BCC warns about * condition is always false and unreachable code */ nlen = ngx_strlen(name); if (nlen == 0) { ngx_log_file.fd = ngx_stderr; return &ngx_log; } p = NULL; #if (NGX_WIN32) if (name[1] != ':') { #else if (name[0] != '/') { #endif if (prefix) { plen = ngx_strlen(prefix); } else { #ifdef NGX_PREFIX prefix = (u_char *) NGX_PREFIX; plen = ngx_strlen(prefix); #else plen = 0; #endif } if (plen) { name = malloc(plen + nlen + 2); if (name == NULL) { return NULL; } p = ngx_cpymem(name, prefix, plen); if (!ngx_path_separator(*(p - 1))) { *p++ = '/'; } ngx_cpystrn(p, (u_char *) NGX_ERROR_LOG_PATH, nlen + 1); p = name; } } ngx_log_file.fd = ngx_open_file(name, NGX_FILE_APPEND, NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS); // 以附加模式打开日志文件, 若文件不存在创建它, 并设置默认的访问权限 if (ngx_log_file.fd == NGX_INVALID_FILE) { ngx_log_stderr(ngx_errno, "[alert] could not open error log file: " ngx_open_file_n " \"%s\" failed", name); #if (NGX_WIN32) ngx_event_log(ngx_errno, "could not open error log file: " ngx_open_file_n " \"%s\" failed", name); #endif ngx_log_file.fd = ngx_stderr; } if (p) { ngx_free(p); } return &ngx_log; // 返回日志对象。 }
以上方法初始化日志对象ngx_log, 设置默认的日志级别为NGX_LOG_NOTICE, 并为其file对象设置文件句柄, 如果没有设置日志记录的文件位置, 那么使用默认的标准错误输出日志。
9. ngx_log_create(ngx_cycle_t *cycle, ngx_str_t *name) : 在内存池中统一进行管理的日志操作文件的创建。
我们知道nginx的内存池用于统一管理内存和资源的使用和撤销, 除了系统开始的日志外, 其他的地方需要使用到的文件打开和关闭, 都统一在内存池中管理, 这样可以避免不断的创建和撤销资源, 以及给编程带来比较大的麻烦。
该函数就是为了给其他地方使用到日志对象的提供一个接口, 通过内存池管理日志对象, 这里是日志对象的创建。
ngx_log_t * ngx_log_create(ngx_cycle_t *cycle, ngx_str_t *name) { ngx_log_t *log; log = ngx_pcalloc(cycle->pool, sizeof(ngx_log_t)); // 在内存池中创建一块内存来存储日志对象 if (log == NULL) { return NULL; } log->file = ngx_conf_open_file(cycle, name); // 通过内存池统一管理打开文件, 根据文件的名称来索引。 if (log->file == NULL) { return NULL; } return log; }
10. ngx_log_error_core() 函数
鉴于不同的操作系统, 该函数的实现方式不同, 这里只讲其中一种实现.
该方法是有可变数量的参数, 前面几个参数为ngx_uint_t level, ngx_log_t *log, ngx_err_t err,const char *fmt, 分别是错误级别, 日志对象, 错误对象, 日志记录的格式。
后面可变的参数部分为格式里边的变量的对应值参数。
#if (NGX_HAVE_VARIADIC_MACROS) void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, ...) #else void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, const char *fmt, va_list args) #endif { #if (NGX_HAVE_VARIADIC_MACROS) va_list args; #endif u_char *p, *last, *msg; u_char errstr[NGX_MAX_ERROR_STR]; if (log->file->fd == NGX_INVALID_FILE) { return; } last = errstr + NGX_MAX_ERROR_STR; ngx_memcpy(errstr, ngx_cached_err_log_time.data, ngx_cached_err_log_time.len); p = errstr + ngx_cached_err_log_time.len; p = ngx_slprintf(p, last, " [%V] ", &err_levels[level]); /* pid#tid */ p = ngx_slprintf(p, last, "%P#" NGX_TID_T_FMT ": ", ngx_log_pid, ngx_log_tid); if (log->connection) { p = ngx_slprintf(p, last, "*%uA ", log->connection); } msg = p; #if (NGX_HAVE_VARIADIC_MACROS) va_start(args, fmt); p = ngx_vslprintf(p, last, fmt, args); va_end(args); #else p = ngx_vslprintf(p, last, fmt, args); #endif if (err) { p = ngx_log_errno(p, last, err); } if (level != NGX_LOG_DEBUG && log->handler) { p = log->handler(log, p, last - p); } if (p > last - NGX_LINEFEED_SIZE) { p = last - NGX_LINEFEED_SIZE; } ngx_linefeed(p); (void) ngx_write_fd(log->file->fd, errstr, p - errstr); if (!ngx_use_stderr || level > NGX_LOG_WARN || log->file->fd == ngx_stderr) { return; } msg -= (7 + err_levels[level].len + 3); (void) ngx_sprintf(msg, "nginx: [%V] ", &err_levels[level]); (void) ngx_write_console(ngx_stderr, msg, p - msg); }
11. 剩下的一些方法基本上是对日志记录的一种包装, 以及设置日志级别之类的, 方法都比较简单, 这里不详细一个个介绍。
顺便继续刚才提到的内存池的资源管理, 补充一句话, 这里看到有ngx_log_create,来创建日志对象的方法, 却没有关闭的句柄, 这是因为, 内存池会统一进行销毁处理, 在处理完一个请求的时候, 会自动清理掉。
顺便也举个使用这个方法的模块的例子:
src/http/ngx_http_core_module.c: clcf->error_log = ngx_log_create(cf->cycle, &name);