Commits vergleichen
10 Commits
568d0e295a
...
7af3762184
Autor | SHA1 | Datum |
---|---|---|
Sebastian Tobie | 7af3762184 | |
Sebastian Tobie | b3873f0239 | |
Sebastian Tobie | 1a63825ba6 | |
Sebastian Tobie | 0d4c72f7fc | |
Sebastian Tobie | dde8c74fa4 | |
Sebastian Tobie | 0b003e9679 | |
Sebastian Tobie | 455267fa2c | |
Sebastian Tobie | b36d2706eb | |
Sebastian Tobie | e46094b73a | |
Sebastian Tobie | a615906dcb |
|
@ -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
|
|
@ -24,6 +24,8 @@ classifiers = [
|
|||
]
|
||||
dependencies = ["gunicorn", "systemd_python"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
aiohttp = ["aiohttp"]
|
||||
[project.urls]
|
||||
Documentation = "https://github.com/unknown/gunicorn-logging-extension#readme"
|
||||
Issues = "https://github.com/unknown/gunicorn-logging-extension/issues"
|
||||
|
@ -34,6 +36,7 @@ path = "src/gunicorn_logging_extension/__init__.py"
|
|||
|
||||
[tool.hatch.build]
|
||||
directory = "../dist"
|
||||
|
||||
[tool.hatch.envs.default]
|
||||
dependencies = ["coverage[toml]>=6.5", "pytest"]
|
||||
[tool.hatch.envs.default.scripts]
|
||||
|
|
|
@ -5,13 +5,13 @@ import json
|
|||
import logging
|
||||
import os.path
|
||||
import threading
|
||||
import traceback
|
||||
from logging.config import dictConfig, fileConfig
|
||||
|
||||
from gunicorn.glogging import CONFIG_DEFAULTS
|
||||
from gunicorn.glogging import Logger as gLogger
|
||||
import traceback
|
||||
|
||||
__version__ = "0.0.7"
|
||||
__version__ = "0.0.11"
|
||||
|
||||
|
||||
CONFIG_DEFAULTS = {
|
||||
|
@ -35,6 +35,8 @@ CONFIG_DEFAULTS = {
|
|||
},
|
||||
}
|
||||
|
||||
REDIRECT_CODES = (301, 302, 303, 307, 308)
|
||||
|
||||
|
||||
class Logger(gLogger):
|
||||
access_log_format = "Access(%(s)s) %(r)s"
|
||||
|
@ -76,7 +78,9 @@ class Logger(gLogger):
|
|||
defaults["__file__"] = cfg.logconfig
|
||||
defaults["here"] = os.path.dirname(cfg.logconfig)
|
||||
fileConfig(
|
||||
cfg.logconfig, defaults=defaults, disable_existing_loggers=False
|
||||
cfg.logconfig,
|
||||
defaults=defaults,
|
||||
disable_existing_loggers=False,
|
||||
)
|
||||
else:
|
||||
msg = "Error: log config '%s' not found"
|
||||
|
@ -91,9 +95,7 @@ class Logger(gLogger):
|
|||
level = self.access_log.warning
|
||||
elif resp.status_code >= 500:
|
||||
level = self.access_log.error
|
||||
safe_atoms = self.atoms_wrapper_class(
|
||||
self.atoms(resp, req, environ, request_time)
|
||||
)
|
||||
safe_atoms = self.atoms_wrapper_class(self.atoms(resp, req, environ, request_time))
|
||||
extra = dict(
|
||||
METHOD=environ.get("REQUEST_METHOD"),
|
||||
PATH=environ.get("PATH_INFO"),
|
||||
|
@ -111,7 +113,11 @@ class Logger(gLogger):
|
|||
extra["REFERER"] = environ.get("HTTP_REFERER")
|
||||
if environ.get("HTTP_USER_AGENT", False):
|
||||
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:
|
||||
level(self.access_log_format, safe_atoms, extra=extra)
|
||||
except Exception:
|
||||
|
|
|
@ -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()
|
Laden…
In neuem Issue referenzieren