本文目录

Nginx 社区版从 1.7.1 开始,支持将 "access log" 和 "error log" 通过网络发送给 syslog/rsyslog 等日志存储服务的功能。

用法

配置指令语法如下:

access_log syslog:server=address[,parameter=valud] [format [if=condition]];
error_log file | stderr | syslog:server=address[,parameter=value] ...;
  • Logging to syslog can be configured by specifying the "syslog:" prefix in the first parameter.

    • server=address - syslog 服务器地址
    • facility=string - man 1 logger
    • severity=string - man 1 logger
    • tag=string - man 1 logger
  • If the format is not specified then the predefined "combined" format is used.

  • The if parameter (1.7.0) enables conditional logging. A request will not be logged if the condition evaluates to "0" or an empty string.

access_log 指令的文档摘录示例如下:

access_log syslog:server=192.168.1.1;
access_log syslog:server=unix:/var/log/nginx.sock
access_log "syslog:server=[2001:db8::1]:12345,facility=local7,tag=nginx,
            severity=info" combined;

syslog

  • rsyslog/syslog-ng 都支持基于 TCP 的可靠日志传输 (syslogd 只支持 UDP 传输);
  • syslog-ng 支持通过 Pipe 传递日志数据;
  • Nginx 实现了基于 UDP 传输的 syslog 协议客户端逻辑;
  • The Quasi-standard BSD syslog protocol specified in RFC 3164:

    • /* TODO: 补充 syslog 协议介绍 */

历史

Nginx 是否该像 Apache 一样 (mod_syslog) 支持将 access logerror log 直接发送至 syslogd/rsyslogd 等日志收集程序的争论由来己久 (好几年了)。这个特性 带来的好处也是显而易见的:可以方便的对 Nginx 日志进行收集以便集中处理,特别是大 数据大行其道的今天,这个功能可以带来无限的便历。

能找到的最早 (2012年) 关于这个功能的争论来自这里这里,有人提议 将一个实现了日志写往类 syslog 软件的 Nginx syslog patchNginx udplog module 合 并入主干代码。争论两方的意见总结如下:

支持方:

  • 即然 syslog(3) 函数是阻塞操作,会影响建立在非阻塞事件驱动机制基础上的 Nginx 的性能,那么可以重新实现这个函数为非阻塞形式;

  • 使用非阻塞 UDP 发送日志数据,接收端在同一主机上时,可靠性问题不大;

  • 使用专用线程调用阻塞的 syslog(3),Nginx 主线程可以使用 fifo 或者文件和该 线程通信;

  • 有些类 syslogd 实现的性能己经足够高了,比如 rsyslogd 可以达到 1M logs/s,并且 它已经是很多 Linux 发行版的默认日志收集程序了;

  • Nginx 向类 syslogd 传输日志完全可以作为非默认的选项提供,接下来系统管理员可以 评估风险并且做合适的决策;

  • 使用 UDP 实时传输日志时,即使类 syslogd 程序挂掉造成了日志丢失,Nginx 进程也不 会被阻塞。

  • syslog(3) 的阻塞特征不是理由!如果 /dev/log 满了带来的问题远比能不能让 Nginx 进程正常运行更严重,这时候你可能连系统都无法登陆系统了。

  • 现在 Nginx 将日志写到文件的方式也是有问题的,这种非网络阻塞操作也不适合 Nginx。 日志最好还是交给独立的线程处理,同时,异步缓冲模式的日志记录方法是大流量网络程 序的标准做法 (缓冲区满了的时候允许丢掉后续的日志数据,线程间的同步需要设计良好的 锁或者使用无锁队列等等)。

  • 还可以使用 pipe,一般系统的 pipe 允许 4K (PIPE_BUF) 的缓冲数据,对一般 日志来说足够了;

反对方 (Maxim Dounin):

  • 类 syslog 软件的主要问题就是它提供的接口是阻塞的,这时如果类 syslogd 进程不 工作了或者负载较高日志量较大的情况下无法及时处理时就会影响 Nginx 的吞吐。并且 它们的处理速度和普通文件比起来相对较慢;

总结:使用线程的话,增加了 Nginx 代码的复杂度;使用 UDP 的话,兼容性好 (毕竟 最原始的 syslogd 只支持这种网络传输方式),但即使只在本机进程间通信 (并且只会在内 核 buffer 间发生数据拷贝开销很小),在系统负载较高或者接收端处理速度较慢的情况下 依然会发生丢包;使用 TCP 的话,在上面的情况下会阻塞 Nginx 进程,即使使实现了非 阻塞 TCP 连接并且用 Nginx 进行事件管理时,会延长当前请求的处理时间 (日志没写完不 能认为当前请求已经完成);

最终,Nginx 还是选择了使用非阻塞 UDP 的方式实现了 syslogd 的支持:

ticket 409 revision 5702:777202558122

但是注意,这个特性还未被合并进稳定版。同时,由于 UDP 本身的特性和对 Nginx 本身 逻辑简洁性的考虑 (从下面的代码可以看到,Nginx 并不会对日志的发送成功或者失败做 检查),要做好日志可能丢失的心理准备。

实现

error_logaccess_log 配置指令解析函数碰到包含 "syslog:" 字样的参数时,初 始化 syslog 协议客户端结构 ngx_syslog_peer_t,随后调用函数 ngx_syslog_process_conf 解析 syslog 详细配置:

typdef struct {
    ngx_pool_t         *pool;
    ngx_uint_t          facility;
    ngx_uint_t          severity;
    ngx_str_t           tag;

    ngx_addr_t          server;
    ngx_connection_t    conn;
    ngx_uint_t          processing;
} ngx_syslog_peer_t;

char *
ngx_syslog_process_conf(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
{
    ...
    ngx_syslog_parse_args(cf, peer);
    ...
    peer->conn.fd = (ngx_socket_t) -1;

    return NGX_CONF_OK;
}

/* parser for `error_log`: ngx_log_set_log */
...
} else if (ngx_strncmp(value[1].data, "syslog:", 7) == 0) {
    peer = ngx_pcalloc(cf->pool, sizeof(ngx_syslog_peer_t));
    ...
    ngx_syslog_process_conf(cf, peer);
    ...
    new_log->writer = ngx_syslog_writer;
    new_log->wdata = peer;

}

/* parser for `access_log`: ngx_http_log_set_log */
...
if (ngx_strncmp(value[1].data, "syslog:", 7) == 0) {
    peer = ngx_pcalloc(cf->pool, sizeof(ngx_syslog_peer_t));
    ...
    ngx_syslog_process_conf(cf, peer);
    ...
    log->syslog_peer = peer;
    ...
}

关于日志的写入时机和写入时调用的函数 error logaccess log 是不同的:

  • error log 主要由 Nginx 核心模块和应用模块输出,用于表示运行状态信息,其内容 不能被用户定义;access log 用于展示用户请求信息,其格式可以由用户定义

  • error log 由函数 ngx_log_error_core 格式化后调用 ngx_syslog_writer 函数 (它为了添加 syslog 日志头会将日志内容进行一次内存拷贝) 传递给接收进程; access log 由函数 ngx_http_log_handler 分别调用 ngx_syslog_add_header 函数 增加 syslog 包头后,再设用 ngx_syslog_send 进行发送。

  • error_logaccess_log 在同一配置文件的作用域中可以被多次定义,但是后者 可以定义不同的日志内容,并且可以定义不同的日志输出目标。所以在 ngx_http_log_handler 没有选择直接调用 ngx_syslog_writer 函数。

下面分析一下 ngx_syslog_writer 函数的实现代码。

/* ngx_syslog_writer */
u_char      *p, msg[NGX_SYSLOG_MAX_STR];
ngx_uint_t   head_len;
ngx_syslog_peer_t *peer;
...
p = ngx_syslog_add_header(peer, msg);
head_len = p - msg;

len -= NGX_LINEFEED_SIZE;
...
p = ngx_snprintf(p, len, "%s", buf);

(void) ngx_syslog_send(peer, msg, p - msg);

对上述代码的补充说明:

  • ngx_syslog_add_header 函数用于添加 syslog 协议规定的日志头;

  • ngx_syslog_send 函数使用 UDP 协议发送日志数据给类 syslog 日志接收程序;

  • 发送给类 syslog 日志接收程序的日志并不需要添加换行符;

  • Nginx 并没有检查日志发送是否成功;

ngx_syslog_send 函数的逻辑也相当简单:如果还未创建 UDP “连接”,就调用 ngx_syslog_init_peer 函数完成创建;然后调用网络发送函数直接发送数据。

/* ngx_syslog_send */
if (peer->conn.fd == (ngx_socket_t) -1) {
    if (ngx_syslog_init_peer(peer) != NGX_OK) {
        return NGX_ERROR;
    }
}

if (ngx_send) {
    return ngx_send(&peer->conn, buf, len);

} else {
    return ngx_os_io.send(&peer->conn, buf, len);
}

/* ngx_syslog_init_peer */
fd = ngx_socket(peer->server.sockaddr->sa_family, SOCK_DGRAM, 0);
...
ngx_nonblocking(fd);
...
connect(fd, peer->server.sockaddr, peer->server.socklen);
...
cln = ngx_pool_cleanup_add(peer->pool, 0);
...
cln->data = peer;
cln->handler = ngx_syslog_cleanup;

peer->conn.fd = fd;

对上述代码的补充说明:

  • 对 UDP socket 为什么还能使用 connect 调用呢?看一下 manpage 中的解答

    If the socket `sockfd` is of type `SOCK_DGRAM` then `addr` is the
    address to which datagrams are sent by default, and the only address
    from which datagrams are received.
    
  • 这里创建的 socket 在全局内存池 (cycle->pool) 被回收时由 ngx_syslog_cleanup 函数关闭。

  • ngx_connection_t 这里只是对 UDP socket 的简单封状,并不参数事件调度。

Comments

不要轻轻地离开我,请留下点什么...

comments powered by Disqus

Published

Category

Nginx

Tags

Contact