之前写过一篇: nginx两种实用的自定义访问日志格式 里面介绍了两种自定义的日志格式,一种是对程序友好的json格式日志,另外一种是手动分析友好的日志格式。人肉分析日志时,如果不是排查应用相关的日志,关注的访问日志多数是指定条件的,如分析502、404等状态码的日志.error log比较简单,就不说了。下面就介绍一下按条件输出访问日志的一些做法。

nginx 输出日志用到的内置模块是ngx_http_log_module,我们先来看看输出access log的语法:

access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];

可以看到access_log是支持if条件的,需要1.7以上版本的nginx,if条件为0或空时不输出日志,有了这个条件判断就可以做很多事了,下面介绍几种按条件输出日志的方式抛砖引玉。

一、按域名输出日志
这种情况主要是针对同一个虚拟主机配置了大量的域名情况,对于某一个域名要输出日志分析的情况。来看配置:

map $host $log_host {

    ~nixops.me  1;
    ~nixops1.me 1;
    default 0;
}

server {

    […]

    access_log /var/log/nginx/access.log main;
    access_log /var/log/nginx/nixops.me.log main if=$log_host;
}

这样 /var/log/nginx/nixops.me.log 里面就只有 nixops.me和nixops1.me这两个域名的日志了。

二、按http状态码输出日志
输出不正常的http状态码到访问日志,如502、503、504、400等状态码,对于分析反向代理或者应用异常会有用。和上面的做法一样,来看配置:

map $status $log_status {

    ~^[50] 1;
    404  1;
    default 0;
}

server {

    […]

    access_log /var/log/nginx/access.log main;
    access_log /var/log/nginx/http_error.log main if=$log_status;
}

这样就输出502、503、504、404日志到/var/log/nginx/http_error.log

三、输出指定uri的日志

输出指定location的日志到访问日志,看配置:

map $uri $log_uri {

    ~*admin 1;
    /api  1;
    default 0;
}

server {

    […]

    access_log /var/log/nginx/access.log main;
    access_log /var/log/nginx/http_uri.log main if=$log_uri;
}

也可以换个方式:

server {

    […]

    set $log_uri 0;
    if ( $uri ~ ^/api ) {
        set $log_uri 1;
    }

    access_log /var/log/nginx/access.log main;
    access_log /var/log/nginx/http_uri.log main if=$log_uri;

}

这种不使用map的方式可以支持正则表达式。

四、输出指定user agent的日志

记录包含指定user agent的请求到访问日志,如搜索引擎的抓取日志:

map $http_user_agent $log_ua {

    ~*Googlebot 1;
    ~*Baiduspider 1;

    default 0;
}
server {

    […]

    access_log /var/log/nginx/access.log main;
    access_log /var/log/nginx/http_ua.log main if=$log_ua;
}

这样就把baidu和google的爬虫抓取日志输出到http_ua.log里了。

五、记录指定cookie

记录指定包含指定cookie的请求日志:

map $http_cookie $log_cookie {

    ~PHPSESSION 1;
    default "";
}
server {

    […]

    access_log /var/log/nginx/access.log main;
    access_log /var/log/nginx/http_cookie.log main if=$log_cookie;
}

聪明的你肯定已经发现,上面的方法都是用map来实现,map一个nginx内置的变量,并设置值0或1,通过if条件判断是否需要记录。 那我们看下通过这种方式,能设置或输出那些条件的日志,看看 nginx内置变量 就知道能输出那些自定义的内容:

$ancient_browser
$arg_
$args
$binary_remote_addr (ngx_http_core_module)
$binary_remote_addr (ngx_stream_core_module)
$body_bytes_sent
$bytes_received
$bytes_sent (ngx_http_core_module)
$bytes_sent (ngx_http_log_module)
$bytes_sent (ngx_stream_core_module)
$connection (ngx_http_core_module)
$connection (ngx_http_log_module)
$connection (ngx_stream_core_module)
$connection_requests (ngx_http_core_module)
$connection_requests (ngx_http_log_module)
$connections_active
$connections_reading
$connections_waiting
$connections_writing
$content_length
$content_type
$cookie_
$date_gmt
$date_local
$document_root
$document_uri
$fastcgi_path_info
$fastcgi_script_name
$geoip_area_code (ngx_http_geoip_module)
$geoip_area_code (ngx_stream_geoip_module)
$geoip_city (ngx_http_geoip_module)
$geoip_city (ngx_stream_geoip_module)
$geoip_city_continent_code (ngx_http_geoip_module)
$geoip_city_continent_code (ngx_stream_geoip_module)
$geoip_city_country_code (ngx_http_geoip_module)
$geoip_city_country_code (ngx_stream_geoip_module)
$geoip_city_country_code3 (ngx_http_geoip_module)
$geoip_city_country_code3 (ngx_stream_geoip_module)
$geoip_city_country_name (ngx_http_geoip_module)
$geoip_city_country_name (ngx_stream_geoip_module)
$geoip_country_code (ngx_http_geoip_module)
$geoip_country_code (ngx_stream_geoip_module)
$geoip_country_code3 (ngx_http_geoip_module)
$geoip_country_code3 (ngx_stream_geoip_module)
$geoip_country_name (ngx_http_geoip_module)
$geoip_country_name (ngx_stream_geoip_module)
$geoip_dma_code (ngx_http_geoip_module)
$geoip_dma_code (ngx_stream_geoip_module)
$geoip_latitude (ngx_http_geoip_module)
$geoip_latitude (ngx_stream_geoip_module)
$geoip_longitude (ngx_http_geoip_module)
$geoip_longitude (ngx_stream_geoip_module)
$geoip_org (ngx_http_geoip_module)
$geoip_org (ngx_stream_geoip_module)
$geoip_postal_code (ngx_http_geoip_module)
$geoip_postal_code (ngx_stream_geoip_module)
$geoip_region (ngx_http_geoip_module)
$geoip_region (ngx_stream_geoip_module)
$geoip_region_name (ngx_http_geoip_module)
$geoip_region_name (ngx_stream_geoip_module)
$gzip_ratio
$host
$hostname (ngx_http_core_module)
$hostname (ngx_stream_core_module)
$http2
$http_
$https
$invalid_referer
$is_args
$jwt_claim_
$jwt_header_
$limit_conn_status (ngx_http_limit_conn_module)
$limit_conn_status (ngx_stream_limit_conn_module)
$limit_rate
$limit_req_status
$memcached_key
$modern_browser
$msec (ngx_http_core_module)
$msec (ngx_http_log_module)
$msec (ngx_stream_core_module)
$msie
$nginx_version (ngx_http_core_module)
$nginx_version (ngx_stream_core_module)
$pid (ngx_http_core_module)
$pid (ngx_stream_core_module)
$pipe (ngx_http_core_module)
$pipe (ngx_http_log_module)
$protocol
$proxy_add_x_forwarded_for
$proxy_host
$proxy_port
$proxy_protocol_addr (ngx_http_core_module)
$proxy_protocol_addr (ngx_stream_core_module)
$proxy_protocol_port (ngx_http_core_module)
$proxy_protocol_port (ngx_stream_core_module)
$proxy_protocol_server_addr (ngx_http_core_module)
$proxy_protocol_server_addr (ngx_stream_core_module)
$proxy_protocol_server_port (ngx_http_core_module)
$proxy_protocol_server_port (ngx_stream_core_module)
$query_string
$realip_remote_addr (ngx_http_realip_module)
$realip_remote_addr (ngx_stream_realip_module)
$realip_remote_port (ngx_http_realip_module)
$realip_remote_port (ngx_stream_realip_module)
$realpath_root
$remote_addr (ngx_http_core_module)
$remote_addr (ngx_stream_core_module)
$remote_port (ngx_http_core_module)
$remote_port (ngx_stream_core_module)
$remote_user
$request
$request_body
$request_body_file
$request_completion
$request_filename
$request_id
$request_length (ngx_http_core_module)
$request_length (ngx_http_log_module)
$request_method
$request_time (ngx_http_core_module)
$request_time (ngx_http_log_module)
$request_uri
$scheme
$secure_link
$secure_link_expires
$sent_http_
$sent_trailer_
$server_addr (ngx_http_core_module)
$server_addr (ngx_stream_core_module)
$server_name
$server_port (ngx_http_core_module)
$server_port (ngx_stream_core_module)
$server_protocol
$session_log_binary_id
$session_log_id
$session_time
$slice_range
$spdy
$spdy_request_priority
$ssl_cipher (ngx_http_ssl_module)
$ssl_cipher (ngx_stream_ssl_module)
$ssl_ciphers (ngx_http_ssl_module)
$ssl_ciphers (ngx_stream_ssl_module)
$ssl_client_cert (ngx_http_ssl_module)
$ssl_client_cert (ngx_stream_ssl_module)
$ssl_client_escaped_cert
$ssl_client_fingerprint (ngx_http_ssl_module)
$ssl_client_fingerprint (ngx_stream_ssl_module)
$ssl_client_i_dn (ngx_http_ssl_module)
$ssl_client_i_dn (ngx_stream_ssl_module)
$ssl_client_i_dn_legacy
$ssl_client_raw_cert (ngx_http_ssl_module)
$ssl_client_raw_cert (ngx_stream_ssl_module)
$ssl_client_s_dn (ngx_http_ssl_module)
$ssl_client_s_dn (ngx_stream_ssl_module)
$ssl_client_s_dn_legacy
$ssl_client_serial (ngx_http_ssl_module)
$ssl_client_serial (ngx_stream_ssl_module)
$ssl_client_v_end (ngx_http_ssl_module)
$ssl_client_v_end (ngx_stream_ssl_module)
$ssl_client_v_remain (ngx_http_ssl_module)
$ssl_client_v_remain (ngx_stream_ssl_module)
$ssl_client_v_start (ngx_http_ssl_module)
$ssl_client_v_start (ngx_stream_ssl_module)
$ssl_client_verify (ngx_http_ssl_module)
$ssl_client_verify (ngx_stream_ssl_module)
$ssl_curves (ngx_http_ssl_module)
$ssl_curves (ngx_stream_ssl_module)
$ssl_early_data
$ssl_preread_alpn_protocols
$ssl_preread_protocol
$ssl_preread_server_name
$ssl_protocol (ngx_http_ssl_module)
$ssl_protocol (ngx_stream_ssl_module)
$ssl_server_name (ngx_http_ssl_module)
$ssl_server_name (ngx_stream_ssl_module)
$ssl_session_id (ngx_http_ssl_module)
$ssl_session_id (ngx_stream_ssl_module)
$ssl_session_reused (ngx_http_ssl_module)
$ssl_session_reused (ngx_stream_ssl_module)
$status (ngx_http_core_module)
$status (ngx_http_log_module)
$status (ngx_stream_core_module)
$tcpinfo_rtt
$tcpinfo_rttvar
$tcpinfo_snd_cwnd
$tcpinfo_rcv_space
$time_iso8601 (ngx_http_core_module)
$time_iso8601 (ngx_http_log_module)
$time_iso8601 (ngx_stream_core_module)
$time_local (ngx_http_core_module)
$time_local (ngx_http_log_module)
$time_local (ngx_stream_core_module)
$uid_got
$uid_reset
$uid_set
$upstream_addr (ngx_http_upstream_module)
$upstream_addr (ngx_stream_upstream_module)
$upstream_bytes_received (ngx_http_upstream_module)
$upstream_bytes_received (ngx_stream_upstream_module)
$upstream_bytes_sent (ngx_http_upstream_module)
$upstream_bytes_sent (ngx_stream_upstream_module)
$upstream_cache_status
$upstream_connect_time (ngx_http_upstream_module)
$upstream_connect_time (ngx_stream_upstream_module)
$upstream_cookie_
$upstream_first_byte_time
$upstream_header_time
$upstream_http_
$upstream_queue_time
$upstream_response_length
$upstream_response_time
$upstream_session_time
$upstream_status
$upstream_trailer_
$uri

这么多变量是不是晕了?不用怕,很多变量其实对于日志分析是没什么用的,常用的变量在nginx两种实用的自定义访问日志格式里面有介绍,过滤掉一下没用的,基本上以下变量就够用:

$host  :访问域名
$uri   :请求uri
$status :http状态码
$request_method :请求方法
$request_completion :请求是否完成
$upstream_addr :反向代理的upstream
$upstream_status  :upstream响应值
$scheme :请求的协议
$http_referer :referer来源
$http_user_agent :浏览器的user agent
$http_cookie : 本地所有cookie,也可以打印指定cookie,如打印session: $http_session
$args 或$arg_ : 请求时的参数

如果有不常见的需求,可以参考官方Nginx里的内置变量进行配置输出。

六、多条件判断
上面例子1-5都是单条件的,也可以使用多条件,nginx没有类似if-else多条件判断语句。涉及到多条件判断的情况,有个通用的写法,先set多个条件的变量,在if判断:

map $http_user_agent $log_ua {

    ~*Googlebot 1;
    ~*Baiduspider 1;
    default 0;
}

map $http_cookie $log_cookie {

    ~PHPSESSION 1;
    default "";
}

server {

    […]

    set $logging 0;
    set $logtmp '';

    if ( $log_ua = 0 ) {
        set $logtest "${logtmp}A";
    }
    if ( $log_cookie = 0 ) {
        set $logtmp "${logtmp}B";
    }
    if ( $logtmp = "AB" ) {
        set $logging 1;
    }

    access_log /var/log/nginx/access.log main if=$logging;

}

nginx if条件句不为0或空时,nginx会认为是true,所以上面的配置可以精简一下写法:

map $http_user_agent $log_ua {

    ~*Googlebot 1;
    ~*Baiduspider 1;
    default 0;
}

map $http_cookie $log_cookie {

    ~PHPSESSION 1;
    default "";
}

server {

    […]

    set $logging 0;
    set $logtmp "$log_ua$log_cookie"
    if ( $logtmp = "11" ) {
        set $logging 1;
    }

    access_log /var/log/nginx/access.log main if=$logging;

}

不用if判断也是可以的,用map的方式:

map $http_user_agent $log_ua {

    ~*Googlebot 1;
    ~*Baiduspider 1;
    default 0;
}

map $http_cookie $log_cookie {

    ~PHPSESSION 1;
    default "";
}

map “$log_ua:$log_cookie” $logging {
“1:1” 1;
default 0;
}

server {

    […]

    access_log /var/log/nginx/access.log main;
    access_log /var/log/nginx/http_multi_conditions.log main if=$logging;
 }

nginx按条件输出日志还是挺简单的,做法就是两种方式:

  1. 上面例子中使用map的方式
  2. 例子三中第二种写法,直接使用if的方式

参考文章:
https://www.bjornjohansen.com/exclude-requests-from-nginx-access-log
https://www.nginx.com/blog/sampling-requests-with-nginx-conditional-logging/