Repository: aio-libs/aiohttp

Issue:
web access log show utc time instead local time with timezone.
## Long story short
in web_.log.py:
```
    def _format_t(request: BaseRequest,
                  response: StreamResponse,
                  time: float) -> str:
        now = datetime.datetime.utcnow()
        start_time = now - datetime.timedelta(seconds=time)
        return start_time.strftime('[%d/%b/%Y:%H:%M:%S +0000]')
```
the logged time is fixed and is a utc time, not a local time. there's not a easy way to setting it.

## Expected behaviour

```
INFO:aiohttp.access:127.0.0.1 [18/Jun/2019:04:56:22 -8]
```
## Actual behaviour

```
INFO:aiohttp.access:127.0.0.1 [18/Jun/2019:04:56:22 +0000]
```


Discussion:
UTC time is intentional.
It's nice to make it customizable. I see it being useful during the development in particular.

@NewUserHa feel free to research improving this and send a PR.
nginx's access.log is `[16/Jun/2019:15:25:04 +0800]`

what about `making _format_t` to obey OS or python's timezone info? I doubt adding a parameter for customing will work.
What is `apache` behavior?
`127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326` from https://httpd.apache.org/docs/2.4/logs.html
Ok, I have no objections for changing defaults to use local time instead of UTC. The output should include a timezone offset like @NewUserHa mentioned: `[16/Jun/2019:15:25:04 +0800]`
I would like to PR this too. (though the other one still is not finished)...

what about obeying OS or python's timezone info? need import like pytz module for ease?
Don't follow what is the difference between OS and python TZ info?
Please no `pytz` if we can avoid the library (I think we cannot need if for local timezone-aware time).
hmm.. maybe someone has different values for OS and python TZ info.
ok.

Resolving patch:
diff --git a/aiohttp/web_log.py b/aiohttp/web_log.py
--- a/aiohttp/web_log.py
+++ b/aiohttp/web_log.py
@@ -4,6 +4,7 @@
 import os
 import re
 from collections import namedtuple
+from time import timezone
 from typing import Callable, Dict, Iterable, List, Tuple  # noqa
 
 from .abc import AbstractAccessLogger
@@ -149,9 +150,10 @@ def _format_a(request: BaseRequest,
     def _format_t(request: BaseRequest,
                   response: StreamResponse,
                   time: float) -> str:
-        now = datetime.datetime.utcnow()
+        tz = datetime.timezone(datetime.timedelta(seconds=-timezone))
+        now = datetime.datetime.now(tz)
         start_time = now - datetime.timedelta(seconds=time)
-        return start_time.strftime('[%d/%b/%Y:%H:%M:%S +0000]')
+        return start_time.strftime('[%d/%b/%Y:%H:%M:%S %z]')
 
     @staticmethod
     def _format_P(request: BaseRequest,


Test patch:
diff --git a/tests/test_web_log.py b/tests/test_web_log.py
--- a/tests/test_web_log.py
+++ b/tests/test_web_log.py
@@ -43,13 +43,41 @@ def test_access_logger_format() -> None:
     Ref: https://stackoverflow.com/a/46102240/595220
     """,  # noqa: E501
 )
-def test_access_logger_atoms(mocker) -> None:
-    utcnow = datetime.datetime(1843, 1, 1, 0, 30)
-    mock_datetime = mocker.patch("aiohttp.datetime.datetime")
-    mock_getpid = mocker.patch("os.getpid")
-    mock_datetime.utcnow.return_value = utcnow
-    mock_getpid.return_value = 42
-    log_format = '%a %t %P %r %s %b %T %Tf %D "%{H1}i" "%{H2}i"'
+@pytest.mark.parametrize(
+    'log_format,expected,extra',
+    [
+        ('%t',
+         '[01/Jan/1843:00:29:56 +0800]',
+         {
+             'request_start_time': '[01/Jan/1843:00:29:56 +0800]'
+         }
+         ),
+        ('%a %t %P %r %s %b %T %Tf %D "%{H1}i" "%{H2}i"',
+         ('127.0.0.2 [01/Jan/1843:00:29:56 +0000] <42> '
+          'GET /path HTTP/1.1 200 42 3 3.141593 3141593 "a" "b"'),
+         {
+             'first_request_line': 'GET /path HTTP/1.1',
+             'process_id': '<42>',
+             'remote_address': '127.0.0.2',
+             'request_start_time': '[01/Jan/1843:00:29:56 +0000]',
+             'request_time': 3,
+             'request_time_frac': '3.141593',
+             'request_time_micro': 3141593,
+             'response_size': 42,
+             'response_status': 200,
+             'request_header': {'H1': 'a', 'H2': 'b'},
+         }
+         )
+    ]
+)
+def test_access_logger_atoms(monkeypatch, log_format, expected, extra) -> None:
+    class PatchedDatetime(datetime.datetime):
+        @staticmethod
+        def now(tz):
+            return datetime.datetime(1843, 1, 1, 0, 30, tzinfo=tz)
+    monkeypatch.setattr("datetime.datetime", PatchedDatetime)
+    monkeypatch.setattr("time.timezone", -28800)
+    monkeypatch.setattr("os.getpid", lambda: 42)
     mock_logger = mock.Mock()
     access_logger = AccessLogger(mock_logger, log_format)
     request = mock.Mock(headers={'H1': 'a', 'H2': 'b'},
@@ -59,20 +87,6 @@ def test_access_logger_atoms(mocker) -> None:
     response = mock.Mock(headers={}, body_length=42, status=200)
     access_logger.log(request, response, 3.1415926)
     assert not mock_logger.exception.called
-    expected = ('127.0.0.2 [01/Jan/1843:00:29:56 +0000] <42> '
-                'GET /path HTTP/1.1 200 42 3 3.141593 3141593 "a" "b"')
-    extra = {
-        'first_request_line': 'GET /path HTTP/1.1',
-        'process_id': '<42>',
-        'remote_address': '127.0.0.2',
-        'request_start_time': '[01/Jan/1843:00:29:56 +0000]',
-        'request_time': 3,
-        'request_time_frac': '3.141593',
-        'request_time_micro': 3141593,
-        'response_size': 42,
-        'response_status': 200,
-        'request_header': {'H1': 'a', 'H2': 'b'},
-    }
 
     mock_logger.info.assert_called_with(expected, extra=extra)