Commits vergleichen

...

10 Commits

4 geänderte Dateien mit 142 neuen und 7 gelöschten Zeilen

11
bau.sh Ausführbare Datei
Datei anzeigen

@ -0,0 +1,11 @@
#!/bin/sh
pwd=$PWD
rm -rf dist
for dir in */ ; do
if [ -r "$dir/pyproject.toml" ] ; then
cd "$dir"
hatch build
cd "$pwd"
fi
done
hatch publish -r sebastian

Datei anzeigen

@ -24,6 +24,8 @@ classifiers = [
] ]
dependencies = ["gunicorn", "systemd_python"] dependencies = ["gunicorn", "systemd_python"]
[project.optional-dependencies]
aiohttp = ["aiohttp"]
[project.urls] [project.urls]
Documentation = "https://github.com/unknown/gunicorn-logging-extension#readme" Documentation = "https://github.com/unknown/gunicorn-logging-extension#readme"
Issues = "https://github.com/unknown/gunicorn-logging-extension/issues" Issues = "https://github.com/unknown/gunicorn-logging-extension/issues"
@ -34,6 +36,7 @@ path = "src/gunicorn_logging_extension/__init__.py"
[tool.hatch.build] [tool.hatch.build]
directory = "../dist" directory = "../dist"
[tool.hatch.envs.default] [tool.hatch.envs.default]
dependencies = ["coverage[toml]>=6.5", "pytest"] dependencies = ["coverage[toml]>=6.5", "pytest"]
[tool.hatch.envs.default.scripts] [tool.hatch.envs.default.scripts]

Datei anzeigen

@ -5,13 +5,13 @@ import json
import logging import logging
import os.path import os.path
import threading import threading
import traceback
from logging.config import dictConfig, fileConfig from logging.config import dictConfig, fileConfig
from gunicorn.glogging import CONFIG_DEFAULTS from gunicorn.glogging import CONFIG_DEFAULTS
from gunicorn.glogging import Logger as gLogger from gunicorn.glogging import Logger as gLogger
import traceback
__version__ = "0.0.7" __version__ = "0.0.11"
CONFIG_DEFAULTS = { CONFIG_DEFAULTS = {
@ -35,6 +35,8 @@ CONFIG_DEFAULTS = {
}, },
} }
REDIRECT_CODES = (301, 302, 303, 307, 308)
class Logger(gLogger): class Logger(gLogger):
access_log_format = "Access(%(s)s) %(r)s" access_log_format = "Access(%(s)s) %(r)s"
@ -76,7 +78,9 @@ class Logger(gLogger):
defaults["__file__"] = cfg.logconfig defaults["__file__"] = cfg.logconfig
defaults["here"] = os.path.dirname(cfg.logconfig) defaults["here"] = os.path.dirname(cfg.logconfig)
fileConfig( fileConfig(
cfg.logconfig, defaults=defaults, disable_existing_loggers=False cfg.logconfig,
defaults=defaults,
disable_existing_loggers=False,
) )
else: else:
msg = "Error: log config '%s' not found" msg = "Error: log config '%s' not found"
@ -91,9 +95,7 @@ class Logger(gLogger):
level = self.access_log.warning level = self.access_log.warning
elif resp.status_code >= 500: elif resp.status_code >= 500:
level = self.access_log.error level = self.access_log.error
safe_atoms = self.atoms_wrapper_class( safe_atoms = self.atoms_wrapper_class(self.atoms(resp, req, environ, request_time))
self.atoms(resp, req, environ, request_time)
)
extra = dict( extra = dict(
METHOD=environ.get("REQUEST_METHOD"), METHOD=environ.get("REQUEST_METHOD"),
PATH=environ.get("PATH_INFO"), PATH=environ.get("PATH_INFO"),
@ -111,7 +113,11 @@ class Logger(gLogger):
extra["REFERER"] = environ.get("HTTP_REFERER") extra["REFERER"] = environ.get("HTTP_REFERER")
if environ.get("HTTP_USER_AGENT", False): if environ.get("HTTP_USER_AGENT", False):
extra["USER_AGENT"] = environ.get("HTTP_USER_AGENT") extra["USER_AGENT"] = environ.get("HTTP_USER_AGENT")
if resp.status_code in REDIRECT_CODES:
for header, value in resp.headers:
if header == "Location":
extra["LOCATION"] = value
break
try: try:
level(self.access_log_format, safe_atoms, extra=extra) level(self.access_log_format, safe_atoms, extra=extra)
except Exception: except Exception:

Datei anzeigen

@ -0,0 +1,115 @@
import asyncio
import os
from typing import Type
from aiohttp import web
from aiohttp.abc import AbstractAccessLogger
from aiohttp.web_app import Application
from aiohttp.web_request import BaseRequest
from aiohttp.web_response import StreamResponse
from . import REDIRECT_CODES
try:
import uvloop
from aiohttp.worker import GunicornUVLoopWebWorker as GunicornWebWorker
except ImportError:
from aiohttp.worker import GunicornWebWorker
class AccessLogger(AbstractAccessLogger):
def log(self, request: BaseRequest, response: StreamResponse, time: float):
level = self.logger.info
if response.status >= 400:
level = self.logger.warning
elif response.status >= 500:
level = self.logger.error
extra = dict(
METHOD=request.method,
PATH=request.rel_url,
QUERY=request.query_string,
PROTOCOL=request.scheme,
TIME=time,
STATUS_CODE=response.status_code,
REMOTE=request.remote,
LENGTH=response.content_length,
)
if request.headers.get("Referer", False):
extra["REFERER"] = request.headers.get("Referer")
if request.headers.get("user-agent", False):
extra["USER_AGENT"] = request.headers.get("user-agent")
location = ""
if response.status_code in REDIRECT_CODES:
extra["LOCATION"] = request.headers.get("location")
location = f" -> {extra['LOCATION']}"
level(
f"Access({response.status}) {request.method} {request.rel_url}{location}",
extra=extra,
)
class ExtendedGunicornWebWorker(GunicornWebWorker):
access_log_class: Type[AbstractAccessLogger] = AccessLogger
async def _run(self) -> None:
runner = None
if isinstance(self.wsgi, Application):
app = self.wsgi
elif asyncio.iscoroutinefunction(self.wsgi):
wsgi = await self.wsgi()
if isinstance(wsgi, web.AppRunner):
runner = wsgi
app = runner.app
else:
app = wsgi
else:
raise RuntimeError(
"wsgi app should be either Application or "
"async function returning Application, got {}".format(self.wsgi)
)
if runner is None:
runner = web.AppRunner(
app,
logger=self.log,
keepalive_timeout=self.cfg.keepalive,
access_log=self.log.access_log,
access_log_class=self.access_log_class,
shutdown_timeout=self.cfg.graceful_timeout / 100 * 95,
)
await runner.setup()
ctx = self._create_ssl_context(self.cfg) if self.cfg.is_ssl else None
runner = runner
assert runner is not None
server = runner.server
assert server is not None
for sock in self.sockets:
site = web.SockSite(
runner,
sock,
ssl_context=ctx,
)
await site.start()
# If our parent changed then we shut down.
pid = os.getpid()
try:
while self.alive: # type: ignore[has-type]
self.notify()
cnt = server.requests_count
if self.max_requests and cnt > self.max_requests:
self.alive = False
self.log.info("Max requests, shutting down: %s", self)
elif pid == os.getpid() and self.ppid != os.getppid():
self.alive = False
self.log.info("Parent changed, shutting down: %s", self)
else:
await self._wait_next_notify()
except BaseException:
pass
await runner.cleanup()