Source code for tradeexecutor.webhook.http_log
"""Web server logging.
- Output HTTP request and response metadata to a separate log file
"""
import datetime
import logging
import threading
from pathlib import Path
from pyramid.registry import Registry
from pyramid.request import Request
from pyramid.response import Response
# Avoid logging.getLogger() here as we do not want this logger as the part of std logging system
http_logger = logging.Logger(name="HTTP traffic")
# Never propagate web logs as root logging output is assuemed to be public.
# IP addresses and such are logged to their own file.
# https://stackoverflow.com/a/67364351/315168
http_logger.propagate = False
#: Unique id for every request - response pair
_req_id_country = 0
_req_lock = threading.Lock()
[docs]def log_tween_factory(handler, registry: Registry):
def log_tween(request: Request):
global _req_id_country
with _req_lock:
# Should not timeout ever
_req_id_country += 1 # Not atomic https://stackoverflow.com/questions/1717393/is-the-operator-thread-safe-in-python
req_id = _req_id_country
country = request.headers.get("CF-IPCountry") or "<no country>"
ip_addr = request.headers.get("CF-Connecting-IP") or "<no CF IP>" # IPv4 or IPv6
user_agent = request.user_agent
http_logger.info("HTTP request #%d %s (%s): %s by %s", req_id, ip_addr, country, request.url, user_agent)
start = datetime.datetime.utcnow()
try:
response: Response = handler(request)
code = response.status_code
end = datetime.datetime.utcnow()
duration = end - start
http_logger.info("HTTP response #%d %s duration:%s %s", req_id, code, duration, request.url)
return response
except Exception as e:
http_logger.error("HTTP response #%d failed: %s", req_id, e)
raise
return log_tween
[docs]def configure_http_request_logging(main_log_path: Path) -> logging.Logger:
"""Configure HTTP requests to be logged to a separate file.
Must be called after other loggers have been configured.
:param main_log_path:
Trade execution log file.
We will prepare a log file with HTTP specific entries.
"""
assert isinstance(main_log_path, Path)
log_path = main_log_path.with_suffix(".http.log")
fmt = "%(asctime)s %(message)s"
formatter = logging.Formatter(fmt)
file_handler = logging.FileHandler(log_path)
file_handler.setFormatter(formatter)
file_handler.setLevel(logging.INFO)
http_logger.handlers.clear()
http_logger.addHandler(file_handler)
http_logger.info("Starting HTTP traffic log at %s", log_path)
return http_logger