mirror of
https://github.com/nginx/nginx.git
synced 2026-05-13 09:36:42 +00:00
Merge bcb409bfee into 319b4ea63f
This commit is contained in:
commit
e58800e60d
5 changed files with 769 additions and 20 deletions
|
|
@ -10,9 +10,12 @@
|
|||
|
||||
|
||||
#define NGX_SYSLOG_MAX_STR \
|
||||
NGX_MAX_ERROR_STR + sizeof("<255>Jan 01 00:00:00 ") - 1 \
|
||||
NGX_MAX_ERROR_STR + sizeof("<255>1 ") - 1 \
|
||||
+ sizeof("1970-09-28T12:00:00.000+06:00") - 1 + 1 /* space */ \
|
||||
+ (NGX_MAXHOSTNAMELEN - 1) + 1 /* space */ \
|
||||
+ 32 /* tag */ + 2 /* colon, space */
|
||||
+ 48 /* APP-NAME/TAG */ + 1 /* space */ \
|
||||
+ NGX_INT64_LEN /* PROCID */ + 1 /* space */ \
|
||||
+ 32 /* MSGID */ + sizeof(" - ") - 1
|
||||
|
||||
|
||||
static char *ngx_syslog_parse_args(ngx_conf_t *cf, ngx_syslog_peer_t *peer);
|
||||
|
|
@ -40,6 +43,8 @@ static ngx_event_t ngx_syslog_dummy_event;
|
|||
char *
|
||||
ngx_syslog_process_conf(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
|
||||
{
|
||||
u_char ch;
|
||||
ngx_uint_t j;
|
||||
ngx_pool_cleanup_t *cln;
|
||||
|
||||
peer->facility = NGX_CONF_UNSET_UINT;
|
||||
|
|
@ -49,6 +54,78 @@ ngx_syslog_process_conf(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
if (peer->tag.data != NULL) {
|
||||
|
||||
if (peer->rfc5424) {
|
||||
|
||||
/*
|
||||
* RFC 5424: APP-NAME is at most 48 printable US-ASCII
|
||||
* characters (0x21-0x7E; space and controls are excluded).
|
||||
*/
|
||||
for (j = 0; j < peer->tag.len; j++) {
|
||||
ch = peer->tag.data[j];
|
||||
|
||||
if (ch < '!' || ch > '~') {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"syslog \"tag\" must contain "
|
||||
"printable US-ASCII characters");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* RFC 3164: the TAG is a string of ABNF alphanumeric
|
||||
* characters that MUST NOT exceed 32 characters.
|
||||
*/
|
||||
if (peer->tag.len > 32) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"syslog tag length exceeds 32");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
for (j = 0; j < peer->tag.len; j++) {
|
||||
ch = ngx_tolower(peer->tag.data[j]);
|
||||
|
||||
if (ch < '0'
|
||||
|| (ch > '9' && ch < 'a' && ch != '_')
|
||||
|| ch > 'z')
|
||||
{
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"syslog \"tag\" only allows "
|
||||
"alphanumeric characters "
|
||||
"and underscore");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (peer->msgid.data != NULL) {
|
||||
|
||||
if (!peer->rfc5424) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"syslog \"msgid\" requires rfc=rfc5424");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* RFC 5424 s6.2.7: MSGID consists of printable US-ASCII
|
||||
* characters (0x21-0x7E; space and controls are excluded).
|
||||
*/
|
||||
for (j = 0; j < peer->msgid.len; j++) {
|
||||
ch = peer->msgid.data[j];
|
||||
|
||||
if (ch < '!' || ch > '~') {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"syslog \"msgid\" must contain "
|
||||
"printable US-ASCII characters");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (peer->server.sockaddr == NULL) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"no syslog server specified");
|
||||
|
|
@ -67,6 +144,10 @@ ngx_syslog_process_conf(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
|
|||
ngx_str_set(&peer->tag, "nginx");
|
||||
}
|
||||
|
||||
if (peer->msgid.data == NULL) {
|
||||
ngx_str_set(&peer->msgid, "-");
|
||||
}
|
||||
|
||||
peer->hostname = &cf->cycle->hostname;
|
||||
peer->logp = &cf->cycle->new_log;
|
||||
|
||||
|
|
@ -92,7 +173,7 @@ ngx_syslog_process_conf(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
|
|||
static char *
|
||||
ngx_syslog_parse_args(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
|
||||
{
|
||||
u_char *p, *comma, c;
|
||||
u_char *p, *comma;
|
||||
size_t len;
|
||||
ngx_str_t *value;
|
||||
ngx_url_t u;
|
||||
|
|
@ -188,30 +269,66 @@ ngx_syslog_parse_args(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
|
|||
}
|
||||
|
||||
/*
|
||||
* RFC 3164: the TAG is a string of ABNF alphanumeric characters
|
||||
* that MUST NOT exceed 32 characters.
|
||||
* Character set and maximum length depend on the syslog
|
||||
* protocol version (rfc= parameter) and are validated in
|
||||
* ngx_syslog_process_conf() once all parameters are known.
|
||||
* RFC 5424 APP-NAME allows up to 48 printable US-ASCII
|
||||
* characters; RFC 3164 TAG allows up to 32 alphanumeric
|
||||
* characters. Reject anything that exceeds the larger
|
||||
* of the two limits here so that the later check can rely
|
||||
* on the data fitting into the statically-sized buffers.
|
||||
*/
|
||||
if (len - 4 > 32) {
|
||||
if (len - 4 > 48) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"syslog tag length exceeds 32");
|
||||
"syslog tag length exceeds 48");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
for (i = 4; i < len; i++) {
|
||||
c = ngx_tolower(p[i]);
|
||||
|
||||
if (c < '0' || (c > '9' && c < 'a' && c != '_') || c > 'z') {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"syslog \"tag\" only allows "
|
||||
"alphanumeric characters "
|
||||
"and underscore");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
peer->tag.data = p + 4;
|
||||
peer->tag.len = len - 4;
|
||||
|
||||
} else if (ngx_strncmp(p, "rfc=", 4) == 0) {
|
||||
|
||||
if (peer->rfc_set) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"duplicate syslog \"rfc\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
peer->rfc_set = 1;
|
||||
|
||||
if (ngx_strcmp(p + 4, "rfc5424") == 0) {
|
||||
peer->rfc5424 = 1;
|
||||
|
||||
} else if (ngx_strcmp(p + 4, "rfc3164") != 0) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"unknown syslog \"rfc\" value \"%s\"",
|
||||
p + 4);
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
} else if (ngx_strncmp(p, "msgid=", 6) == 0) {
|
||||
|
||||
if (peer->msgid.data != NULL) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"duplicate syslog \"msgid\"");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* RFC 5424 s6.2.7: MSGID is at most 32 printable US-ASCII
|
||||
* characters. Character set and protocol-version constraints
|
||||
* are validated in ngx_syslog_process_conf() once all
|
||||
* parameters are known.
|
||||
*/
|
||||
if (len - 6 > 32) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"syslog msgid length exceeds 32");
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
peer->msgid.data = p + 6;
|
||||
peer->msgid.len = len - 6;
|
||||
|
||||
} else if (len == 10 && ngx_strncmp(p, "nohostname", 10) == 0) {
|
||||
peer->nohostname = 1;
|
||||
|
||||
|
|
@ -237,10 +354,56 @@ ngx_syslog_parse_args(ngx_conf_t *cf, ngx_syslog_peer_t *peer)
|
|||
u_char *
|
||||
ngx_syslog_add_header(ngx_syslog_peer_t *peer, u_char *buf)
|
||||
{
|
||||
ngx_uint_t pri;
|
||||
ngx_uint_t pri;
|
||||
ngx_str_t datetime, tz;
|
||||
ngx_time_t *tp;
|
||||
|
||||
pri = peer->facility * 8 + peer->severity;
|
||||
|
||||
if (peer->rfc5424) {
|
||||
|
||||
/*
|
||||
* RFC 5424 HEADER: VERSION SP TIMESTAMP SP HOSTNAME SP
|
||||
* APP-NAME SP PROCID SP MSGID SP STRUCTURED-DATA SP
|
||||
*
|
||||
* TIMESTAMP is formatted as an ISO 8601 date-time with
|
||||
* millisecond precision and UTC offset, e.g.:
|
||||
* 2003-10-11T22:14:15.003+05:30
|
||||
*
|
||||
* The date, time, and UTC offset are taken from the
|
||||
* ngx_cached_http_log_iso8601 cache ("YYYY-MM-DDTHH:MM:SS+/-HH:MM",
|
||||
* 25 bytes). The first 19 bytes are "YYYY-MM-DDTHH:MM:SS"
|
||||
* and the trailing 6 bytes are "+/-HH:MM". The millisecond
|
||||
* field is read live from ngx_timeofday() so that it reflects
|
||||
* the current event-loop tick rather than the start of the
|
||||
* current second.
|
||||
*
|
||||
* PROCID is the nginx process PID. MSGID defaults to the nil
|
||||
* value "-" and can be overridden with the "msgid=" parameter.
|
||||
* STRUCTURED-DATA is always the nil value "-".
|
||||
*/
|
||||
|
||||
tp = ngx_timeofday();
|
||||
|
||||
datetime.data = ngx_cached_http_log_iso8601.data;
|
||||
datetime.len = sizeof("1970-09-28T12:00:00") - 1;
|
||||
|
||||
tz.data = ngx_cached_http_log_iso8601.data
|
||||
+ sizeof("1970-09-28T12:00:00") - 1;
|
||||
tz.len = sizeof("+06:00") - 1;
|
||||
|
||||
if (peer->nohostname) {
|
||||
return ngx_sprintf(buf, "<%ui>1 %V.%03ui%V - %V %P %V - ",
|
||||
pri, &datetime, tp->msec, &tz,
|
||||
&peer->tag, ngx_pid, &peer->msgid);
|
||||
}
|
||||
|
||||
return ngx_sprintf(buf, "<%ui>1 %V.%03ui%V %V %V %P %V - ",
|
||||
pri, &datetime, tp->msec, &tz,
|
||||
peer->hostname, &peer->tag, ngx_pid,
|
||||
&peer->msgid);
|
||||
}
|
||||
|
||||
if (peer->nohostname) {
|
||||
return ngx_sprintf(buf, "<%ui>%V %V: ", pri, &ngx_cached_syslog_time,
|
||||
&peer->tag);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ typedef struct {
|
|||
ngx_uint_t facility;
|
||||
ngx_uint_t severity;
|
||||
ngx_str_t tag;
|
||||
ngx_str_t msgid;
|
||||
|
||||
ngx_str_t *hostname;
|
||||
|
||||
|
|
@ -23,6 +24,8 @@ typedef struct {
|
|||
|
||||
unsigned busy:1;
|
||||
unsigned nohostname:1;
|
||||
unsigned rfc5424:1;
|
||||
unsigned rfc_set:1;
|
||||
} ngx_syslog_peer_t;
|
||||
|
||||
|
||||
|
|
|
|||
276
t/syslog_rfc5424.t
Normal file
276
t/syslog_rfc5424.t
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# (C) Nginx, Inc.
|
||||
|
||||
# Tests for RFC 5424 syslog message format.
|
||||
|
||||
###############################################################################
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
use Test::More;
|
||||
|
||||
use IO::Select;
|
||||
use IO::Socket::INET;
|
||||
|
||||
BEGIN { use FindBin; chdir($FindBin::Bin); }
|
||||
|
||||
use lib 'lib';
|
||||
use Test::Nginx;
|
||||
|
||||
###############################################################################
|
||||
|
||||
select STDERR; $| = 1;
|
||||
select STDOUT; $| = 1;
|
||||
|
||||
plan(skip_all => 'win32') if $^O eq 'MSWin32';
|
||||
|
||||
my $t = Test::Nginx->new()->has(qw/http/)->plan(23);
|
||||
|
||||
###############################################################################
|
||||
|
||||
$t->write_file_expand('nginx.conf', <<'EOF');
|
||||
|
||||
%%TEST_GLOBALS%%
|
||||
|
||||
error_log syslog:server=127.0.0.1:%%PORT_8981_UDP%%,rfc=rfc5424 info;
|
||||
|
||||
daemon off;
|
||||
|
||||
events {
|
||||
}
|
||||
|
||||
http {
|
||||
%%TEST_GLOBALS_HTTP%%
|
||||
|
||||
log_format logf "$uri:$status";
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:8080;
|
||||
server_name localhost;
|
||||
|
||||
# RFC 5424 access log — basic format check
|
||||
location /a5424 {
|
||||
access_log syslog:server=127.0.0.1:%%PORT_8982_UDP%%,rfc=rfc5424
|
||||
logf;
|
||||
}
|
||||
|
||||
# RFC 5424 nohostname — HOSTNAME must be the nil value "-"
|
||||
location /nohostname5424 {
|
||||
access_log
|
||||
syslog:server=127.0.0.1:%%PORT_8982_UDP%%,rfc=rfc5424,nohostname
|
||||
logf;
|
||||
}
|
||||
|
||||
# RFC 5424 error log with custom tag (hyphen) and facility
|
||||
location /e5424 {
|
||||
error_log
|
||||
syslog:server=127.0.0.1:%%PORT_8982_UDP%%,rfc=rfc5424,tag=my-app,facility=user;
|
||||
}
|
||||
|
||||
# RFC 5424 access log with explicit msgid=
|
||||
location /a5424_msgid {
|
||||
access_log syslog:server=127.0.0.1:%%PORT_8982_UDP%%,rfc=rfc5424,msgid=MYAPP
|
||||
logf;
|
||||
}
|
||||
|
||||
# RFC 3164 access log — verify backward compatibility
|
||||
location /a3164 {
|
||||
access_log syslog:server=127.0.0.1:%%PORT_8983_UDP%% logf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
# Port 8981: background daemon that writes global error_log messages to a file.
|
||||
# Port 8982: the test binds this socket directly after run().
|
||||
# Port 8983: the test binds this socket directly after run().
|
||||
|
||||
$t->run_daemon(\&syslog_daemon, port(8981), $t, 's_glob.log');
|
||||
$t->waitforfile($t->testdir() . '/s_glob.log');
|
||||
|
||||
$t->run();
|
||||
|
||||
###############################################################################
|
||||
|
||||
my $s5424 = IO::Socket::INET->new(
|
||||
Proto => 'udp',
|
||||
LocalAddr => '127.0.0.1:' . port(8982)
|
||||
) or die "Can't open syslog socket (8982): $!";
|
||||
|
||||
my $s3164 = IO::Socket::INET->new(
|
||||
Proto => 'udp',
|
||||
LocalAddr => '127.0.0.1:' . port(8983)
|
||||
) or die "Can't open syslog socket (8983): $!";
|
||||
|
||||
###############################################################################
|
||||
|
||||
# RFC 5424 access log — full field-by-field check
|
||||
|
||||
parse_rfc5424_message('access_log', get_syslog($s5424, '/a5424'));
|
||||
|
||||
# RFC 5424 with nohostname — HOSTNAME field must be nil "-"
|
||||
|
||||
my $msg = get_syslog($s5424, '/nohostname5424');
|
||||
like($msg,
|
||||
qr/^<\d+>1\s # PRI + VERSION
|
||||
\S+\s # TIMESTAMP
|
||||
-\s # HOSTNAME = nil "-"
|
||||
\S+\s # APP-NAME
|
||||
\d+\s # PROCID
|
||||
-\s-\s/x,
|
||||
'rfc5424 nohostname: HOSTNAME is nil "-"');
|
||||
|
||||
# RFC 5424 error log — custom hyphenated tag (only valid in rfc5424)
|
||||
|
||||
$msg = get_syslog($s5424, '/e5424');
|
||||
like($msg, qr/my-app/, 'rfc5424: hyphenated tag present in APP-NAME');
|
||||
|
||||
# RFC 5424 facility=user must be encoded as facility 1 in PRI
|
||||
|
||||
my ($pri) = $msg =~ /^<(\d+)>/;
|
||||
my $fac = ($pri & 0x03f8) >> 3;
|
||||
is($fac, 1, 'rfc5424: facility=user (1) encoded in PRI');
|
||||
|
||||
# RFC 5424 MSGID field: explicit msgid= value appears in position 6 of the header
|
||||
|
||||
$msg = get_syslog($s5424, '/a5424_msgid');
|
||||
my ($msgid_field) = $msg =~ /^<\d+>1\s\S+\s\S+\s\S+\s\d+\s(\S+)\s/;
|
||||
is($msgid_field, 'MYAPP', 'rfc5424: explicit msgid= appears in MSGID field');
|
||||
|
||||
# Without msgid=, the MSGID field defaults to nil "-"
|
||||
|
||||
$msg = get_syslog($s5424, '/a5424');
|
||||
($msgid_field) = $msg =~ /^<\d+>1\s\S+\s\S+\s\S+\s\d+\s(\S+)\s/;
|
||||
is($msgid_field, '-', 'rfc5424: default MSGID is nil "-"');
|
||||
|
||||
# Global error_log uses rfc5424 — check via background-daemon log file
|
||||
|
||||
http_get('/a5424');
|
||||
|
||||
my $glob = '';
|
||||
for (1 .. 50) {
|
||||
select undef, undef, undef, 0.1;
|
||||
$glob = $t->read_file('s_glob.log');
|
||||
last if $glob;
|
||||
}
|
||||
like($glob, qr/^<\d+>1\s/m, 'rfc5424 global error_log: VERSION "1" present');
|
||||
|
||||
# Millisecond field is live (ngx_timeofday()->msec, not once-per-second cache).
|
||||
# Send several messages with small gaps and verify that the ms field is not
|
||||
# permanently stuck at "000" as it would be with a second-boundary-only cache.
|
||||
|
||||
my @ms_vals;
|
||||
for (1..10) {
|
||||
select undef, undef, undef, 0.02; # 20 ms gap
|
||||
my $m = get_syslog($s5424, '/a5424');
|
||||
my ($ms_field) = $m =~ /T\d{2}:\d{2}:\d{2}\.(\d{3})/;
|
||||
push @ms_vals, $ms_field if defined $ms_field;
|
||||
}
|
||||
ok((grep { $_ ne '000' } @ms_vals) > 0,
|
||||
'rfc5424: millisecond field is not always zero (live ngx_timeofday)');
|
||||
|
||||
# RFC 3164 format is unchanged (backward compatibility)
|
||||
|
||||
$msg = get_syslog($s3164, '/a3164');
|
||||
like($msg,
|
||||
qr/^<\d+> # PRI (no VERSION field)
|
||||
[A-Z][a-z]{2}\s # mon (BSD syslog timestamp)
|
||||
[ \d]\d\s\d{2}:\d{2}:\d{2}\s # day HH:MM:SS
|
||||
/x,
|
||||
'rfc3164: BSD syslog timestamp unchanged');
|
||||
unlike($msg, qr/^<\d+>1\s/, 'rfc3164: no VERSION field');
|
||||
|
||||
###############################################################################
|
||||
|
||||
sub get_syslog {
|
||||
my ($sock, $uri) = @_;
|
||||
my $data = '';
|
||||
|
||||
http_get($uri);
|
||||
|
||||
IO::Select->new($sock)->can_read(1);
|
||||
while (IO::Select->new($sock)->can_read(0.1)) {
|
||||
my $buf;
|
||||
sysread($sock, $buf, 4096);
|
||||
$data .= $buf;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
# Validate that $line looks like a valid RFC 5424 message and run
|
||||
# 14 individual Test::More assertions, one per protocol field.
|
||||
|
||||
sub parse_rfc5424_message {
|
||||
my ($desc, $line) = @_;
|
||||
|
||||
unless ($line) {
|
||||
fail("$desc: no syslog message received");
|
||||
return;
|
||||
}
|
||||
|
||||
# RFC 5424 SYSLOG-MSG:
|
||||
# <PRI>VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP
|
||||
# PROCID SP MSGID SP STRUCTURED-DATA SP MSG
|
||||
|
||||
my ($pri, $ts, $host, $app, $pid, $msgid, $sd, $msg) =
|
||||
$line =~ /^<(\d{1,3})> # PRI
|
||||
1\s # VERSION (literal "1")
|
||||
(\S+)\s # TIMESTAMP
|
||||
(\S+)\s # HOSTNAME
|
||||
(\S+)\s # APP-NAME
|
||||
(\S+)\s # PROCID
|
||||
(\S+)\s # MSGID
|
||||
(\S+)\s # STRUCTURED-DATA
|
||||
(.*)/x; # MSG
|
||||
|
||||
ok(defined($pri), "$desc: has PRI");
|
||||
|
||||
my $sev = $pri & 0x07;
|
||||
my $fac = ($pri & 0x03f8) >> 3;
|
||||
ok($sev >= 0 && $sev <= 7, "$desc: severity in PRI is 0-7");
|
||||
ok($fac >= 0 && $fac < 24, "$desc: facility in PRI is 0-23");
|
||||
|
||||
ok(defined($ts), "$desc: has TIMESTAMP");
|
||||
like($ts,
|
||||
qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}$/,
|
||||
"$desc: TIMESTAMP is ISO 8601 with ms and tz offset");
|
||||
|
||||
ok(defined($host), "$desc: has HOSTNAME");
|
||||
ok(length($host) > 0 && $host ne '-', "$desc: HOSTNAME is non-nil");
|
||||
|
||||
ok(defined($app), "$desc: has APP-NAME");
|
||||
like($app, qr/^[!-~]+$/, "$desc: APP-NAME is printable US-ASCII");
|
||||
|
||||
ok(defined($pid), "$desc: has PROCID");
|
||||
like($pid, qr/^\d+$/, "$desc: PROCID is a decimal integer");
|
||||
|
||||
is($msgid, '-', "$desc: MSGID is nil");
|
||||
is($sd, '-', "$desc: STRUCTURED-DATA is nil");
|
||||
|
||||
ok(defined($msg) && length($msg) > 0, "$desc: MSG is non-empty");
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
|
||||
sub syslog_daemon {
|
||||
my ($port, $t, $file) = @_;
|
||||
|
||||
my $s = IO::Socket::INET->new(
|
||||
Proto => 'udp',
|
||||
LocalAddr => "127.0.0.1:$port"
|
||||
);
|
||||
|
||||
open my $fh, '>', $t->testdir() . '/' . $file;
|
||||
select $fh; $| = 1;
|
||||
|
||||
while (1) {
|
||||
my $buffer;
|
||||
$s->recv($buffer, 4096);
|
||||
print $fh $buffer . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
139
t/syslog_rfc5424_config.t
Normal file
139
t/syslog_rfc5424_config.t
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# (C) Nginx, Inc.
|
||||
|
||||
# Tests for "rfc=" parameter of the syslog directive.
|
||||
# Uses "nginx -t" config-check mode; no server is started.
|
||||
|
||||
###############################################################################
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
use Test::More;
|
||||
|
||||
BEGIN { use FindBin; chdir($FindBin::Bin); }
|
||||
|
||||
use lib 'lib';
|
||||
use Test::Nginx;
|
||||
|
||||
###############################################################################
|
||||
|
||||
select STDERR; $| = 1;
|
||||
select STDOUT; $| = 1;
|
||||
|
||||
plan(skip_all => 'win32') if $^O eq 'MSWin32';
|
||||
|
||||
my $t = Test::Nginx->new();
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Write a minimal nginx.conf containing $syslog_param as a global error_log
|
||||
# directive and run "nginx -t" against it. Returns combined stdout+stderr.
|
||||
|
||||
sub config_check {
|
||||
my ($syslog_param) = @_;
|
||||
|
||||
$t->write_file_expand('nginx.conf', <<"EOF");
|
||||
|
||||
%%TEST_GLOBALS%%
|
||||
|
||||
error_log $syslog_param info;
|
||||
|
||||
daemon off;
|
||||
|
||||
events {
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
my $testdir = $t->testdir();
|
||||
return qx{$Test::Nginx::NGINX -t -p $testdir/ -c nginx.conf 2>&1};
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
|
||||
my $out;
|
||||
|
||||
# rfc=rfc3164 is the default and must be accepted explicitly.
|
||||
|
||||
$out = config_check('syslog:server=127.0.0.1:5140,rfc=rfc3164');
|
||||
like($out, qr/test is successful/i, 'rfc=rfc3164 accepted');
|
||||
|
||||
# rfc=rfc5424 must be accepted.
|
||||
|
||||
$out = config_check('syslog:server=127.0.0.1:5140,rfc=rfc5424');
|
||||
like($out, qr/test is successful/i, 'rfc=rfc5424 accepted');
|
||||
|
||||
# An unknown rfc= value must be rejected with a descriptive error.
|
||||
|
||||
$out = config_check('syslog:server=127.0.0.1:5140,rfc=rfc9999');
|
||||
like($out, qr/unknown syslog "rfc" value/, 'rfc=rfc9999 rejected');
|
||||
|
||||
# A hyphenated tag is printable ASCII and must be accepted with rfc5424.
|
||||
|
||||
$out = config_check('syslog:server=127.0.0.1:5140,rfc=rfc5424,tag=my-app');
|
||||
like($out, qr/test is successful/i, 'rfc5424: hyphenated tag accepted');
|
||||
|
||||
# The same hyphenated tag must be rejected with rfc3164.
|
||||
|
||||
$out = config_check('syslog:server=127.0.0.1:5140,rfc=rfc3164,tag=my-app');
|
||||
like($out, qr/only allows alphanumeric/, 'rfc3164: hyphenated tag rejected');
|
||||
|
||||
# A tag with a dot is printable ASCII and must be accepted with rfc5424.
|
||||
|
||||
$out = config_check('syslog:server=127.0.0.1:5140,rfc=rfc5424,tag=nginx.1');
|
||||
like($out, qr/test is successful/i, 'rfc5424: dot in tag accepted');
|
||||
|
||||
# A 33-character tag is within the rfc5424 limit (48) and must be accepted.
|
||||
|
||||
my $tag33 = 'a' x 33;
|
||||
$out = config_check("syslog:server=127.0.0.1:5140,rfc=rfc5424,tag=$tag33");
|
||||
like($out, qr/test is successful/i, 'rfc5424: 33-char tag accepted');
|
||||
|
||||
# The same 33-character tag exceeds the rfc3164 limit (32) and must fail.
|
||||
|
||||
$out = config_check("syslog:server=127.0.0.1:5140,rfc=rfc3164,tag=$tag33");
|
||||
like($out, qr/tag length exceeds 32/, 'rfc3164: 33-char tag rejected');
|
||||
|
||||
# A 49-character tag exceeds the maximum of both protocol versions (48).
|
||||
|
||||
my $tag49 = 'a' x 49;
|
||||
$out = config_check("syslog:server=127.0.0.1:5140,rfc=rfc5424,tag=$tag49");
|
||||
like($out, qr/tag length exceeds 48/, 'rfc5424: 49-char tag rejected');
|
||||
|
||||
$out = config_check("syslog:server=127.0.0.1:5140,rfc=rfc3164,tag=$tag49");
|
||||
like($out, qr/tag length exceeds 48/, 'rfc3164: 49-char tag rejected');
|
||||
|
||||
# msgid= is accepted with rfc=rfc5424.
|
||||
|
||||
$out = config_check('syslog:server=127.0.0.1:5140,rfc=rfc5424,msgid=MYAPP');
|
||||
like($out, qr/test is successful/i, 'rfc5424: msgid= accepted');
|
||||
|
||||
# msgid= without rfc=rfc5424 must be rejected.
|
||||
|
||||
$out = config_check('syslog:server=127.0.0.1:5140,msgid=MYAPP');
|
||||
like($out, qr/requires rfc=rfc5424/, 'msgid= without rfc5424 rejected');
|
||||
|
||||
# msgid= with a non-ASCII byte (>0x7E) must be rejected.
|
||||
# Space cannot be tested this way because nginx's config parser splits on
|
||||
# whitespace before syslog parsing sees the value.
|
||||
|
||||
$out = config_check("syslog:server=127.0.0.1:5140,rfc=rfc5424,msgid=MY\xc3APP");
|
||||
like($out, qr/printable US-ASCII/, 'msgid= with non-ASCII byte rejected');
|
||||
|
||||
# msgid= must not exceed 32 characters.
|
||||
|
||||
my $msgid33 = 'x' x 33;
|
||||
$out = config_check("syslog:server=127.0.0.1:5140,rfc=rfc5424,msgid=$msgid33");
|
||||
like($out, qr/msgid length exceeds 32/, 'msgid= 33-char rejected');
|
||||
|
||||
# A 32-character msgid is within the limit and must be accepted.
|
||||
|
||||
my $msgid32 = 'x' x 32;
|
||||
$out = config_check("syslog:server=127.0.0.1:5140,rfc=rfc5424,msgid=$msgid32");
|
||||
like($out, qr/test is successful/i, 'msgid= 32-char accepted');
|
||||
|
||||
done_testing;
|
||||
|
||||
###############################################################################
|
||||
168
t/syslog_rfc5424_stream.t
Normal file
168
t/syslog_rfc5424_stream.t
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# (C) Nginx, Inc.
|
||||
|
||||
# Tests for RFC 5424 syslog format in the stream access_log directive.
|
||||
|
||||
###############################################################################
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
use Test::More;
|
||||
|
||||
use IO::Select;
|
||||
use IO::Socket::INET;
|
||||
|
||||
BEGIN { use FindBin; chdir($FindBin::Bin); }
|
||||
|
||||
use lib 'lib';
|
||||
use Test::Nginx;
|
||||
use Test::Nginx::Stream qw/ stream /;
|
||||
|
||||
###############################################################################
|
||||
|
||||
select STDERR; $| = 1;
|
||||
select STDOUT; $| = 1;
|
||||
|
||||
plan(skip_all => 'win32') if $^O eq 'MSWin32';
|
||||
|
||||
my $t = Test::Nginx->new()->has(qw/stream http/)->plan(15);
|
||||
|
||||
###############################################################################
|
||||
|
||||
$t->write_file_expand('nginx.conf', <<'EOF');
|
||||
|
||||
%%TEST_GLOBALS%%
|
||||
|
||||
daemon off;
|
||||
|
||||
events {
|
||||
}
|
||||
|
||||
http {
|
||||
%%TEST_GLOBALS_HTTP%%
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:8080;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
return 200 ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream {
|
||||
%%TEST_GLOBALS_STREAM%%
|
||||
|
||||
log_format streamf "$remote_addr $protocol";
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:8081;
|
||||
proxy_pass 127.0.0.1:8080;
|
||||
access_log syslog:server=127.0.0.1:%%PORT_8982_UDP%%,rfc=rfc5424
|
||||
streamf;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:8082;
|
||||
proxy_pass 127.0.0.1:8080;
|
||||
access_log syslog:server=127.0.0.1:%%PORT_8982_UDP%%,rfc=rfc5424,nohostname
|
||||
streamf;
|
||||
}
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
$t->run();
|
||||
|
||||
###############################################################################
|
||||
|
||||
my $s5424 = IO::Socket::INET->new(
|
||||
Proto => 'udp',
|
||||
LocalAddr => '127.0.0.1:' . port(8982)
|
||||
) or die "Can't open syslog socket: $!";
|
||||
|
||||
# RFC 5424 stream access log — full field-by-field check
|
||||
|
||||
stream('127.0.0.1:' . port(8081))->read();
|
||||
parse_rfc5424_message('stream access_log', get_syslog_raw($s5424));
|
||||
|
||||
# RFC 5424 stream with nohostname
|
||||
|
||||
stream('127.0.0.1:' . port(8082))->read();
|
||||
my $msg = get_syslog_raw($s5424);
|
||||
like($msg,
|
||||
qr/^<\d+>1\s # PRI + VERSION
|
||||
\S+\s # TIMESTAMP
|
||||
-\s # HOSTNAME = nil "-"
|
||||
\S+\s # APP-NAME
|
||||
\d+\s # PROCID
|
||||
-\s-\s/x,
|
||||
'stream rfc5424 nohostname: HOSTNAME is nil "-"');
|
||||
|
||||
###############################################################################
|
||||
|
||||
sub get_syslog_raw {
|
||||
my ($sock) = @_;
|
||||
my $data = '';
|
||||
|
||||
IO::Select->new($sock)->can_read(2);
|
||||
while (IO::Select->new($sock)->can_read(0.1)) {
|
||||
my $buf;
|
||||
sysread($sock, $buf, 4096);
|
||||
$data .= $buf;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
# Validate all RFC 5424 header fields; runs 14 assertions.
|
||||
|
||||
sub parse_rfc5424_message {
|
||||
my ($desc, $line) = @_;
|
||||
|
||||
unless ($line) {
|
||||
fail("$desc: no syslog message received");
|
||||
return;
|
||||
}
|
||||
|
||||
my ($pri, $ts, $host, $app, $pid, $msgid, $sd, $msg) =
|
||||
$line =~ /^<(\d{1,3})> # PRI
|
||||
1\s # VERSION
|
||||
(\S+)\s # TIMESTAMP
|
||||
(\S+)\s # HOSTNAME
|
||||
(\S+)\s # APP-NAME
|
||||
(\S+)\s # PROCID
|
||||
(\S+)\s # MSGID
|
||||
(\S+)\s # STRUCTURED-DATA
|
||||
(.*)/x; # MSG
|
||||
|
||||
ok(defined($pri), "$desc: has PRI");
|
||||
|
||||
my $sev = $pri & 0x07;
|
||||
my $fac = ($pri & 0x03f8) >> 3;
|
||||
ok($sev >= 0 && $sev <= 7, "$desc: severity in PRI is 0-7");
|
||||
ok($fac >= 0 && $fac < 24, "$desc: facility in PRI is 0-23");
|
||||
|
||||
ok(defined($ts), "$desc: has TIMESTAMP");
|
||||
like($ts,
|
||||
qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}$/,
|
||||
"$desc: TIMESTAMP is ISO 8601 with ms and tz offset");
|
||||
|
||||
ok(defined($host), "$desc: has HOSTNAME");
|
||||
ok(length($host) > 0 && $host ne '-', "$desc: HOSTNAME is non-nil");
|
||||
|
||||
ok(defined($app), "$desc: has APP-NAME");
|
||||
like($app, qr/^[!-~]+$/, "$desc: APP-NAME is printable US-ASCII");
|
||||
|
||||
ok(defined($pid), "$desc: has PROCID");
|
||||
like($pid, qr/^\d+$/, "$desc: PROCID is a decimal integer");
|
||||
|
||||
is($msgid, '-', "$desc: MSGID is nil");
|
||||
is($sd, '-', "$desc: STRUCTURED-DATA is nil");
|
||||
|
||||
ok(defined($msg) && length($msg) > 0, "$desc: MSG is non-empty");
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
Loading…
Add table
Add a link
Reference in a new issue