Nginx日志管理与异常

我眼里的日志系统:

发生异常---->将异常信息写入日志文件------>退出

Nginx实现的日志系统

image-20240527210455690

专门的日志数据结构

struct ngx_log_s {
    ngx_uint_t           log_level;            // 日志级别,用于控制日志的详细程度(例如,DEBUG、INFO、ERROR 等)
    ngx_open_file_t     *file;                 // 指向打开文件的指针,表示日志输出的目标文件
    ngx_atomic_uint_t    connection;           // 记录当前连接数,使用原子类型以保证多线程环境下的安全性
    time_t               disk_full_time;       // 上次磁盘满时间,用于记录磁盘满的时间戳
    ngx_log_handler_pt   handler;              // 自定义日志处理函数指针,用于特殊日志处理
    void                *data;                 // 任意类型的指针,可以存放与日志相关的附加数据
    ngx_log_writer_pt    writer;               // 自定义日志写入函数指针,用于特殊日志写入方式
    void                *wdata;                // 任意类型的指针,可以存放与日志写入相关的附加数据
    /*
     * 我们将 "action" 声明为 "char *" 类型,因为动作通常是静态字符串,
     * 如果使用 "u_char *" 类型,我们需要经常覆盖其类型。
     */
    char                *action;               // 描述当前操作的字符串(例如,"reading client request")
    ngx_log_t           *next;                 // 指向下一个日志对象的指针,用于支持链式日志记录
};
Nginx的文件描述
struct ngx_open_file_s {
    ngx_fd_t              fd;       // 文件描述符
    ngx_str_t             name;     // 文件名

    void                (*flush)(ngx_open_file_t *file, ngx_log_t *log);  // 刷新文件缓冲区函数指针
    void                 *data;     // 指向用户自定义数据的指针
};

将错误日志进行分类—>等级数组

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

初始化日志文件

ngx_log_t * ngx_log_init(u_char *prefix, u_char *error_log)
{
    u_char  *p, *name;
    size_t   nlen, plen;

    // 初始化全局的 ngx_log 结构
    ngx_log.file = &ngx_log_file;
    ngx_log.log_level = NGX_LOG_NOTICE;  // 将默认日志级别设置为 NOTICE

    // 如果未提供错误日志路径,则使用默认错误日志路径
    if (error_log == NULL) {
        error_log = (u_char *) NGX_ERROR_LOG_PATH;
    }

    name = error_log;  // 将提供的错误日志路径赋给 name
    nlen = ngx_strlen(name);  // 获取错误日志路径的长度

    // 如果错误日志路径为空,则使用标准错误输出(stderr)
    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, error_log, nlen + 1);  // 将错误日志路径拷贝到路径末尾

            p = name;
        }
    }
    // 打开错误日志文件
    ngx_log_file.fd = ngx_open_file(name, NGX_FILE_APPEND,
                                    NGX_FILE_CREATE_OR_OPEN,
                                    NGX_FILE_DEFAULT_ACCESS);

    // 如果打开文件失败,则输出错误日志到标准错误输出(stderr)
    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;
}

设置日志文件配置项

char *
ngx_log_set_log(ngx_conf_t *cf, ngx_log_t **head)
{
    ngx_log_t          *new_log;           // 新日志结构体
    ngx_str_t          *value, name;      // 配置项值和名称
    ngx_syslog_peer_t  *peer;            // syslog 配置结构体

    // 如果当前日志链表已存在且日志级别为0,表示已存在一个未指定级别的日志结构
    if (*head != NULL && (*head)->log_level == 0) {
        new_log = *head;  // 直接使用现有的日志结构

    } else {
        // 否则,创建一个新的日志结构
        new_log = ngx_pcalloc(cf->pool, sizeof(ngx_log_t));
        if (new_log == NULL) {
            return NGX_CONF_ERROR;
        }

        // 如果当前日志链表为空,则将新日志结构设置为链表的头部
        if (*head == NULL) {
            *head = new_log;
        }
    }

    // 获取配置项参数值
    value = cf->args->elts;

    // 如果配置为输出到标准错误输出
    if (ngx_strcmp(value[1].data, "stderr") == 0) {
        ngx_str_null(&name);  // 清空日志文件名
        cf->cycle->log_use_stderr = 1;  // 标记使用标准错误输出

        // 打开标准错误输出文件
        new_log->file = ngx_conf_open_file(cf->cycle, &name);
        if (new_log->file == NULL) {
            return NGX_CONF_ERROR;
        }

    // 如果配置为输出到内存
    } else if (ngx_strncmp(value[1].data, "memory:", 7) == 0) {

#if (NGX_DEBUG)
        size_t                 size, needed;  // 缓冲区大小及所需大小
        ngx_pool_cleanup_t    *cln;          // 清理回调结构体
        ngx_log_memory_buf_t  *buf;         // 内存日志缓冲区

        // 去除"memory:"前缀
        value[1].len -= 7;
        value[1].data += 7;

        // 计算所需的缓冲区大小
        needed = sizeof("MEMLOG  :" NGX_LINEFEED)
                 + cf->conf_file->file.name.len
                 + NGX_SIZE_T_LEN
                 + NGX_INT_T_LEN
                 + NGX_MAX_ERROR_STR;

        // 解析配置的缓冲区大小
        size = ngx_parse_size(&value[1]);

        // 如果解析失败或缓冲区太小,则返回错误
        if (size == (size_t) NGX_ERROR || size < needed) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid buffer size \"%V\"", &value[1]);
            return NGX_CONF_ERROR;
        }

        // 分配内存日志缓冲区
        buf = ngx_pcalloc(cf->pool, sizeof(ngx_log_memory_buf_t));
        if (buf == NULL) {
            return NGX_CONF_ERROR;
        }

        // 分配缓冲区内存
        buf->start = ngx_pnalloc(cf->pool, size);
        if (buf->start == NULL) {
            return NGX_CONF_ERROR;
        }

        buf->end = buf->start + size;

        // 初始化缓冲区,记录日志文件名及行号
        buf->pos = ngx_slprintf(buf->start, buf->end, "MEMLOG %uz %V:%ui%N",
                                size, &cf->conf_file->file.name,
                                cf->conf_file->line);

        // 填充剩余空间为 ' '
        ngx_memset(buf->pos, ' ', buf->end - buf->pos);

        // 设置清理回调,用于释放内存日志缓冲区
        cln = ngx_pool_cleanup_add(cf->pool, 0);
        if (cln == NULL) {
            return NGX_CONF_ERROR;
        }

        cln->data = new_log;
        cln->handler = ngx_log_memory_cleanup;

        // 设置日志写入函数和写入数据
        new_log->writer = ngx_log_memory_writer;
        new_log->wdata = buf;

#else
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "nginx was built without debug support");
        return NGX_CONF_ERROR;
#endif

    // 如果配置为输出到syslog
    } else if (ngx_strncmp(value[1].data, "syslog:", 7) == 0) {
        // 分配syslog配置结构体
        peer = ngx_pcalloc(cf->pool, sizeof(ngx_syslog_peer_t));
        if (peer == NULL) {
            return NGX_CONF_ERROR;
        }

        // 处理syslog配置
        if (ngx_syslog_process_conf(cf, peer) != NGX_CONF_OK) {
            return NGX_CONF_ERROR;
        }

        // 设置日志写入函数和写入数据
        new_log->writer = ngx_syslog_writer;
        new_log->wdata = peer;

    // 其他情况,输出到指定文件
    } else {
        // 打开指定文件
        new_log->file = ngx_conf_open_file(cf->cycle, &value[1]);
        if (new_log->file == NULL) {
            return NGX_CONF_ERROR;
        }
    }

    // 设置日志级别
    if (ngx_log_set_levels(cf, new_log) != NGX_CONF_OK) {
        return NGX_CONF_ERROR;
    }

    // 如果日志链表不为空,则将新日志插入到链表中
    if (*head != new_log) {
        ngx_log_insert(*head, new_log);
    }

    return NGX_CONF_OK;
}

格式化错误信息

void ngx_cdecl
ngx_log_abort(ngx_err_t err, const char *fmt, ...)
{
    u_char   *p;
    va_list   args;
    u_char    errstr[NGX_MAX_CONF_ERRSTR];

    //使用va_start宏开始对可变参数的处理,并调用ngx_vsnprintf函数将格式化字符串fmt和可变参数args写入errstr数组中,生成格式化的错误信息
    va_start(args, fmt);
    p = ngx_vsnprintf(errstr, sizeof(errstr) - 1, fmt, args);
    va_end(args);
   //将生成的错误信息以NGX_LOG_ALERT级别写入到nginx的日志中
    ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
                  "%*s", p - errstr, errstr);
}
错误码err转换为对应的错误描述
u_char *
ngx_log_errno(u_char *buf, u_char *last, ngx_err_t err)
{
    //首先函数进行了边界检查,确保在向缓冲区写入前还有足够的空间。如果缓冲区的剩余空间不足50个字节,就会留出一个空间用于放置错误码,然后在缓冲区的末尾写入三个点号"...",以表示截断了部分错误信息。
    if (buf > last - 50) {
        /* leave a space for an error code */

        buf = last - 50;
        *buf++ = '.';
        *buf++ = '.';
        *buf++ = '.';
    }
#if (NGX_WIN32)
    buf = ngx_slprintf(buf, last, ((unsigned) err < 0x80000000)
                                       ? " (%d: " : " (%Xd: ", err);
#else
    buf = ngx_slprintf(buf, last, " (%d: ", err);
#endif
//调用ngx_strerror函数将错误码转换为对应的错误描述,并将描述信息追加到缓冲区中
    buf = ngx_strerror(err, buf, last - buf);

    if (buf < last) {
        *buf++ = ')';
    }

    return buf;
}

异常信息首先输入到缓冲区

u_char * ngx_cdecl
ngx_slprintf(u_char *buf, u_char *last, const char *fmt, ...)
{
    u_char   *p;
    va_list   args;

    va_start(args, fmt);
    p = ngx_vslprintf(buf, last, fmt, args);
    va_end(args);

    return p;
}

打开/新建日志文件并插入日志链表

ngx_int_t
ngx_log_open_default(ngx_cycle_t *cycle)
{
    ngx_log_t  *log;

    if (ngx_log_get_file_log(&cycle->new_log) != NULL) {
        return NGX_OK;
    }

    if (cycle->new_log.log_level != 0) {
        /* there are some error logs, but no files */

        log = ngx_pcalloc(cycle->pool, sizeof(ngx_log_t));
        if (log == NULL) {
            return NGX_ERROR;
        }

    } else {
        /* no error logs at all */
        log = &cycle->new_log;
    }

    log->log_level = NGX_LOG_ERR;

    log->file = ngx_conf_open_file(cycle, &cycle->error_log);
    if (log->file == NULL) {
        return NGX_ERROR;
    }

    if (log != &cycle->new_log) {
        ngx_log_insert(&cycle->new_log, log);
    }

    return NGX_OK;
}

获取日志文件

ngx_log_t *
ngx_log_get_file_log(ngx_log_t *head)
{
    ngx_log_t  *log;

    for (log = head; log; log = log->next) {
        if (log->file != NULL) {
            return log;
        }
    }

    return NULL;
}

刷入磁盘

/*
 * 将文本写入标准错误流
 */
static ngx_inline void
ngx_write_stderr(char *text)
{
    (void) ngx_write_fd(ngx_stderr, text, ngx_strlen(text));
}

/*
 * 将文本写入标准输出流
 */
static ngx_inline void
ngx_write_stdout(char *text)
{
    (void) ngx_write_fd(ngx_stdout, text, ngx_strlen(text));
}
/*
 * 在指定文件描述符上写入数据
 * 参数:
 *     - fd:文件描述符
 *     - buf:待写入数据的缓冲区指针
 *     - n:待写入数据的字节数
 * 返回值:
 *     成功:返回写入的字节数
 *     失败:返回-1,并设置errno为对应的错误码
 */
static ngx_inline ssize_t
ngx_write_fd(ngx_fd_t fd, void *buf, size_t n)
{
    return write(fd, buf, n);
}

输入到控制台的功能

void ngx_cdecl
ngx_log_stderr(ngx_err_t err, const char *fmt, ...)
{
    u_char   *p, *last;   // 指向错误信息字符串的指针
    va_list   args;       // 可变参数列表
    u_char    errstr[NGX_MAX_ERROR_STR];  // 错误信息缓冲区

    last = errstr + NGX_MAX_ERROR_STR;  // 缓冲区的末尾位置
    p = ngx_cpymem(errstr, "nginx: ", 7);  // 将固定的前缀拷贝到缓冲区
    va_start(args, fmt);  // 初始化可变参数列表
    p = ngx_vslprintf(p, last, fmt, args);  // 将格式化字符串和参数格式化到缓冲区
    va_end(args);  // 结束可变参数列表

    // 如果有错误码,将错误码添加到错误信息中
    if (err) {
        p = ngx_log_errno(p, last, err);
    }

    // 如果错误信息超出缓冲区末尾,截断字符串
    if (p > last - NGX_LINEFEED_SIZE) {
        p = last - NGX_LINEFEED_SIZE;
    }

    ngx_linefeed(p);  // 添加换行符

    // 将错误信息输出到标准错误流
    (void) ngx_write_console(ngx_stderr, errstr, p - errstr);
}

Nginx有关错误宏定义

#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H

#define	EPERM		 1	/* Operation not permitted */
#define	ENOENT		 2	/* No such file or directory */
#define	ESRCH		 3	/* No such process */
#define	EINTR		 4	/* Interrupted system call */
#define	EIO		 5	/* I/O error */
#define	ENXIO		 6	/* No such device or address */
#define	E2BIG		 7	/* Argument list too long */
#define	ENOEXEC		 8	/* Exec format error */
#define	EBADF		 9	/* Bad file number */
#define	ECHILD		10	/* No child processes */
#define	EAGAIN		11	/* Try again */
#define	ENOMEM		12	/* Out of memory */
#define	EACCES		13	/* Permission denied */
#define	EFAULT		14	/* Bad address */
#define	ENOTBLK		15	/* Block device required */
#define	EBUSY		16	/* Device or resource busy */
#define	EEXIST		17	/* File exists */
#define	EXDEV		18	/* Cross-device link */
#define	ENODEV		19	/* No such device */
#define	ENOTDIR		20	/* Not a directory */
#define	EISDIR		21	/* Is a directory */
#define	EINVAL		22	/* Invalid argument */
#define	ENFILE		23	/* File table overflow */
#define	EMFILE		24	/* Too many open files */
#define	ENOTTY		25	/* Not a typewriter */
#define	ETXTBSY		26	/* Text file busy */
#define	EFBIG		27	/* File too large */
#define	ENOSPC		28	/* No space left on device */
#define	ESPIPE		29	/* Illegal seek */
#define	EROFS		30	/* Read-only file system */
#define	EMLINK		31	/* Too many links */
#define	EPIPE		32	/* Broken pipe */
#define	EDOM		33	/* Math argument out of domain of func */
#define	ERANGE		34	/* Math result not representable */

#endif

Nginx处理错误字符串

选择不同的方法来获取错误信息并将其复制到指定的缓冲区中。

  1. 第一部分使用了strerrordesc_np函数,该函数在glibc 2.32中引入。它是异步信号安全的,可以直接使用它来获取系统错误信息,而无需复制错误消息。如果系统支持这个函数,就直接调用它获取错误信息。如果不支持,则使用预定义的ngx_unknown_error
  2. 第二部分处理了不支持strerrordesc_np的情况,主要是为了兼容之前的版本。它首先初始化了一个错误消息列表,存储了系统中可能的错误消息。然后通过strerror函数获取每个错误码对应的错误消息,并将其存储在动态分配的内存中。这样,在需要获取错误信息时,就可以根据错误码直接在列表中查找对应的错误消息了。
static ngx_str_t   ngx_unknown_error = ngx_string("Unknown error");

#if (NGX_HAVE_STRERRORDESC_NP)

/*
 * strerrordesc_np()函数是在glibc 2.32中引入的,是异步信号安全的。
 * 这使得可以直接使用它,而不需要复制错误消息。
 */

u_char *
ngx_strerror(ngx_err_t err, u_char *errstr, size_t size)
{
    size_t       len;
    const char  *msg;

    msg = strerrordesc_np(err);

    if (msg == NULL) {
        msg = (char *) ngx_unknown_error.data;
        len = ngx_unknown_error.len;

    } else {
        len = ngx_strlen(msg);
    }

    size = ngx_min(size, len);

    return ngx_cpymem(errstr, msg, size);
}


ngx_int_t
ngx_strerror_init(void)
{
    return NGX_OK;
}

#else

/*
 * strerror()消息被复制,原因是:
 *
 * 1) strerror()和strerror_r()函数不是异步信号安全的,
 *    因此不能在信号处理程序中使用;
 *
 * 2) 可以使用直接的sys_errlist[]数组来替代这些函数,
 *    但Linux链接器会警告其使用:
 *
 * warning: `sys_errlist' is deprecated; use `strerror' or `strerror_r' instead
 * warning: `sys_nerr' is deprecated; use `strerror' or `strerror_r' instead
 *
 *    这会导致错误的bug报告。
 */

static ngx_str_t  *ngx_sys_errlist;
static ngx_err_t   ngx_first_error;
static ngx_err_t   ngx_last_error;

u_char *
ngx_strerror(ngx_err_t err, u_char *errstr, size_t size)
{
    ngx_str_t  *msg;

    if (err >= ngx_first_error && err < ngx_last_error) {
        msg = &ngx_sys_errlist[err - ngx_first_error];

    } else {
        msg = &ngx_unknown_error;
    }

    size = ngx_min(size, msg->len);

    return ngx_cpymem(errstr, msg->data, size);
}

ngx_int_t
ngx_strerror_init(void)
{
    char       *msg;
    u_char     *p;
    size_t      len;
    ngx_err_t   err;

#if (NGX_SYS_NERR)
    ngx_first_error = 0;
    ngx_last_error = NGX_SYS_NERR;

#elif (EPERM > 1000 && EPERM < 0x7fffffff - 1000)

    /*
     * 如果错误数未知,并且EPERM错误代码有很大但合理的值,
     * 则根据strerror()返回的错误消息从EPERM开始猜测可能的错误代码。
     * 特别是,这包括GNU/Hurd,其错误从0x40000001开始。
     */

    for (err = EPERM; err > EPERM - 1000; err--) {
        ngx_set_errno(0);
        msg = strerror(err);

        if (errno == EINVAL
            || msg == NULL
            || strncmp(msg, "Unknown error", 13) == 0)
        {
            continue;
        }

        ngx_first_error = err;
    }

    for (err = EPERM; err < EPERM + 1000; err++) {
        ngx_set_errno(0);
        msg = strerror(err);

        if (errno == EINVAL
            || msg == NULL
            || strncmp(msg, "Unknown error", 13) == 0)
        {
            continue;
        }

        ngx_last_error = err + 1;
    }

#else

    /*
     * 如果错误数未知,则根据strerror()返回的错误消息猜测错误数。
     */

    ngx_first_error = 0;

    for (err = 0; err < 1000; err++) {
        ngx_set_errno(0);
        msg = strerror(err);

        if (errno == EINVAL
            || msg == NULL
            || strncmp(msg, "Unknown error", 13) == 0)
        {
            continue;
        }

        ngx_last_error = err + 1;
    }

#endif

    /*
     * ngx_strerror()在此阶段尚不准备工作,因此使用malloc(),
     * 并使用strerror()记录可能的错误。
     */

    len = (ngx_last_error - ngx_first_error) * sizeof(ngx_str_t);

    ngx_sys_errlist = malloc(len);
    if (ngx_sys_errlist == NULL) {
        goto failed;
    }

    for (err = ngx_first_error; err < ngx_last_error; err++) {
        msg = strerror(err);

        if (msg == NULL) {
            ngx_sys_errlist[err - ngx_first_error] = ngx_unknown_error;
            continue;
        }

        len = ngx_strlen(msg);

        p = malloc(len);
        if (p == NULL) {
            goto failed;
        }

        ngx_memcpy(p, msg, len);
        ngx_sys_errlist[err - ngx_first_error].len = len;
        ngx_sys_errlist[err - ngx_first_error].data = p;
    }

    return NGX_OK;

failed:

    err = errno;
    ngx_log_stderr(0, "malloc(%uz) failed (%d: %s)", len, err, strerror(err));

    return NGX_ERROR;
}

#endif

总结

  1. 封装专门的日志和文件数据结构

  2. 分类异常信息

  3. 先读入缓冲区后刷入磁盘

  4. 日志链表设计以及日志配置设计

  5. 格式化输入输出

  6. 刷入磁盘
    or; err < ngx_last_error; err++) {
    msg = strerror(err);

     if (msg == NULL) {
         ngx_sys_errlist[err - ngx_first_error] = ngx_unknown_error;
         continue;
     }
    
     len = ngx_strlen(msg);
    
     p = malloc(len);
     if (p == NULL) {
         goto failed;
     }
    
     ngx_memcpy(p, msg, len);
     ngx_sys_errlist[err - ngx_first_error].len = len;
     ngx_sys_errlist[err - ngx_first_error].data = p;
    

    }

    return NGX_OK;

failed:

err = errno;
ngx_log_stderr(0, "malloc(%uz) failed (%d: %s)", len, err, strerror(err));

return NGX_ERROR;

}

#endif


## 总结

1. 封装专门的日志和文件数据结构
2. 分类异常信息
3. 先读入缓冲区后刷入磁盘
4. 日志链表设计以及日志配置设计
5. 格式化输入输出
6. 刷入磁盘
  • 31
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值