85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
import logging
|
|
import os
|
|
import sys
|
|
import time
|
|
from typing import Any, Optional, Union
|
|
|
|
|
|
class AccessLogger:
|
|
def __init__(self, log_format: str, target: Union[logging.Logger, str, None]) -> None:
|
|
self.logger: Optional[logging.Logger] = None
|
|
self.log_format = log_format
|
|
if isinstance(target, logging.Logger):
|
|
self.logger = target
|
|
elif target is not None:
|
|
self.logger = logging.getLogger("hypercorn.access")
|
|
self.logger.propagate = False
|
|
self.logger.handlers = []
|
|
if target == "-":
|
|
self.logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
else:
|
|
self.logger.addHandler(logging.FileHandler(target))
|
|
self.logger.setLevel(logging.INFO)
|
|
|
|
def access(self, request: dict, response: dict, request_time: float) -> None:
|
|
if self.logger is not None:
|
|
self.logger.info(self.log_format, AccessLogAtoms(request, response, request_time))
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
if self.logger is None:
|
|
return lambda *_: None
|
|
else:
|
|
return getattr(self.logger, name)
|
|
|
|
|
|
class AccessLogAtoms(dict):
|
|
def __init__(self, request: dict, response: dict, request_time: float) -> None:
|
|
for name, value in request["headers"]:
|
|
self[f"{{{name.decode().lower()}}}i"] = value.decode()
|
|
for name, value in response["headers"]:
|
|
self[f"{{{name.decode().lower()}}}o"] = value.decode()
|
|
for name, value in os.environ.items():
|
|
self[f"{{{name.lower()}}}e"] = value
|
|
protocol = request.get("http_version", "ws")
|
|
client = request.get("client")
|
|
if client is None:
|
|
remote_addr = None
|
|
elif len(client) == 2:
|
|
remote_addr = f"{client[0]}:{client[1]}"
|
|
elif len(client) == 1:
|
|
remote_addr = client[0]
|
|
else: # make sure not to throw UnboundLocalError
|
|
remote_addr = f"<???{client}???>"
|
|
method = request.get("method", "GET")
|
|
self.update(
|
|
{
|
|
"h": remote_addr,
|
|
"l": "-",
|
|
"t": time.strftime("[%d/%b/%Y:%H:%M:%S %z]"),
|
|
"r": f"{method} {request['path']} {protocol}",
|
|
"s": response["status"],
|
|
"S": request["scheme"],
|
|
"m": method,
|
|
"U": request["path"],
|
|
"q": request["query_string"].decode(),
|
|
"H": protocol,
|
|
"b": self["{Content-Length}o"],
|
|
"B": self["{Content-Length}o"],
|
|
"f": self["{Referer}i"],
|
|
"a": self["{User-Agent}i"],
|
|
"T": int(request_time),
|
|
"D": int(request_time * 1_000_000),
|
|
"L": f"{request_time:.6f}",
|
|
"p": f"<{os.getpid()}>",
|
|
}
|
|
)
|
|
|
|
def __getitem__(self, key: str) -> str:
|
|
try:
|
|
if key.startswith("{"):
|
|
return super().__getitem__(key.lower())
|
|
else:
|
|
return super().__getitem__(key)
|
|
except KeyError:
|
|
return "-"
|