Merge lp:~allenap/maas/make-web-access-logs-prettier into lp:~maas-committers/maas/trunk
- make-web-access-logs-prettier
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 5897 |
Proposed branch: | lp:~allenap/maas/make-web-access-logs-prettier |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
366 lines (+207/-6) 6 files modified
src/maasserver/tests/test_webapp.py (+5/-2) src/maasserver/webapp.py (+5/-2) src/provisioningserver/rackdservices/image.py (+2/-1) src/provisioningserver/tests/test_plugin.py (+4/-0) src/provisioningserver/utils/tests/test_twisted.py (+124/-1) src/provisioningserver/utils/twisted.py (+67/-0) |
To merge this branch: | bzr merge lp:~allenap/maas/make-web-access-logs-prettier |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mike Pontillo (community) | Approve | ||
Review via email: mp+321495@code.launchpad.net |
Commit message
Make web access log lines prettier and more readable.
Description of the change
Transforms:
2017-03-30 23:01:26 regiond: [info] ::ffff:192.168.1.3 - - [30/Mar/
into:
2017-03-30 23:01:27 regiond: [info] 192.168.1.3 GET /MAAS/rpc/ HTTP/1.0 --> 200 OK (referrer: -; agent: provisioningser
i.e. gets rid of the duplicated timestamp, reorders stuff a bit, and un-IPv6-ifies IPv4 addresses.
Gavin Panella (allenap) wrote : | # |
There is utility in the combined log format, which is what Twisted claimed it was producing, but that's reduced because we're publishing it to a "regular" log, where a namespace and level are added, and an additional timestamp. Tools designed for combined logs can't operate on it without pre-processing. In 5 years we haven't shown an interest in analysing this stuff, and there's not much HTTP traffic anyway: the UI is mostly WebSocket traffic and that is not logged. Then, most HTTP traffic is to /MAAS/rpc/ by racks.
To keep access logs for analysis we should pipe them to their own file. t.w.server.Site would prefer to do this: pushing access logs out via the "normal" loggers is a getting-started kind of mode.
But I avoided this because it goes close to the existing can of logging worms (i.e. logging both to regiond.log and journald), because it would need fiddly packaging work (inc. permissions, rotation), and because no one is yet asking for it.
Or, we could override Site.log to publish structured logs from which we can extract, for example, request errors, and produce some kind of in-app report.
In short, here I've tried to optimise for human consumption. We're not shutting the door to other, e.g. machine readable, options later on if we find a need.
Mike Pontillo (mpontillo) wrote : | # |
Sounds good. +1 from me.
Gavin Panella (allenap) wrote : | # |
Thanks!
Preview Diff
1 | === modified file 'src/maasserver/tests/test_webapp.py' | |||
2 | --- src/maasserver/tests/test_webapp.py 2017-03-03 17:20:13 +0000 | |||
3 | +++ src/maasserver/tests/test_webapp.py 2017-03-30 22:13:21 +0000 | |||
4 | @@ -23,6 +23,7 @@ | |||
5 | 23 | from maastesting.matchers import MockCalledOnceWith | 23 | from maastesting.matchers import MockCalledOnceWith |
6 | 24 | from maastesting.testcase import MAASTestCase | 24 | from maastesting.testcase import MAASTestCase |
7 | 25 | from maastesting.twisted import TwistedLoggerFixture | 25 | from maastesting.twisted import TwistedLoggerFixture |
8 | 26 | from provisioningserver.utils.twisted import reducedWebLogFormatter | ||
9 | 26 | from testtools.matchers import ( | 27 | from testtools.matchers import ( |
10 | 27 | Equals, | 28 | Equals, |
11 | 28 | Is, | 29 | Is, |
12 | @@ -205,8 +206,10 @@ | |||
13 | 205 | def test__init_creates_site(self): | 206 | def test__init_creates_site(self): |
14 | 206 | service = self.make_webapp() | 207 | service = self.make_webapp() |
15 | 207 | self.assertThat(service.site, IsInstance(Site)) | 208 | self.assertThat(service.site, IsInstance(Site)) |
18 | 208 | self.assertThat( | 209 | self.assertThat(service.site, MatchesStructure( |
19 | 209 | service.site.requestFactory, Is(webapp.CleanPathRequest)) | 210 | requestFactory=Is(webapp.CleanPathRequest), |
20 | 211 | _logFormatter=Is(reducedWebLogFormatter), | ||
21 | 212 | )) | ||
22 | 210 | self.assertThat(service.websocket, IsInstance(WebSocketFactory)) | 213 | self.assertThat(service.websocket, IsInstance(WebSocketFactory)) |
23 | 211 | 214 | ||
24 | 212 | def test__default_site_renders_starting_page(self): | 215 | def test__default_site_renders_starting_page(self): |
25 | 213 | 216 | ||
26 | === modified file 'src/maasserver/webapp.py' | |||
27 | --- src/maasserver/webapp.py 2017-03-03 17:07:03 +0000 | |||
28 | +++ src/maasserver/webapp.py 2017-03-30 22:13:21 +0000 | |||
29 | @@ -25,6 +25,7 @@ | |||
30 | 25 | from provisioningserver.logger import LegacyLogger | 25 | from provisioningserver.logger import LegacyLogger |
31 | 26 | from provisioningserver.utils.twisted import ( | 26 | from provisioningserver.utils.twisted import ( |
32 | 27 | asynchronous, | 27 | asynchronous, |
33 | 28 | reducedWebLogFormatter, | ||
34 | 28 | ThreadPoolLimiter, | 29 | ThreadPoolLimiter, |
35 | 29 | ) | 30 | ) |
36 | 30 | from twisted.application.internet import StreamServerEndpointService | 31 | from twisted.application.internet import StreamServerEndpointService |
37 | @@ -184,7 +185,8 @@ | |||
38 | 184 | """ | 185 | """ |
39 | 185 | 186 | ||
40 | 186 | def __init__(self, endpoint, listener, status_worker): | 187 | def __init__(self, endpoint, listener, status_worker): |
42 | 187 | self.site = OverlaySite(StartPage()) | 188 | self.site = OverlaySite( |
43 | 189 | StartPage(), logFormatter=reducedWebLogFormatter) | ||
44 | 188 | self.site.requestFactory = CleanPathRequest | 190 | self.site.requestFactory = CleanPathRequest |
45 | 189 | super(WebApplicationService, self).__init__(endpoint, self.site) | 191 | super(WebApplicationService, self).__init__(endpoint, self.site) |
46 | 190 | self.websocket = WebSocketFactory(listener) | 192 | self.websocket = WebSocketFactory(listener) |
47 | @@ -235,7 +237,8 @@ | |||
48 | 235 | WSGIResource(reactor, self.threadpool, application)) | 237 | WSGIResource(reactor, self.threadpool, application)) |
49 | 236 | underlay_root = Resource() | 238 | underlay_root = Resource() |
50 | 237 | underlay_root.putChild(b'MAAS', underlay_maas) | 239 | underlay_root.putChild(b'MAAS', underlay_maas) |
52 | 238 | underlay_site = Site(underlay_root) | 240 | underlay_site = Site( |
53 | 241 | underlay_root, logFormatter=reducedWebLogFormatter) | ||
54 | 239 | underlay_site.requestFactory = CleanPathRequest | 242 | underlay_site.requestFactory = CleanPathRequest |
55 | 240 | 243 | ||
56 | 241 | # Setup the main resource as the twisted handler and the underlay | 244 | # Setup the main resource as the twisted handler and the underlay |
57 | 242 | 245 | ||
58 | === modified file 'src/provisioningserver/rackdservices/image.py' | |||
59 | --- src/provisioningserver/rackdservices/image.py 2015-12-16 14:24:50 +0000 | |||
60 | +++ src/provisioningserver/rackdservices/image.py 2017-03-30 22:13:21 +0000 | |||
61 | @@ -7,6 +7,7 @@ | |||
62 | 7 | "BootImageEndpointService", | 7 | "BootImageEndpointService", |
63 | 8 | ] | 8 | ] |
64 | 9 | 9 | ||
65 | 10 | from provisioningserver.utils.twisted import reducedWebLogFormatter | ||
66 | 10 | from twisted.application.internet import StreamServerEndpointService | 11 | from twisted.application.internet import StreamServerEndpointService |
67 | 11 | from twisted.web.resource import Resource | 12 | from twisted.web.resource import Resource |
68 | 12 | from twisted.web.server import Site | 13 | from twisted.web.server import Site |
69 | @@ -28,5 +29,5 @@ | |||
70 | 28 | """ | 29 | """ |
71 | 29 | resource = Resource() | 30 | resource = Resource() |
72 | 30 | resource.putChild(b'images', File(resource_root)) | 31 | resource.putChild(b'images', File(resource_root)) |
74 | 31 | self.site = Site(resource) | 32 | self.site = Site(resource, logFormatter=reducedWebLogFormatter) |
75 | 32 | super(BootImageEndpointService, self).__init__(endpoint, self.site) | 33 | super(BootImageEndpointService, self).__init__(endpoint, self.site) |
76 | 33 | 34 | ||
77 | === modified file 'src/provisioningserver/tests/test_plugin.py' | |||
78 | --- src/provisioningserver/tests/test_plugin.py 2017-01-28 00:51:47 +0000 | |||
79 | +++ src/provisioningserver/tests/test_plugin.py 2017-03-30 22:13:21 +0000 | |||
80 | @@ -46,10 +46,12 @@ | |||
81 | 46 | ) | 46 | ) |
82 | 47 | from provisioningserver.rackdservices.tftp_offload import TFTPOffloadService | 47 | from provisioningserver.rackdservices.tftp_offload import TFTPOffloadService |
83 | 48 | from provisioningserver.testing.config import ClusterConfigurationFixture | 48 | from provisioningserver.testing.config import ClusterConfigurationFixture |
84 | 49 | from provisioningserver.utils.twisted import reducedWebLogFormatter | ||
85 | 49 | from testtools.matchers import ( | 50 | from testtools.matchers import ( |
86 | 50 | AfterPreprocessing, | 51 | AfterPreprocessing, |
87 | 51 | Contains, | 52 | Contains, |
88 | 52 | Equals, | 53 | Equals, |
89 | 54 | Is, | ||
90 | 53 | IsInstance, | 55 | IsInstance, |
91 | 54 | KeysEqual, | 56 | KeysEqual, |
92 | 55 | MatchesAll, | 57 | MatchesAll, |
93 | @@ -206,6 +208,8 @@ | |||
94 | 206 | image_service = service.getServiceNamed("image_service") | 208 | image_service = service.getServiceNamed("image_service") |
95 | 207 | self.assertIsInstance(image_service, BootImageEndpointService) | 209 | self.assertIsInstance(image_service, BootImageEndpointService) |
96 | 208 | self.assertIsInstance(image_service.site, Site) | 210 | self.assertIsInstance(image_service.site, Site) |
97 | 211 | self.assertThat(image_service.site, MatchesStructure( | ||
98 | 212 | _logFormatter=Is(reducedWebLogFormatter))) | ||
99 | 209 | resource = image_service.site.resource | 213 | resource = image_service.site.resource |
100 | 210 | root = resource.getChildWithDefault(b"images", request=None) | 214 | root = resource.getChildWithDefault(b"images", request=None) |
101 | 211 | self.assertThat(root, IsInstance(FilePath)) | 215 | self.assertThat(root, IsInstance(FilePath)) |
102 | 212 | 216 | ||
103 | === modified file 'src/provisioningserver/utils/tests/test_twisted.py' | |||
104 | --- src/provisioningserver/utils/tests/test_twisted.py 2017-03-24 15:28:07 +0000 | |||
105 | +++ src/provisioningserver/utils/tests/test_twisted.py 2017-03-30 22:13:21 +0000 | |||
106 | @@ -63,6 +63,7 @@ | |||
107 | 63 | LONGTIME, | 63 | LONGTIME, |
108 | 64 | makeDeferredWithProcessProtocol, | 64 | makeDeferredWithProcessProtocol, |
109 | 65 | pause, | 65 | pause, |
110 | 66 | reducedWebLogFormatter, | ||
111 | 66 | retries, | 67 | retries, |
112 | 67 | RPCFetcher, | 68 | RPCFetcher, |
113 | 68 | synchronous, | 69 | synchronous, |
114 | @@ -71,6 +72,7 @@ | |||
115 | 71 | ThreadPoolLimiter, | 72 | ThreadPoolLimiter, |
116 | 72 | ThreadUnpool, | 73 | ThreadUnpool, |
117 | 73 | ) | 74 | ) |
118 | 75 | from testscenarios import multiply_scenarios | ||
119 | 74 | from testtools.content import content_from_stream | 76 | from testtools.content import content_from_stream |
120 | 75 | from testtools.deferredruntest import assert_fails_with | 77 | from testtools.deferredruntest import assert_fails_with |
121 | 76 | from testtools.matchers import ( | 78 | from testtools.matchers import ( |
122 | @@ -88,7 +90,10 @@ | |||
123 | 88 | Raises, | 90 | Raises, |
124 | 89 | ) | 91 | ) |
125 | 90 | from testtools.testcase import ExpectedException | 92 | from testtools.testcase import ExpectedException |
127 | 91 | from twisted.internet import reactor | 93 | from twisted.internet import ( |
128 | 94 | address, | ||
129 | 95 | reactor, | ||
130 | 96 | ) | ||
131 | 92 | from twisted.internet.defer import ( | 97 | from twisted.internet.defer import ( |
132 | 93 | AlreadyCalledError, | 98 | AlreadyCalledError, |
133 | 94 | CancelledError, | 99 | CancelledError, |
134 | @@ -112,6 +117,7 @@ | |||
135 | 112 | threadable, | 117 | threadable, |
136 | 113 | ) | 118 | ) |
137 | 114 | from twisted.python.failure import Failure | 119 | from twisted.python.failure import Failure |
138 | 120 | from twisted.web.test import requesthelper | ||
139 | 115 | 121 | ||
140 | 116 | 122 | ||
141 | 117 | def return_args(*args, **kwargs): | 123 | def return_args(*args, **kwargs): |
142 | @@ -1883,3 +1889,120 @@ | |||
143 | 1883 | )) | 1889 | )) |
144 | 1884 | self.assertThat( | 1890 | self.assertThat( |
145 | 1885 | twisted_module._os_killpg, MockNotCalled()) | 1891 | twisted_module._os_killpg, MockNotCalled()) |
146 | 1892 | |||
147 | 1893 | |||
148 | 1894 | class TestReducedWebLogFormatter(MAASTestCase): | ||
149 | 1895 | """Tests for `reducedWebLogFormatter`.""" | ||
150 | 1896 | |||
151 | 1897 | ipv4_address = factory.make_ipv4_address() | ||
152 | 1898 | ipv6_address = factory.make_ipv6_address() | ||
153 | 1899 | simple_http_url = factory.make_simple_http_url() | ||
154 | 1900 | simple_uri = factory.make_absolute_path() | ||
155 | 1901 | agent_name = factory.make_name("agent") | ||
156 | 1902 | status = factory.make_status_code() | ||
157 | 1903 | |||
158 | 1904 | scenarios_methods = ( | ||
159 | 1905 | ("no-method", {"method": None, "method_expected": "???"}), | ||
160 | 1906 | ("method", {"method": "GET", "method_expected": "GET"}), | ||
161 | 1907 | ) | ||
162 | 1908 | |||
163 | 1909 | scenarios_clients = ( | ||
164 | 1910 | ("no-client", { | ||
165 | 1911 | "client": None, | ||
166 | 1912 | "client_expected": "-", | ||
167 | 1913 | }), | ||
168 | 1914 | ("ipv4-client", { | ||
169 | 1915 | "client": ipv4_address, | ||
170 | 1916 | "client_expected": ipv4_address, | ||
171 | 1917 | }), | ||
172 | 1918 | ("ipv6-client", { | ||
173 | 1919 | "client": ipv6_address, | ||
174 | 1920 | "client_expected": ipv6_address, | ||
175 | 1921 | }), | ||
176 | 1922 | ("ipv4-mapped-client", { | ||
177 | 1923 | "client": "::ffff:" + ipv4_address, | ||
178 | 1924 | "client_expected": ipv4_address, | ||
179 | 1925 | }), | ||
180 | 1926 | ) | ||
181 | 1927 | |||
182 | 1928 | scenarios_referrers = ( | ||
183 | 1929 | ("no-referrer", { | ||
184 | 1930 | "referrer": None, | ||
185 | 1931 | "referrer_expected": "-", | ||
186 | 1932 | }), | ||
187 | 1933 | ("referrer", { | ||
188 | 1934 | "referrer": simple_http_url, | ||
189 | 1935 | "referrer_expected": simple_http_url, | ||
190 | 1936 | }), | ||
191 | 1937 | ) | ||
192 | 1938 | |||
193 | 1939 | scenarios_agents = ( | ||
194 | 1940 | ("no-agent", { | ||
195 | 1941 | "agent": None, | ||
196 | 1942 | "agent_expected": "-", | ||
197 | 1943 | }), | ||
198 | 1944 | ("agent", { | ||
199 | 1945 | "agent": agent_name, | ||
200 | 1946 | "agent_expected": agent_name, | ||
201 | 1947 | }), | ||
202 | 1948 | ) | ||
203 | 1949 | |||
204 | 1950 | scenarios_uris = ( | ||
205 | 1951 | ("no-uri", { | ||
206 | 1952 | "uri": None, | ||
207 | 1953 | "uri_expected": "-", | ||
208 | 1954 | }), | ||
209 | 1955 | ("uri", { | ||
210 | 1956 | "uri": simple_uri, | ||
211 | 1957 | "uri_expected": simple_uri, | ||
212 | 1958 | }), | ||
213 | 1959 | ) | ||
214 | 1960 | |||
215 | 1961 | scenarios_statuses = ( | ||
216 | 1962 | ("no-status", { | ||
217 | 1963 | "status": None, | ||
218 | 1964 | "status_expected": "???", | ||
219 | 1965 | }), | ||
220 | 1966 | ("status", { | ||
221 | 1967 | "status": status.value, | ||
222 | 1968 | "status_expected": "%d %s" % (status.value, status.name), | ||
223 | 1969 | }), | ||
224 | 1970 | ("status-unknown", { | ||
225 | 1971 | "status": 678, | ||
226 | 1972 | "status_expected": "678", | ||
227 | 1973 | }), | ||
228 | 1974 | ) | ||
229 | 1975 | |||
230 | 1976 | scenarios_types = ( | ||
231 | 1977 | ("plain", {"prep": lambda string: string}), | ||
232 | 1978 | ("bytes", {"prep": lambda string: ( | ||
233 | 1979 | None if string is None else string.encode("ascii"))}), | ||
234 | 1980 | ) | ||
235 | 1981 | |||
236 | 1982 | scenarios = multiply_scenarios( | ||
237 | 1983 | scenarios_methods, scenarios_clients, scenarios_referrers, | ||
238 | 1984 | scenarios_agents, scenarios_uris, scenarios_statuses, scenarios_types, | ||
239 | 1985 | ) | ||
240 | 1986 | |||
241 | 1987 | def test__renders_full_request(self): | ||
242 | 1988 | request = requesthelper.DummyRequest("foo/bar") | ||
243 | 1989 | request.method = self.prep(self.method) | ||
244 | 1990 | request.client = address.IPv4Address( | ||
245 | 1991 | "TCP", self.prep(self.client), 12345) | ||
246 | 1992 | request.requestHeaders.addRawHeader( | ||
247 | 1993 | "referer", self.prep(self.referrer)) | ||
248 | 1994 | request.requestHeaders.addRawHeader( | ||
249 | 1995 | "user-agent", self.prep(self.agent)) | ||
250 | 1996 | request.uri = self.prep(self.uri) | ||
251 | 1997 | request.code = self.status | ||
252 | 1998 | |||
253 | 1999 | self.assertThat( | ||
254 | 2000 | reducedWebLogFormatter(sentinel.timestamp, request), | ||
255 | 2001 | Equals( | ||
256 | 2002 | "%s %s %s HTTP/1.0 --> %s (referrer: %s; agent: %s)" % ( | ||
257 | 2003 | self.client_expected, self.method_expected, | ||
258 | 2004 | self.uri_expected, self.status_expected, | ||
259 | 2005 | self.referrer_expected, self.agent_expected, | ||
260 | 2006 | ) | ||
261 | 2007 | ), | ||
262 | 2008 | ) | ||
263 | 1886 | 2009 | ||
264 | === modified file 'src/provisioningserver/utils/twisted.py' | |||
265 | --- src/provisioningserver/utils/twisted.py 2017-03-24 15:28:07 +0000 | |||
266 | +++ src/provisioningserver/utils/twisted.py 2017-03-30 22:13:21 +0000 | |||
267 | @@ -20,6 +20,7 @@ | |||
268 | 20 | 'LONGTIME', | 20 | 'LONGTIME', |
269 | 21 | 'makeDeferredWithProcessProtocol', | 21 | 'makeDeferredWithProcessProtocol', |
270 | 22 | 'pause', | 22 | 'pause', |
271 | 23 | 'reducedWebLogFormatter', | ||
272 | 23 | 'retries', | 24 | 'retries', |
273 | 24 | 'synchronous', | 25 | 'synchronous', |
274 | 25 | 'ThreadPool', | 26 | 'ThreadPool', |
275 | @@ -35,6 +36,7 @@ | |||
276 | 35 | partial, | 36 | partial, |
277 | 36 | wraps, | 37 | wraps, |
278 | 37 | ) | 38 | ) |
279 | 39 | from http import HTTPStatus | ||
280 | 38 | from itertools import ( | 40 | from itertools import ( |
281 | 39 | chain, | 41 | chain, |
282 | 40 | repeat, | 42 | repeat, |
283 | @@ -50,6 +52,10 @@ | |||
284 | 50 | import threading | 52 | import threading |
285 | 51 | 53 | ||
286 | 52 | from crochet import run_in_reactor | 54 | from crochet import run_in_reactor |
287 | 55 | from netaddr import ( | ||
288 | 56 | AddrFormatError, | ||
289 | 57 | IPAddress, | ||
290 | 58 | ) | ||
291 | 53 | from provisioningserver.logger import LegacyLogger | 59 | from provisioningserver.logger import LegacyLogger |
292 | 54 | from twisted.internet import reactor | 60 | from twisted.internet import reactor |
293 | 55 | from twisted.internet.defer import ( | 61 | from twisted.internet.defer import ( |
294 | @@ -70,7 +76,9 @@ | |||
295 | 70 | ) | 76 | ) |
296 | 71 | from twisted.python.failure import Failure | 77 | from twisted.python.failure import Failure |
297 | 72 | from twisted.python.threadable import isInIOThread | 78 | from twisted.python.threadable import isInIOThread |
298 | 79 | from twisted.web.iweb import IAccessLogFormatter | ||
299 | 73 | from zope import interface | 80 | from zope import interface |
300 | 81 | from zope.interface import provider | ||
301 | 74 | 82 | ||
302 | 75 | 83 | ||
303 | 76 | log = LegacyLogger() | 84 | log = LegacyLogger() |
304 | @@ -1043,3 +1051,62 @@ | |||
305 | 1043 | killer.cancel() | 1051 | killer.cancel() |
306 | 1044 | 1052 | ||
307 | 1045 | done.addBoth(callOut, ended) | 1053 | done.addBoth(callOut, ended) |
308 | 1054 | |||
309 | 1055 | |||
310 | 1056 | @provider(IAccessLogFormatter) | ||
311 | 1057 | def reducedWebLogFormatter(timestamp, request): | ||
312 | 1058 | """Return a reduced formatted log line for the given request. | ||
313 | 1059 | |||
314 | 1060 | The `timestamp` argument is ignored. The line returned is expected to be | ||
315 | 1061 | sent out by a logger which will add its own timestamp, so this one is | ||
316 | 1062 | superfluous. | ||
317 | 1063 | |||
318 | 1064 | :see: `IAccessLogFormatter` | ||
319 | 1065 | :see: `combinedLogFormatter` | ||
320 | 1066 | """ | ||
321 | 1067 | template = ( | ||
322 | 1068 | "{origin} {method} {uri} {proto} --> {status} " | ||
323 | 1069 | "(referrer: {referrer}; agent: {agent})" | ||
324 | 1070 | ) | ||
325 | 1071 | |||
326 | 1072 | def field(value, default): | ||
327 | 1073 | if value is None or len(value) == 0 or value.isspace(): | ||
328 | 1074 | return default | ||
329 | 1075 | elif isinstance(value, bytes): | ||
330 | 1076 | return value.decode("ascii", "replace") | ||
331 | 1077 | else: | ||
332 | 1078 | return value | ||
333 | 1079 | |||
334 | 1080 | def normaliseAddress(address): | ||
335 | 1081 | """Normalise an IP address.""" | ||
336 | 1082 | try: | ||
337 | 1083 | address = IPAddress(address) | ||
338 | 1084 | except AddrFormatError: | ||
339 | 1085 | return address # Hostname? | ||
340 | 1086 | else: | ||
341 | 1087 | if address.is_ipv4_mapped(): | ||
342 | 1088 | return address.ipv4() | ||
343 | 1089 | else: | ||
344 | 1090 | return address | ||
345 | 1091 | |||
346 | 1092 | def describeHttpStatus(code): | ||
347 | 1093 | try: | ||
348 | 1094 | code = HTTPStatus(code) | ||
349 | 1095 | except ValueError: | ||
350 | 1096 | if isinstance(code, int): | ||
351 | 1097 | return str(code) | ||
352 | 1098 | else: | ||
353 | 1099 | return "???" | ||
354 | 1100 | else: | ||
355 | 1101 | return "{code.value:d} {code.name}".format(code=code) | ||
356 | 1102 | |||
357 | 1103 | origin = field(request.getClientIP(), None) | ||
358 | 1104 | origin = "-" if origin is None else normaliseAddress(origin) | ||
359 | 1105 | |||
360 | 1106 | return template.format( | ||
361 | 1107 | referrer=field(request.getHeader(b"referer"), "-"), | ||
362 | 1108 | agent=field(request.getHeader(b"user-agent"), "-"), | ||
363 | 1109 | status=describeHttpStatus(request.code), origin=origin, | ||
364 | 1110 | method=field(request.method, "???"), uri=field(request.uri, "-"), | ||
365 | 1111 | proto=field(request.clientproto, "-"), | ||
366 | 1112 | ) |
I'm on the fence about this one. It looks like our previous logging was /almost/ in the "standard" HTTP log format[1], which has some utility: our current-format might make it easy to analyze the logs with any standard HTTP log analyzer. (ok, granted, maybe after a little grep/sed trickery.)
The best part of this change, to me, is the removal of the IPv4-mapped IPv6 address. That alone makes the log so much better already.
Thoughts?
[1]: /en.wikipedia. org/wiki/ Common_ Log_Format
https:/