Apache HTTPD good logging

Table of Contents

1 Apache proxy server logging

There are two main reasons for ducking around with Apache's default log style:

  • You're proxying stuff, and want to propagate the original IP address down the stack
  • You want to parse logs programatically, which is nigh impossible in the default setup.

Here's an opinionated setup which works.

1.1 Apache configuration

1.1.1 On proxy host

# Global configuration:

LogFormat "%t ^_%a ^_%l ^_%u ^_\"%r\" ^_%>s ^_%b ^_\"%{Referer}i\" ^_\"%{User-Agent}i\"^_ %{UNIQUE_ID}e" combined_single_vhost
LogFormat "%t ^_%{Host}i ^_%a ^_%{SSL_PROTOCOL}i ^_%l ^_%u ^_\"%r\" ^_%>s ^_%b ^_\"%{Referer}i\" ^_\"%{User-Agent}i\"^_ %{UNIQUE_ID}e" combined_multiple

<Macro Unique_Header>
	RequestHeader set X-bobs-request-id "%{UNIQUE_ID}e"
	Header add X-bobs-request-id "%{UNIQUE_ID}e"
<Macro>

<Macro Log_To $name>
	CustomLog "/var/log/httpd/$name-access.log" combined_single_vhost
	ErrorLog "/var/log/https/$name-error.log"
</Macro>

CustomLog "/var/log/httpd/default-access.log" combined_multiple
ErrorLog "/var/log/httpd/default-error.log"

#
# ... then, later, in vhost config:
#

Use UniqueHeader
Use Log_To mysite

TODO: Why add two headers? (Gotta love post-hoc documentation…)

1.1.2 On proxied host

Note the directive RemoteIP(Internal|Trusted)Proxy, which differs in whether RFC 1918-style addresses are allowed to occur in X-Forwarded-For. Read the mod_remoteip documentation.

LoadModule remoteip_module modules/mod_remoteip.so
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.1.2.3
RemoteIPTrustedProxy 172.16.17.18

LogFormat "%t ^_%a ^_%l ^_%u ^_\"%r\" ^_%>s ^_%b ^_\"%{Referer}i\" ^_\"%{User-Agent}i\"^_ %{X-bobs-request-id}i" combined_single_vhost_proxied
LogFormat "%t ^_%{Host}i ^_%a ^_%{SSL_PROTOCOL}i ^_%l ^_%u ^_\"%r\" ^_%>s ^_%b ^_\"%{Referer}i\" ^_\"%{User-Agent}i\"^_ %{X-bobs-request-id}i" combined_multiple_proxied

Note and beware the intersprinkled ^_'s, which are explained below.

1.2 Format legend

Complete listing at httpd.apache.org, relevant part replicated below:

Format Description
%t Timestamp
%a Client IP address
%l Logname (useless, but ….)
%u Username or "-"
%r The request
%>s Final status
%b Size of response
%{hdr}i Contents of header "hdr"
%{var}e Content of env. variable "var"

Note that the fields are separated by a space (0x20) followed by an ASCII unit separator (0x1F). For converting the above display-code to actual code, pipe through e.g. perl -pe 's/\^_/@{[chr 31]}/g;'.

The unit separator is non-printable, and so plays (mostly) nice with visual inspection of log files, and also makes possible programmatically parsing a log file, which is logically impossible otherwise, due to the fact that fields may contain spaces, newlines, and, well, virtually anything.

A request containing a separator character will show up in the logs like so:

... "GET /wat\x1fwat HTTP/1.1" 400 ...

Separators (indeed, any Unicode characters) can be inserted in Emacs by: C-x 8 <RET> 1f <RET>. Or in Vim (from insert mode): ^v u 1f<RET>.

Author: rkv

Created: 2020-06-18 Thu 15:11

Validate