Skip to content

Commit 9483209

Browse files
authored
Extract AccessLog into a separate module (#3336)
1 parent bd224f8 commit 9483209

11 files changed

+421
-406
lines changed

aiohttp/base_protocol.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ class BaseProtocol(asyncio.Protocol):
1010

1111
def __init__(self, loop: Optional[asyncio.AbstractEventLoop]=None) -> None:
1212
if loop is None:
13-
self._loop = asyncio.get_event_loop()
14-
else:
15-
self._loop = loop
13+
loop = asyncio.get_event_loop()
14+
self._loop = loop # type: asyncio.AbstractEventLoop
1615
self._paused = False
1716
self._drain_waiter = None # type: Optional[asyncio.Future[None]]
1817
self._connection_lost = False

aiohttp/client_proto.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import asyncio
12
from contextlib import suppress
3+
from typing import Optional
24

35
from .base_protocol import BaseProtocol
46
from .client_exceptions import (ClientOSError, ClientPayloadError,
@@ -10,7 +12,10 @@
1012
class ResponseHandler(BaseProtocol, DataQueue):
1113
"""Helper class to adapt between Protocol and StreamReader."""
1214

13-
def __init__(self, *, loop=None):
15+
def __init__(self, *,
16+
loop: Optional[asyncio.AbstractEventLoop]=None) -> None:
17+
if loop is None:
18+
loop = asyncio.get_event_loop()
1419
BaseProtocol.__init__(self, loop=loop)
1520
DataQueue.__init__(self, loop=loop)
1621

aiohttp/helpers.py

+4-238
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
import base64
55
import binascii
66
import cgi
7-
import datetime
87
import functools
98
import inspect
10-
import logging
119
import netrc
1210
import os
1311
import re
@@ -19,9 +17,9 @@
1917
from math import ceil
2018
from pathlib import Path
2119
from types import TracebackType
22-
from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator,
23-
List, Mapping, Optional, Pattern, Tuple, Type, TypeVar,
24-
Union, cast)
20+
from typing import (Any, Callable, Dict, Iterable, Iterator, List, # noqa
21+
Mapping, Optional, Pattern, Tuple, Type, TypeVar, Union,
22+
cast)
2523
from urllib.parse import quote
2624
from urllib.request import getproxies
2725

@@ -31,7 +29,6 @@
3129
from yarl import URL
3230

3331
from . import hdrs
34-
from .abc import AbstractAccessLogger
3532
from .log import client_logger
3633
from .typedefs import PathLike # noqa
3734

@@ -51,12 +48,6 @@
5148
from typing_extensions import ContextManager
5249

5350

54-
if TYPE_CHECKING: # pragma: no cover
55-
# run in mypy mode only to prevent circular imports
56-
from .web_request import BaseRequest # noqa
57-
from .web_response import StreamResponse # noqa
58-
59-
6051
_T = TypeVar('_T')
6152

6253

@@ -67,7 +58,7 @@
6758
# for compatibility with older versions
6859
DEBUG = (getattr(sys.flags, 'dev_mode', False) or
6960
(not sys.flags.ignore_environment and
70-
bool(os.environ.get('PYTHONASYNCIODEBUG'))))
61+
bool(os.environ.get('PYTHONASYNCIODEBUG')))) # type: bool
7162

7263

7364
CHAR = set(chr(i) for i in range(0, 128))
@@ -323,231 +314,6 @@ def content_disposition_header(disptype: str,
323314
return value
324315

325316

326-
KeyMethod = namedtuple('KeyMethod', 'key method')
327-
328-
329-
class AccessLogger(AbstractAccessLogger):
330-
"""Helper object to log access.
331-
332-
Usage:
333-
log = logging.getLogger("spam")
334-
log_format = "%a %{User-Agent}i"
335-
access_logger = AccessLogger(log, log_format)
336-
access_logger.log(request, response, time)
337-
338-
Format:
339-
%% The percent sign
340-
%a Remote IP-address (IP-address of proxy if using reverse proxy)
341-
%t Time when the request was started to process
342-
%P The process ID of the child that serviced the request
343-
%r First line of request
344-
%s Response status code
345-
%b Size of response in bytes, including HTTP headers
346-
%T Time taken to serve the request, in seconds
347-
%Tf Time taken to serve the request, in seconds with floating fraction
348-
in .06f format
349-
%D Time taken to serve the request, in microseconds
350-
%{FOO}i request.headers['FOO']
351-
%{FOO}o response.headers['FOO']
352-
%{FOO}e os.environ['FOO']
353-
354-
"""
355-
LOG_FORMAT_MAP = {
356-
'a': 'remote_address',
357-
't': 'request_start_time',
358-
'P': 'process_id',
359-
'r': 'first_request_line',
360-
's': 'response_status',
361-
'b': 'response_size',
362-
'T': 'request_time',
363-
'Tf': 'request_time_frac',
364-
'D': 'request_time_micro',
365-
'i': 'request_header',
366-
'o': 'response_header',
367-
}
368-
369-
LOG_FORMAT = '%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"'
370-
FORMAT_RE = re.compile(r'%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbOD]|Tf?)')
371-
CLEANUP_RE = re.compile(r'(%[^s])')
372-
_FORMAT_CACHE = {} # type: Dict[str, Tuple[str, List[KeyMethod]]]
373-
374-
def __init__(self, logger: logging.Logger,
375-
log_format: str=LOG_FORMAT) -> None:
376-
"""Initialise the logger.
377-
378-
logger is a logger object to be used for logging.
379-
log_format is an string with apache compatible log format description.
380-
381-
"""
382-
super().__init__(logger, log_format=log_format)
383-
384-
_compiled_format = AccessLogger._FORMAT_CACHE.get(log_format)
385-
if not _compiled_format:
386-
_compiled_format = self.compile_format(log_format)
387-
AccessLogger._FORMAT_CACHE[log_format] = _compiled_format
388-
389-
self._log_format, self._methods = _compiled_format
390-
391-
def compile_format(self, log_format: str) -> Tuple[str, List[KeyMethod]]:
392-
"""Translate log_format into form usable by modulo formatting
393-
394-
All known atoms will be replaced with %s
395-
Also methods for formatting of those atoms will be added to
396-
_methods in appropriate order
397-
398-
For example we have log_format = "%a %t"
399-
This format will be translated to "%s %s"
400-
Also contents of _methods will be
401-
[self._format_a, self._format_t]
402-
These method will be called and results will be passed
403-
to translated string format.
404-
405-
Each _format_* method receive 'args' which is list of arguments
406-
given to self.log
407-
408-
Exceptions are _format_e, _format_i and _format_o methods which
409-
also receive key name (by functools.partial)
410-
411-
"""
412-
# list of (key, method) tuples, we don't use an OrderedDict as users
413-
# can repeat the same key more than once
414-
methods = list()
415-
416-
for atom in self.FORMAT_RE.findall(log_format):
417-
if atom[1] == '':
418-
format_key1 = self.LOG_FORMAT_MAP[atom[0]]
419-
m = getattr(AccessLogger, '_format_%s' % atom[0])
420-
key_method = KeyMethod(format_key1, m)
421-
else:
422-
format_key2 = (self.LOG_FORMAT_MAP[atom[2]], atom[1])
423-
m = getattr(AccessLogger, '_format_%s' % atom[2])
424-
key_method = KeyMethod(format_key2,
425-
functools.partial(m, atom[1]))
426-
427-
methods.append(key_method)
428-
429-
log_format = self.FORMAT_RE.sub(r'%s', log_format)
430-
log_format = self.CLEANUP_RE.sub(r'%\1', log_format)
431-
return log_format, methods
432-
433-
@staticmethod
434-
def _format_i(key: str,
435-
request: 'BaseRequest',
436-
response: 'StreamResponse',
437-
time: float) -> str:
438-
if request is None:
439-
return '(no headers)'
440-
441-
# suboptimal, make istr(key) once
442-
return request.headers.get(key, '-')
443-
444-
@staticmethod
445-
def _format_o(key: str,
446-
request: 'BaseRequest',
447-
response: 'StreamResponse',
448-
time: float) -> str:
449-
# suboptimal, make istr(key) once
450-
return response.headers.get(key, '-')
451-
452-
@staticmethod
453-
def _format_a(request: 'BaseRequest',
454-
response: 'StreamResponse',
455-
time: float) -> str:
456-
if request is None:
457-
return '-'
458-
ip = request.remote
459-
return ip if ip is not None else '-'
460-
461-
@staticmethod
462-
def _format_t(request: 'BaseRequest',
463-
response: 'StreamResponse',
464-
time: float) -> str:
465-
now = datetime.datetime.utcnow()
466-
start_time = now - datetime.timedelta(seconds=time)
467-
return start_time.strftime('[%d/%b/%Y:%H:%M:%S +0000]')
468-
469-
@staticmethod
470-
def _format_P(request: 'BaseRequest',
471-
response: 'StreamResponse',
472-
time: float) -> str:
473-
return "<%s>" % os.getpid()
474-
475-
@staticmethod
476-
def _format_r(request: 'BaseRequest',
477-
response: 'StreamResponse',
478-
time: float) -> str:
479-
if request is None:
480-
return '-'
481-
return '%s %s HTTP/%s.%s' % (request.method, request.path_qs,
482-
request.version.major,
483-
request.version.minor)
484-
485-
@staticmethod
486-
def _format_s(request: 'BaseRequest',
487-
response: 'StreamResponse',
488-
time: float) -> str:
489-
return response.status
490-
491-
@staticmethod
492-
def _format_b(request: 'BaseRequest',
493-
response: 'StreamResponse',
494-
time: float) -> str:
495-
return response.body_length
496-
497-
@staticmethod
498-
def _format_T(request: 'BaseRequest',
499-
response: 'StreamResponse',
500-
time: float) -> str:
501-
return str(round(time))
502-
503-
@staticmethod
504-
def _format_Tf(request: 'BaseRequest',
505-
response: 'StreamResponse',
506-
time: float) -> str:
507-
return '%06f' % time
508-
509-
@staticmethod
510-
def _format_D(request: 'BaseRequest',
511-
response: 'StreamResponse',
512-
time: float) -> str:
513-
return str(round(time * 1000000))
514-
515-
def _format_line(self,
516-
request: 'BaseRequest',
517-
response: 'StreamResponse',
518-
time: float) -> Iterable[Tuple[str,
519-
Callable[['BaseRequest',
520-
'StreamResponse',
521-
float],
522-
str]]]:
523-
return [(key, method(request, response, time))
524-
for key, method in self._methods]
525-
526-
def log(self,
527-
request: 'BaseRequest',
528-
response: 'StreamResponse',
529-
time: float) -> None:
530-
try:
531-
fmt_info = self._format_line(request, response, time)
532-
533-
values = list()
534-
extra = dict()
535-
for key, value in fmt_info:
536-
values.append(value)
537-
538-
if key.__class__ is str:
539-
extra[key] = value
540-
else:
541-
k1, k2 = key
542-
dct = extra.get(k1, {})
543-
dct[k2] = value # type: ignore
544-
extra[k1] = dct # type: ignore
545-
546-
self.logger.info(self._log_format % tuple(values), extra=extra)
547-
except Exception:
548-
self.logger.exception("Error in logging")
549-
550-
551317
class reify:
552318
"""Use as a class method decorator. It operates almost exactly like
553319
the Python `@property` decorator, but it puts the result of the

aiohttp/web.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
from collections.abc import Iterable
77
from importlib import import_module
88

9-
from . import (helpers, web_app, web_exceptions, web_fileresponse,
10-
web_middlewares, web_protocol, web_request, web_response,
11-
web_routedef, web_runner, web_server, web_urldispatcher, web_ws)
9+
from . import (web_app, web_exceptions, web_fileresponse, web_middlewares,
10+
web_protocol, web_request, web_response, web_routedef,
11+
web_runner, web_server, web_urldispatcher, web_ws)
1212
from .log import access_logger
1313
from .web_app import * # noqa
1414
from .web_exceptions import * # noqa
1515
from .web_fileresponse import * # noqa
16+
from .web_log import AccessLogger
1617
from .web_middlewares import * # noqa
1718
from .web_protocol import * # noqa
1819
from .web_request import * # noqa
@@ -42,8 +43,8 @@
4243

4344
def run_app(app, *, host=None, port=None, path=None, sock=None,
4445
shutdown_timeout=60.0, ssl_context=None,
45-
print=print, backlog=128, access_log_class=helpers.AccessLogger,
46-
access_log_format=helpers.AccessLogger.LOG_FORMAT,
46+
print=print, backlog=128, access_log_class=AccessLogger,
47+
access_log_format=AccessLogger.LOG_FORMAT,
4748
access_log=access_logger, handle_signals=True,
4849
reuse_address=None, reuse_port=None):
4950
"""Run an app locally"""

aiohttp/web_app.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
from . import hdrs
1010
from .abc import AbstractAccessLogger, AbstractMatchInfo, AbstractRouter
1111
from .frozenlist import FrozenList
12-
from .helpers import DEBUG, AccessLogger
12+
from .helpers import DEBUG
1313
from .log import web_logger
1414
from .signals import Signal
15+
from .web_log import AccessLogger
1516
from .web_middlewares import _fix_request_current_app
1617
from .web_request import Request
1718
from .web_response import StreamResponse

0 commit comments

Comments
 (0)