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 loggerseverity=string
- man 1 loggertag=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 thecondition
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 log 和 error log 直接发送至 syslogd/rsyslogd 等日志收集程序的争论由来己久 (好几年了)。这个特性 带来的好处也是显而易见的:可以方便的对 Nginx 日志进行收集以便集中处理,特别是大 数据大行其道的今天,这个功能可以带来无限的便历。
能找到的最早 (2012年) 关于这个功能的争论来自这里 和这里,有人提议 将一个实现了日志写往类 syslog 软件的 Nginx syslog patch 和 Nginx 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_log
和 access_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 log 和 access 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_log
和access_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
不要轻轻地离开我,请留下点什么...