Merge pull request #44 from nvllsvm/log_request

Add custom request logging
This commit is contained in:
dave-shawley 2022-03-16 08:39:33 -04:00 committed by GitHub
commit 11315f1653
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 130 additions and 3 deletions

View file

@ -1,9 +1,10 @@
import asyncio import asyncio
import datetime
import logging import logging
import sys import sys
import warnings import warnings
from tornado import concurrent, web from tornado import concurrent, log, web
class _ShutdownHandler: class _ShutdownHandler:
@ -214,6 +215,49 @@ class Application(CallbackManager, web.Application):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs) super().__init__(self, *args, **kwargs)
def log_request(self, handler):
"""Customized access log function.
:param tornado.web.RequestHandler handler:
"""
status = handler.get_status()
if status < 400:
if getattr(handler, 'access_log_failures_only', False):
log_level = logging.DEBUG
else:
log_level = logging.INFO
elif status < 500:
log_level = logging.WARNING
else:
log_level = logging.ERROR
started_at = datetime.datetime.fromtimestamp(
handler.request._start_time, datetime.timezone.utc)
try:
bytes_written = handler.response_bytes_written
except AttributeError:
bytes_written = '-'
log.access_log.log(
log_level,
'%s %s %s [%s] "%s %s %s" %d "%s" %s "%s" "%s" (secs:%.03f)',
handler.request.remote_ip,
'-', # RFC-1413 user identifier
handler.get_current_user() or '-',
started_at.strftime('%d/%b/%Y:%H:%M:%S %z'),
handler.request.method,
handler.request.uri,
handler.request.version,
status,
handler._reason or '-',
bytes_written,
handler.request.headers.get('Referer', '-'),
handler.request.headers.get('User-Agent', '-'),
handler.request.request_time(),
)
class _ApplicationAdapter(CallbackManager): class _ApplicationAdapter(CallbackManager):
""" """

View file

@ -1,16 +1,18 @@
from unittest import mock from unittest import mock
import contextlib import contextlib
import datetime
import distutils.dist import distutils.dist
import distutils.errors import distutils.errors
import json
import logging import logging
import os import os
import json import re
import time import time
import unittest import unittest
import uuid import uuid
import warnings import warnings
from tornado import concurrent, httpserver, httputil, ioloop, testing, web from tornado import concurrent, httpserver, httputil, ioloop, log, testing, web
import sprockets.http.app import sprockets.http.app
import sprockets.http.mixins import sprockets.http.mixins
@ -835,3 +837,84 @@ class ShutdownHandlerTests(unittest.TestCase):
fake_loop.time.return_value = float(shutdown_limit) fake_loop.time.return_value = float(shutdown_limit)
handler._maybe_stop() handler._maybe_stop()
fake_loop.stop.assert_called_once_with() fake_loop.stop.assert_called_once_with()
class AccessLogTests(sprockets.http.testing.SprocketsHttpTestCase):
def get_app(self):
self.app = sprockets.http.app.Application([])
return self.app
def test_that_log_request_uses_expected_format(self):
request = httputil.HTTPServerRequest('GET', '/search?q=42')
request.remote_ip = '1.1.1.1'
request._start_time = time.time()
request.connection = unittest.mock.Mock()
handler = web.RequestHandler(self.app, request)
with self.assertLogs(log.access_log) as context:
self.app.log_request(handler)
when = datetime.datetime.fromtimestamp(request._start_time,
datetime.timezone.utc)
expected_message = re.compile(
r'^%s - - %s "%s %s %s" %d "%s" - "-" "-" \(secs:([^)]*)\)' %
(request.remote_ip,
re.escape(
when.strftime('[%d/%b/%Y:%H:%M:%S %z]')), request.method,
re.escape(request.uri), request.version, handler.get_status(),
handler._reason))
message = context.records[0].getMessage()
match = expected_message.match(message)
if match is None:
self.fail(f'"{message}" did not match "{expected_message}"')
try:
float(match.group(1))
except ValueError:
self.fail(f'Expected {match.group(1)} to be a float')
def test_that_log_request_uses_correct_log_level(self):
expectations = {
200: logging.INFO,
303: logging.INFO,
400: logging.WARNING,
404: logging.WARNING,
500: logging.ERROR,
599: logging.ERROR,
}
request = httputil.HTTPServerRequest('GET', '/search?q=42')
request.remote_ip = '1.1.1.1'
request._start_time = time.time()
request.connection = unittest.mock.Mock()
for status, log_level in expectations.items():
handler = web.RequestHandler(self.app, request)
handler.set_status(status)
with self.assertLogs(log.access_log, log_level) as context:
self.app.log_request(handler)
self.assertEqual(context.records[0].levelno, log_level)
def test_that_log_request_uses_correct_log_level_with_only_failures(self):
expectations = {
200: logging.DEBUG,
303: logging.DEBUG,
400: logging.WARNING,
404: logging.WARNING,
500: logging.ERROR,
599: logging.ERROR,
}
request = httputil.HTTPServerRequest('GET', '/search?q=42')
request.remote_ip = '1.1.1.1'
request._start_time = time.time()
request.connection = unittest.mock.Mock()
for status, log_level in expectations.items():
handler = web.RequestHandler(self.app, request)
handler.access_log_failures_only = True
handler.set_status(status)
with self.assertLogs(log.access_log, log_level) as context:
self.app.log_request(handler)
self.assertEqual(context.records[0].levelno, log_level)