awx/awx/main/utils/external_logging.py
Ryan Petrello 2a4b009f04 rsyslogd: use %rawmsg-after-pri% instead of %msg%
after some prolonged RFC reading and tinkering w/ rsyslogd...

cpython's SysLogHandler doesn't emit RFC3164 formatted messages
in the format you'd expect; it's missing the ISO date, hostname, etc...
along with other header values; the handler implementation relies on you
to specify a syslog-like formatter (we've replaced all of this with our
own *custom* logstash-esque formatter that effectively outputs valid JSON
- without dates and other syslog header values prepended)

because of this unanticipated format, rsyslogd chokes when trying to
parse the message's parts;  AWX is emitting:

<priority>RAWJSON

...so the usage of `%msg%` isn't going to work for us, because rsyslog
tries to parse *all* of the possible headers (and yells, because it
can't find a date to parse):

see: https://www.rsyslog.com/files/temp/doc-indent/configuration/properties.html#message-properties

this is fine, because we don't *need* any of that message parsing
anyways; in the end, we're *just* interested in forwarding the raw
JSON/text content to the third party log handler
2020-04-13 11:44:00 -04:00

81 lines
3.2 KiB
Python

import urllib.parse as urlparse
from django.conf import settings
from awx.main.utils.reload import supervisor_service_command
def construct_rsyslog_conf_template(settings=settings):
tmpl = ''
parts = []
host = getattr(settings, 'LOG_AGGREGATOR_HOST', '')
port = getattr(settings, 'LOG_AGGREGATOR_PORT', '')
protocol = getattr(settings, 'LOG_AGGREGATOR_PROTOCOL', '')
if protocol.startswith('http'):
scheme = 'https'
# urlparse requires '//' to be provided if scheme is not specified
original_parsed = urlparse.urlsplit(host)
if (not original_parsed.scheme and not host.startswith('//')) or original_parsed.hostname is None:
host = '%s://%s' % (scheme, host) if scheme else '//%s' % host
parsed = urlparse.urlsplit(host)
host = parsed.hostname
try:
if parsed.port:
port = parsed.port
except ValueError:
port = settings.LOG_AGGREGATOR_PORT
max_bytes = settings.MAX_EVENT_RES_DATA
parts.extend([
'$WorkDirectory /var/lib/awx/rsyslog',
f'$MaxMessageSize {max_bytes}',
'$IncludeConfig /var/lib/awx/rsyslog/conf.d/*.conf',
'$ModLoad imuxsock',
'input(type="imuxsock" Socket="' + settings.LOGGING['handlers']['external_logger']['address'] + '" unlink="on")',
'template(name="awx" type="string" string="%rawmsg-after-pri%")',
])
if protocol.startswith('http'):
# https://github.com/rsyslog/rsyslog-doc/blob/master/source/configuration/modules/omhttp.rst
ssl = "on" if parsed.scheme == 'https' else "off"
skip_verify = "off" if settings.LOG_AGGREGATOR_VERIFY_CERT else "on"
if not port:
port = 443 if parsed.scheme == 'https' else 80
params = [
'type="omhttp"',
f'server="{host}"',
f'serverport="{port}"',
f'usehttps="{ssl}"',
f'skipverifyhost="{skip_verify}"',
'action.resumeRetryCount="-1"',
'template="awx"',
'errorfile="/var/log/tower/external.err"',
'healthchecktimeout="20000"',
]
if parsed.path:
params.append(f'restpath="{parsed.path[1:]}"')
username = getattr(settings, 'LOG_AGGREGATOR_USERNAME', '')
password = getattr(settings, 'LOG_AGGREGATOR_PASSWORD', '')
if username:
params.append(f'uid="{username}"')
if username and password:
# you can only have a basic auth password if there's a username
params.append(f'pwd="{password}"')
params = ' '.join(params)
parts.extend(['module(load="omhttp")', f'action({params})'])
elif protocol and host and port:
parts.append(
f'action(type="omfwd" target="{host}" port="{port}" protocol="{protocol}" action.resumeRetryCount="-1" template="awx")' # noqa
)
else:
parts.append(f'action(type="omfile" file="/dev/null")') # rsyslog needs *at least* one valid action to start
tmpl = '\n'.join(parts)
return tmpl
def reconfigure_rsyslog():
tmpl = construct_rsyslog_conf_template()
with open('/var/lib/awx/rsyslog/rsyslog.conf', 'w') as f:
f.write(tmpl + '\n')
supervisor_service_command(command='restart', service='awx-rsyslogd')