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"]
|
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]
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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